add addon template reading (#2648)

render definition into application

new format

fix cue render

fix one bad registry make all addon unusable
This commit is contained in:
qiaozp
2021-11-08 16:34:03 +08:00
committed by GitHub
parent eb258fae66
commit b98cec127e
2 changed files with 118 additions and 86 deletions
+7 -15
View File
@@ -22,6 +22,7 @@ import (
"github.com/getkin/kin-openapi/openapi3"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
@@ -103,13 +104,6 @@ type AddonMeta struct {
Dependencies []*AddonDependency `json:"dependencies,omitempty"`
}
// Definition defines the metadata for a single X-Definition
type Definition struct {
Kind string `json:"kind"`
Name string `json:"name"`
Description string `json:"description"`
}
// AddonElementFile can be addon's definition or addon's component
type AddonElementFile struct {
Data string
@@ -122,14 +116,12 @@ type DetailAddonResponse struct {
AddonMeta
// More details about the addon, e.g. README
Detail string `json:"detail,omitempty"`
Definitions []*Definition `json:"definitions"`
Parameters string `json:"parameters"`
CUETemplates []AddonElementFile `json:"cue_templates"`
YAMLTemplates []AddonElementFile `json:"yaml_templates,omitempty"`
Detail string `json:"detail,omitempty"`
Definitions []AddonElementFile `json:"definitions"`
Parameters string `json:"parameters"`
CUETemplates []AddonElementFile `json:"cue_templates"`
YAMLTemplates []AddonElementFile `json:"yaml_templates,omitempty"`
AppTemplate *v1beta1.Application `json:"app_template"`
}
// AddonStatusResponse defines the format of addon status response
+111 -71
View File
@@ -47,8 +47,11 @@ const (
// AddonMetadataFileName is the addon meatadata.yaml file name
AddonMetadataFileName string = "metadata.yaml"
// AddonTemplateDirName is the addon template/ dir name
AddonTemplateDirName string = "template"
// AddonTemplateFileName is the addon template.yaml dir name
AddonTemplateFileName string = "template.yaml"
// AddonResourcesDirName is the addon resources/ dir name
AddonResourcesDirName string = "resources"
// AddonDefinitionsDirName is the addon definitions/ dir name
AddonDefinitionsDirName string = "definitions"
@@ -143,7 +146,8 @@ func (u *addonUsecaseImpl) ListAddons(ctx context.Context, detailed bool, regist
}
gitAddons, err := getAddonsFromGit(r.Git.URL, r.Git.Path, r.Git.Token, detailed)
if err != nil {
return nil, err
log.Logger.Errorf("fail to get addons from registry %s", r.Name)
continue
}
addons = mergeAddons(addons, gitAddons)
}
@@ -213,19 +217,25 @@ func renderApplication(addon *apis.DetailAddonResponse, args *apis.EnableAddonRe
if args == nil {
args = &apis.EnableAddonRequest{Args: map[string]string{}}
}
app := &v1beta1.Application{
TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
ObjectMeta: metav1.ObjectMeta{
Name: AddonName2AppName(addon.Name),
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
oam.LabelAddonName: addon.Name,
app := addon.AppTemplate
if app == nil {
app = &v1beta1.Application{
TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
ObjectMeta: metav1.ObjectMeta{
Name: AddonName2AppName(addon.Name),
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
oam.LabelAddonName: addon.Name,
},
},
},
Spec: v1beta1.ApplicationSpec{
Components: []common2.ApplicationComponent{},
},
Spec: v1beta1.ApplicationSpec{
Components: []common2.ApplicationComponent{},
},
}
}
app.Name = AddonName2AppName(addon.Name)
app.Labels = util.MergeMapOverrideWithDst(app.Labels, map[string]string{oam.LabelAddonName: addon.Name})
for _, tmpl := range addon.YAMLTemplates {
comp, err := renderRawComponent(tmpl)
if err != nil {
@@ -234,40 +244,22 @@ func renderApplication(addon *apis.DetailAddonResponse, args *apis.EnableAddonRe
app.Spec.Components = append(app.Spec.Components, *comp)
}
for _, tmpl := range addon.CUETemplates {
yamlData, err := renderCUETemplate(tmpl.Data, addon.Parameters, args.Args)
comp, err := renderCUETemplate(tmpl, addon.Parameters, args.Args)
if err != nil {
log.Logger.Errorf("failed to render CUE template: %v", err)
return nil, bcode.ErrAddonRenderFail
}
comp, err := renderRawComponent(apis.AddonElementFile{Data: yamlData, Name: tmpl.Name, Path: tmpl.Path})
app.Spec.Components = append(app.Spec.Components, *comp)
}
for _, def := range addon.Definitions {
comp, err := renderRawComponent(def)
if err != nil {
return nil, err
}
app.Spec.Components = append(app.Spec.Components, *comp)
}
return app, nil
}
func renderCUETemplate(template string, parameters string, args map[string]string) (string, error) {
bt, err := json.Marshal(args)
if err != nil {
return "", err
}
var paramFile = cuemodel.ParameterFieldName + ": {}"
if string(bt) != "null" {
paramFile = fmt.Sprintf("%s: %s", cuemodel.ParameterFieldName, string(bt))
}
param := fmt.Sprintf("%s\n%s", paramFile, parameters)
v, err := value.NewValue(param, nil, "")
if err != nil {
return "", err
}
out, err := v.LookupByScript(fmt.Sprintf("{%s}", template))
if err != nil {
return "", err
}
b, err := cueyaml.Encode(out.CueValue())
return string(b), err
return app, nil
}
func (u *addonUsecaseImpl) EnableAddon(ctx context.Context, name string, args apis.EnableAddonRequest) error {
@@ -320,6 +312,41 @@ func renderRawComponent(elem apis.AddonElementFile) (*common2.ApplicationCompone
return &baseRawComponent, nil
}
// renderCUETemplate will return a component from cue template
func renderCUETemplate(elem apis.AddonElementFile, parameters string, args map[string]string) (*common2.ApplicationComponent, error) {
bt, err := json.Marshal(args)
if err != nil {
return nil, err
}
var paramFile = cuemodel.ParameterFieldName + ": {}"
if string(bt) != "null" {
paramFile = fmt.Sprintf("%s: %s", cuemodel.ParameterFieldName, string(bt))
}
param := fmt.Sprintf("%s\n%s", paramFile, parameters)
v, err := value.NewValue(param, nil, "")
if err != nil {
return nil, err
}
out, err := v.LookupByScript(fmt.Sprintf("{%s}", elem.Data))
if err != nil {
return nil, err
}
compContent, err := out.LookupValue("output")
if err != nil {
return nil, err
}
b, err := cueyaml.Encode(compContent.CueValue())
comp := common2.ApplicationComponent{
Name: strings.Join(append(elem.Path, elem.Name), "-"),
}
err = yaml.Unmarshal(b, &comp)
if err != nil {
return nil, err
}
return &comp, err
}
func addonRegistryModelFromCreateAddonRegistryRequest(req apis.CreateAddonRegistryRequest) *model.AddonRegistry {
return &model.AddonRegistry{
Name: req.Name,
@@ -390,11 +417,16 @@ func getAddonsFromGit(baseURL, dir, token string, detailed bool) ([]*apis.Detail
break
}
err = readDefinitions(addonRes, gith, file)
case AddonTemplateDirName:
case AddonResourcesDirName:
if !detailed {
break
}
err = readTemplates(addonRes, gith, file)
err = readResources(addonRes, gith, file)
case AddonTemplateFileName:
if !detailed {
break
}
err = readTemplate(addonRes, gith, file)
}
if err != nil {
@@ -408,15 +440,38 @@ func getAddonsFromGit(baseURL, dir, token string, detailed bool) ([]*apis.Detail
return addons, nil
}
func readTemplates(addon *apis.DetailAddonResponse, h *gitHelper, dir *github.RepositoryContent) error {
dirPath := strings.Split(dir.GetPath(), "/")
// remove
for i, d := range dirPath {
if d == AddonTemplateDirName {
dirPath = dirPath[i:]
break
func cutPathUntil(path []string, end string) ([]string, error) {
for i, d := range path {
if d == end {
return path[i:], nil
}
dirPath = dirPath[i:]
}
return nil, errors.New("cut path fail, target directory name not found")
}
func readTemplate(addon *apis.DetailAddonResponse, h *gitHelper, file *github.RepositoryContent) error {
content, _, _, err := h.Client.Repositories.GetContents(context.Background(), h.Meta.Owner, h.Meta.Repo, *file.Path, nil)
if err != nil {
return err
}
data, err := content.GetContent()
if err != nil {
return err
}
dec := k8syaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
addon.AppTemplate = &v1beta1.Application{}
_, _, err = dec.Decode([]byte(data), nil, addon.AppTemplate)
if err != nil {
return err
}
return nil
}
func readResources(addon *apis.DetailAddonResponse, h *gitHelper, dir *github.RepositoryContent) error {
dirPath := strings.Split(dir.GetPath(), "/")
dirPath, err := cutPathUntil(dirPath, AddonResourcesDirName)
if err != nil {
return err
}
_, files, _, err := h.Client.Repositories.GetContents(context.Background(), h.Meta.Owner, h.Meta.Repo, *dir.Path, nil)
@@ -446,7 +501,7 @@ func readTemplates(addon *apis.DetailAddonResponse, h *gitHelper, dir *github.Re
addon.YAMLTemplates = append(addon.YAMLTemplates, apis.AddonElementFile{Data: b, Name: file.GetName(), Path: dirPath})
}
case "dir":
err = readTemplates(addon, h, file)
err = readResources(addon, h, file)
if err != nil {
return err
}
@@ -456,6 +511,12 @@ func readTemplates(addon *apis.DetailAddonResponse, h *gitHelper, dir *github.Re
}
func readDefinitions(addon *apis.DetailAddonResponse, h *gitHelper, dir *github.RepositoryContent) error {
dirPath := strings.Split(dir.GetPath(), "/")
dirPath, err := cutPathUntil(dirPath, AddonDefinitionsDirName)
if err != nil {
return err
}
_, files, _, err := h.Client.Repositories.GetContents(context.Background(), h.Meta.Owner, h.Meta.Repo, *dir.Path, nil)
if err != nil {
return err
@@ -471,11 +532,7 @@ func readDefinitions(addon *apis.DetailAddonResponse, h *gitHelper, dir *github.
if err != nil {
return err
}
d, err := getDefinitionMetaFromYAML(b)
if err != nil {
return err
}
addon.Definitions = append(addon.Definitions, d)
addon.Definitions = append(addon.Definitions, apis.AddonElementFile{Data: b, Name: file.GetName(), Path: dirPath})
case "dir":
err = readDefinitions(addon, h, file)
if err != nil {
@@ -486,23 +543,6 @@ func readDefinitions(addon *apis.DetailAddonResponse, h *gitHelper, dir *github.
return nil
}
func getDefinitionMetaFromYAML(data string) (*apis.Definition, error) {
dec := k8syaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
obj := &unstructured.Unstructured{}
_, _, err := dec.Decode([]byte(data), nil, obj)
if err != nil {
return nil, bcode.ErrAddonRenderFail
}
d := &apis.Definition{
Name: obj.GetName(),
Kind: obj.GetKind(),
}
if ann := obj.GetAnnotations(); ann != nil {
d.Description = ann[types.AnnDescription]
}
return d, nil
}
func readMetadata(addon *apis.DetailAddonResponse, h *gitHelper, file *github.RepositoryContent) error {
content, _, _, err := h.Client.Repositories.GetContents(context.Background(), h.Meta.Owner, h.Meta.Repo, *file.Path, nil)
if err != nil {