|
1 | 1 | package configmanager |
2 | 2 |
|
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + |
| 6 | + "gopkg.in/yaml.v3" |
| 7 | + |
| 8 | + "github.com/devstream-io/devstream/pkg/util/log" |
| 9 | + "github.com/devstream-io/devstream/pkg/util/mapz" |
| 10 | + "github.com/devstream-io/devstream/pkg/util/scm" |
| 11 | +) |
| 12 | + |
| 13 | +const ( |
| 14 | + repoScaffoldingPluginName = "repo-scaffolding" |
| 15 | +) |
| 16 | + |
3 | 17 | type ( |
4 | | - App struct { |
5 | | - Name string `yaml:"name" mapstructure:"name"` |
6 | | - Spec RawOptions `yaml:"spec" mapstructure:"spec"` |
7 | | - Repo *Repo `yaml:"repo" mapstructure:"repo"` |
8 | | - RepoTemplate *RepoTemplate `yaml:"repoTemplate" mapstructure:"repoTemplate"` |
9 | | - CIPipelines []PipelineTemplate `yaml:"ci" mapstructure:"ci"` |
10 | | - CDPipelines []PipelineTemplate `yaml:"cd" mapstructure:"cd"` |
| 18 | + appRaw struct { |
| 19 | + Name string `yaml:"name" mapstructure:"name"` |
| 20 | + Spec map[string]any `yaml:"spec" mapstructure:"spec"` |
| 21 | + Repo *scm.SCMInfo `yaml:"repo" mapstructure:"repo"` |
| 22 | + RepoTemplate *scm.SCMInfo `yaml:"repoTemplate" mapstructure:"repoTemplate"` |
| 23 | + CIRawConfigs []pipelineRaw `yaml:"ci" mapstructure:"ci"` |
| 24 | + CDRawConfigs []pipelineRaw `yaml:"cd" mapstructure:"cd"` |
11 | 25 | } |
12 | | - Apps []App |
| 26 | +) |
13 | 27 |
|
14 | | - // AppInConfig is the raw structured data in config file. |
15 | | - // The main difference between App and AppInConfig is "CI" and "CD" field. |
16 | | - // The "CIRawConfigs" is the raw data of "CI" field defined in config file, |
17 | | - // which will be rendered to "CIPipelines" field in App with "PipelineTemplates". |
18 | | - // The "CDRawConfigs" is similar to "CIRawConfigs". |
19 | | - AppInConfig struct { |
20 | | - Name string `yaml:"name" mapstructure:"name"` |
21 | | - Spec RawOptions `yaml:"spec" mapstructure:"spec"` |
22 | | - Repo *Repo `yaml:"repo" mapstructure:"repo"` |
23 | | - RepoTemplate *RepoTemplate `yaml:"repoTemplate" mapstructure:"repoTemplate"` |
24 | | - CIRawConfigs []CICD `yaml:"ci" mapstructure:"ci"` |
25 | | - CDRawConfigs []CICD `yaml:"cd" mapstructure:"cd"` |
| 28 | +// getAppTools return app tools |
| 29 | +func getAppTools(appStr string, globalVars map[string]any, templateMap map[string]string) (Tools, error) { |
| 30 | + //1. render appStr with globalVars |
| 31 | + appRenderStr, err := renderConfigWithVariables(appStr, globalVars) |
| 32 | + if err != nil { |
| 33 | + log.Debugf("configmanager/app %s render globalVars %+v failed", appRenderStr, globalVars) |
| 34 | + return nil, fmt.Errorf("app render globalVars failed: %w", err) |
26 | 35 | } |
27 | | - |
28 | | - CICD struct { |
29 | | - Type string `yaml:"type" mapstructure:"type"` |
30 | | - TemplateName string `yaml:"templateName" mapstructure:"templateName"` |
31 | | - Options RawOptions `yaml:"options" mapstructure:"options"` |
32 | | - Vars RawOptions `yaml:"vars" mapstructure:"vars"` |
| 36 | + // 2. unmarshal appRaw config for render pipelineTemplate |
| 37 | + var rawData appRaw |
| 38 | + if err := yaml.Unmarshal([]byte(appRenderStr), &rawData); err != nil { |
| 39 | + return nil, fmt.Errorf("app parse yaml failed: %w", err) |
33 | 40 | } |
34 | | -) |
| 41 | + rawData.setDefault() |
| 42 | + appVars := mapz.MergeMaps(globalVars, rawData.Spec) |
| 43 | + // 3. generate app repo and tempalte repo from scmInfo |
| 44 | + repoScaffoldingTool, err := rawData.getRepoTemplateTool(appVars) |
| 45 | + if err != nil { |
| 46 | + return nil, fmt.Errorf("app[%s] get repo failed: %w", rawData.Name, err) |
| 47 | + } |
| 48 | + // 4. get ci/cd pipelineTemplates |
| 49 | + tools, err := rawData.getPipelineTools(templateMap, appVars) |
| 50 | + if err != nil { |
| 51 | + return nil, fmt.Errorf("app[%s] get pipeline tools failed: %w", rawData.Name, err) |
| 52 | + } |
| 53 | + if repoScaffoldingTool != nil { |
| 54 | + tools = append(tools, *repoScaffoldingTool) |
| 55 | + } |
| 56 | + return tools, nil |
| 57 | +} |
35 | 58 |
|
36 | | -func (apps Apps) validate() (errs []error) { |
37 | | - for _, app := range apps { |
38 | | - if err := app.validate(); err != nil { |
39 | | - errs = append(errs, err) |
| 59 | +// getAppPipelineTool generate ci/cd tools from app config |
| 60 | +func (a *appRaw) getPipelineTools(templateMap map[string]string, appVars map[string]any) (Tools, error) { |
| 61 | + allPipelineRaw := append(a.CIRawConfigs, a.CDRawConfigs...) |
| 62 | + var tools Tools |
| 63 | + for _, p := range allPipelineRaw { |
| 64 | + t, err := p.newPipeline(a.Repo, templateMap, appVars) |
| 65 | + if err != nil { |
| 66 | + return nil, err |
40 | 67 | } |
| 68 | + pipelineTool, err := t.getPipelineTool(a.Name) |
| 69 | + if err != nil { |
| 70 | + return nil, err |
| 71 | + } |
| 72 | + pipelineTool.DependsOn = a.getRepoTemplateDepends() |
| 73 | + tools = append(tools, *pipelineTool) |
41 | 74 | } |
42 | | - return errs |
| 75 | + return tools, nil |
43 | 76 | } |
44 | 77 |
|
45 | | -func (app *App) validate() error { |
46 | | - if app.Repo.RepoInfo != nil && app.Repo.RepoInfo.Name == "" { |
47 | | - app.Repo.RepoInfo.Name = app.Name |
| 78 | +// getRepoTemplateTool will use repo-scaffolding plugin for app |
| 79 | +func (a *appRaw) getRepoTemplateTool(appVars map[string]any) (*Tool, error) { |
| 80 | + if a.Repo == nil { |
| 81 | + return nil, fmt.Errorf("app.repo field can't be empty") |
48 | 82 | } |
49 | | - |
50 | | - err := app.Repo.FillAndValidate() |
| 83 | + appRepo, err := a.Repo.BuildRepoInfo() |
51 | 84 | if err != nil { |
52 | | - return err |
| 85 | + return nil, fmt.Errorf("configmanager[app] parse repo failed: %w", err) |
53 | 86 | } |
54 | | - |
55 | | - if app.RepoTemplate != nil { |
56 | | - err = app.RepoTemplate.FillAndValidate() |
| 87 | + if a.RepoTemplate != nil { |
| 88 | + templateRepo, err := a.RepoTemplate.BuildRepoInfo() |
57 | 89 | if err != nil { |
58 | | - return err |
| 90 | + return nil, fmt.Errorf("configmanager[app] parse repoTemplate failed: %w", err) |
59 | 91 | } |
| 92 | + return newTool( |
| 93 | + repoScaffoldingPluginName, a.Name, RawOptions{ |
| 94 | + "destinationRepo": RawOptions(appRepo.Encode()), |
| 95 | + "sourceRepo": RawOptions(templateRepo.Encode()), |
| 96 | + "vars": RawOptions(appVars), |
| 97 | + }, |
| 98 | + ), nil |
60 | 99 | } |
61 | | - |
62 | | - return nil |
| 100 | + return nil, nil |
63 | 101 | } |
64 | 102 |
|
65 | | -func (config *ConfigRaw) constructApps(ciPipelines, cdPipelines [][]PipelineTemplate) *Config { |
66 | | - configFinal := &Config{} |
67 | | - configFinal.PluginDir = config.PluginDir |
68 | | - configFinal.State = config.State |
69 | | - configFinal.Tools = config.Tools |
70 | | - |
71 | | - for i, app := range config.AppsInConfig { |
72 | | - appFinal := App{ |
73 | | - Name: app.Name, |
74 | | - Spec: app.Spec, |
75 | | - Repo: app.Repo, |
76 | | - RepoTemplate: app.RepoTemplate, |
77 | | - } |
78 | | - appFinal.CIPipelines = ciPipelines[i] |
79 | | - appFinal.CDPipelines = cdPipelines[i] |
80 | | - configFinal.Apps = append(configFinal.Apps, appFinal) |
| 103 | +// setDefault will set repoName to appName if repo.name field is empty |
| 104 | +func (a *appRaw) setDefault() { |
| 105 | + if a.Repo != nil && a.Repo.Name == "" { |
| 106 | + a.Repo.Name = a.Name |
81 | 107 | } |
| 108 | +} |
82 | 109 |
|
83 | | - return configFinal |
| 110 | +// since all plugin depends on code is deployed, get dependsOn for repoTemplate |
| 111 | +func (a *appRaw) getRepoTemplateDepends() []string { |
| 112 | + var dependsOn []string |
| 113 | + // if a.RepoTemplate is configured, pipeline need to wait reposcaffolding finished |
| 114 | + if a.RepoTemplate != nil { |
| 115 | + dependsOn = []string{fmt.Sprintf("%s.%s", repoScaffoldingPluginName, a.Name)} |
| 116 | + } |
| 117 | + return dependsOn |
84 | 118 | } |
0 commit comments