Skip to content

Commit 350f65e

Browse files
authored
Merge pull request #1259 from merico-dev/feat-app-spec-option
feat: app support ci/cd default config for app.spec
2 parents 8101e44 + 9319603 commit 350f65e

File tree

23 files changed

+542
-399
lines changed

23 files changed

+542
-399
lines changed

internal/pkg/configmanager/app.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,30 @@ const (
1010
repoScaffoldingPluginName = "repo-scaffolding"
1111
)
1212

13+
type repoTemplate struct {
14+
*scm.SCMInfo `yaml:",inline"`
15+
Vars map[string]any `yaml:"vars"`
16+
}
17+
1318
type app struct {
14-
Name string `yaml:"name" mapstructure:"name"`
15-
Spec map[string]any `yaml:"spec" mapstructure:"spec"`
16-
Repo *scm.SCMInfo `yaml:"repo" mapstructure:"repo"`
17-
RepoTemplate *scm.SCMInfo `yaml:"repoTemplate" mapstructure:"repoTemplate"`
18-
CIRawConfigs []pipelineRaw `yaml:"ci" mapstructure:"ci"`
19-
CDRawConfigs []pipelineRaw `yaml:"cd" mapstructure:"cd"`
19+
Name string `yaml:"name" mapstructure:"name"`
20+
Spec *appSpec `yaml:"spec" mapstructure:"spec"`
21+
Repo *scm.SCMInfo `yaml:"repo" mapstructure:"repo"`
22+
RepoTemplate *repoTemplate `yaml:"repoTemplate" mapstructure:"repoTemplate"`
23+
CIRawConfigs []pipelineRaw `yaml:"ci" mapstructure:"ci"`
24+
CDRawConfigs []pipelineRaw `yaml:"cd" mapstructure:"cd"`
2025
}
2126

2227
// getAppPipelineTool generate ci/cd tools from app config
2328
func (a *app) generateCICDToolsFromAppConfig(templateMap map[string]string, appVars map[string]any) (Tools, error) {
2429
allPipelineRaw := append(a.CIRawConfigs, a.CDRawConfigs...)
2530
var tools Tools
2631
for _, p := range allPipelineRaw {
27-
t, err := p.newPipeline(a.Repo, templateMap, appVars)
32+
t, err := p.getPipelineTemplate(templateMap, appVars)
2833
if err != nil {
2934
return nil, err
3035
}
31-
pipelineTool, err := t.getPipelineTool(a.Name)
36+
pipelineTool, err := t.generatePipelineTool(a)
3237
if err != nil {
3338
return nil, err
3439
}
@@ -39,7 +44,7 @@ func (a *app) generateCICDToolsFromAppConfig(templateMap map[string]string, appV
3944
}
4045

4146
// getRepoTemplateTool will use repo-scaffolding plugin for app
42-
func (a *app) getRepoTemplateTool(appVars map[string]any) (*Tool, error) {
47+
func (a *app) getRepoTemplateTool() (*Tool, error) {
4348
if a.Repo == nil {
4449
return nil, fmt.Errorf("app.repo field can't be empty")
4550
}
@@ -56,7 +61,7 @@ func (a *app) getRepoTemplateTool(appVars map[string]any) (*Tool, error) {
5661
repoScaffoldingPluginName, a.Name, RawOptions{
5762
"destinationRepo": RawOptions(appRepo.Encode()),
5863
"sourceRepo": RawOptions(templateRepo.Encode()),
59-
"vars": RawOptions(appVars),
64+
"vars": RawOptions(a.RepoTemplate.Vars),
6065
},
6166
), nil
6267
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package configmanager
2+
3+
import (
4+
"github.com/imdario/mergo"
5+
6+
"github.com/devstream-io/devstream/pkg/util/log"
7+
"github.com/devstream-io/devstream/pkg/util/mapz"
8+
)
9+
10+
// appSpec is app special options
11+
type appSpec struct {
12+
// language config
13+
Language string `yaml:"language" mapstructure:"language"`
14+
FrameWork string `yaml:"framework" mapstructure:"framework"`
15+
}
16+
17+
// merge will merge vars and appSpec
18+
func (s *appSpec) merge(vars map[string]any) map[string]any {
19+
specMap, err := mapz.DecodeStructToMap(s)
20+
if err != nil {
21+
log.Warnf("appspec %+v decode failed: %+v", s, err)
22+
return map[string]any{}
23+
}
24+
if err := mergo.Merge(&specMap, vars); err != nil {
25+
log.Warnf("appSpec %+v merge map failed: %+v", s, err)
26+
return vars
27+
}
28+
return specMap
29+
}
30+
31+
func (s *appSpec) updatePiplineOption(options RawOptions) {
32+
if _, exist := options["language"]; !exist && s.hasLanguageConfig() {
33+
options["language"] = RawOptions{
34+
"name": s.Language,
35+
"framework": s.FrameWork,
36+
}
37+
}
38+
}
39+
40+
func (s *appSpec) hasLanguageConfig() bool {
41+
return s.Language != "" || s.FrameWork != ""
42+
}

internal/pkg/configmanager/config.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"gopkg.in/yaml.v3"
77

88
"github.com/devstream-io/devstream/pkg/util/log"
9-
"github.com/devstream-io/devstream/pkg/util/mapz"
109
)
1110

1211
// Config is a general config in DevStream.
@@ -55,16 +54,15 @@ func (c *Config) getToolsWithVarsFromApp(a app) (Tools, error) {
5554
return nil, fmt.Errorf("app parse yaml failed: %w", err)
5655
}
5756

58-
rawApp.setDefault()
59-
appVars := mapz.Merge(c.Vars, rawApp.Spec)
60-
6157
// 3. generate app repo and template repo from scmInfo
62-
repoScaffoldingTool, err := rawApp.getRepoTemplateTool(appVars)
58+
rawApp.setDefault()
59+
repoScaffoldingTool, err := rawApp.getRepoTemplateTool()
6360
if err != nil {
6461
return nil, fmt.Errorf("app[%s] get repo failed: %w", rawApp.Name, err)
6562
}
6663

6764
// 4. get ci/cd pipelineTemplates
65+
appVars := rawApp.Spec.merge(c.Vars)
6866
tools, err := rawApp.generateCICDToolsFromAppConfig(c.pipelineTemplateMap, appVars)
6967
if err != nil {
7068
return nil, fmt.Errorf("app[%s] get pipeline tools failed: %w", rawApp.Name, err)

internal/pkg/configmanager/configmanager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,6 @@ func (m *Manager) getConfigFromFile() (*Config, error) {
7474

7575
// escapeBrackets is used to escape []byte(": [[xxx]]xxx\n") to []byte(": \"[[xxx]]\"xxx\n")
7676
func escapeBrackets(param []byte) []byte {
77-
re := regexp.MustCompile(`([^:]+:)(\s*)(\[\[[^\]]+\]\][^\s]*)`)
77+
re := regexp.MustCompile(`([^:]+:)(\s*)((\[\[[^\]]+\]\][^\s\[]*)+)[^\s#\n]*`)
7878
return re.ReplaceAll(param, []byte("$1$2\"$3\""))
7979
}

internal/pkg/configmanager/configmanager_test.go

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ pipelineTemplates:
8686
namespace: [[ argocdNamespace ]] # you can use global vars in templates
8787
destination:
8888
server: https://kubernetes.default.svc
89-
namespace: default
89+
namespace: devstream-io
9090
source:
9191
valuefile: values.yaml
9292
path: helm/[[ app ]]
@@ -128,6 +128,10 @@ var _ = Describe("LoadConfig", func() {
128128
Options: RawOptions{
129129
"instanceID": "service-a",
130130
"pipeline": RawOptions{
131+
"language": RawOptions{
132+
"name": "python",
133+
"framework": "django",
134+
},
131135
"docker": RawOptions{
132136
"registry": RawOptions{
133137
"repository": "service-a",
@@ -159,28 +163,17 @@ var _ = Describe("LoadConfig", func() {
159163
},
160164
Options: RawOptions{
161165
"instanceID": "service-a",
162-
"pipeline": RawOptions{
163-
"destination": RawOptions{
164-
"namespace": "devstream-io",
165-
"server": "https://kubernetes.default.svc",
166-
},
167-
"app": RawOptions{
168-
"namespace": "argocd",
169-
},
170-
"source": RawOptions{
171-
"valuefile": "values.yaml",
172-
"path": "helm/service-a",
173-
"repoURL": "${{repo-scaffolding.myapp.outputs.repoURL}}",
174-
},
175-
"configLocation": "",
166+
"destination": RawOptions{
167+
"namespace": "devstream-io",
168+
"server": "https://kubernetes.default.svc",
176169
},
177-
"scm": RawOptions{
178-
"url": "https://github.com/devstream-io/service-a",
179-
"apiURL": "gitlab.com/some/path/to/your/api",
180-
"owner": "devstream-io",
181-
"org": "devstream-io",
182-
"name": "service-a",
183-
"scmType": "github",
170+
"app": RawOptions{
171+
"namespace": "argocd",
172+
},
173+
"source": RawOptions{
174+
"valuefile": "values.yaml",
175+
"path": "helm/service-a",
176+
"repoURL": "${{repo-scaffolding.myapp.outputs.repoURL}}",
184177
},
185178
},
186179
}
@@ -210,14 +203,7 @@ var _ = Describe("LoadConfig", func() {
210203
"repo": "dtm-scaffolding-golang",
211204
"branch": "main",
212205
},
213-
"vars": RawOptions{
214-
"foo1": "bar1",
215-
"foo2": "bar2",
216-
"registryType": "dockerhub",
217-
"framework": "django",
218-
"language": "python",
219-
"argocdNamespace": "argocd",
220-
},
206+
"vars": RawOptions{},
221207
},
222208
}
223209

@@ -273,17 +259,20 @@ var _ = Describe("LoadConfig", func() {
273259
var _ = Describe("escapeBrackets", func() {
274260
When("escape brackets", func() {
275261
It("should works right", func() {
276-
testStrBytes1 := "foo: [[ foo ]]\n"
277-
testStr2 := "foo: xx[[ foo ]]\n"
278-
testStr3 := "foo: [[ foo ]]xx\n"
279-
280-
retStr1 := escapeBrackets([]byte(testStrBytes1))
281-
retStr2 := escapeBrackets([]byte(testStr2))
282-
retStr3 := escapeBrackets([]byte(testStr3))
262+
testMap := map[string]string{
263+
"foo: [[ foo ]]": "foo: \"[[ foo ]]\"",
264+
"foo: [[ foo ]] #comment": "foo: \"[[ foo ]]\" #comment",
265+
"foo: xx[[ foo ]]": "foo: xx[[ foo ]]",
266+
"foo: [[ foo ]]xx": "foo: \"[[ foo ]]xx\"",
267+
"foo: [[ foo ]]/[[ poo ]]": "foo: \"[[ foo ]]/[[ poo ]]\"",
268+
`foo: [[ test ]]
269+
poo: [[ gg ]]`: "foo: \"[[ test ]]\"\npoo: \"[[ gg ]]\"",
270+
}
283271

284-
Expect(string(retStr1)).To(Equal("foo: \"[[ foo ]]\"\n"))
285-
Expect(string(retStr2)).To(Equal("foo: xx[[ foo ]]\n"))
286-
Expect(string(retStr3)).To(Equal("foo: \"[[ foo ]]xx\"\n"))
272+
for testStr, expectStr := range testMap {
273+
retStr1 := escapeBrackets([]byte(testStr))
274+
Expect(string(retStr1)).Should(Equal(expectStr))
275+
}
287276
})
288277
})
289278
})
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package configmanager
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/devstream-io/devstream/pkg/util/log"
7+
)
8+
9+
var optionConfiguratorMap = map[string]pipelineOption{
10+
"github-actions": githubGeneral,
11+
"gitlab-ci": gitlabGeneral,
12+
"jenkins-pipeline": jenkinsGeneral,
13+
"argocdapp": argocdApp,
14+
}
15+
16+
type pipelineOptionGenerator func(originOption RawOptions, app *app) RawOptions
17+
18+
type pipelineOption struct {
19+
defaultConfigLocation string
20+
optionGeneratorFunc pipelineOptionGenerator
21+
}
22+
23+
// TODO(steinliber) unify all ci/cd config to same config options
24+
var (
25+
// github actions pipeline options
26+
githubGeneral = pipelineOption{
27+
defaultConfigLocation: "[email protected]:devstream-io/ci-template.git//github-actions",
28+
optionGeneratorFunc: pipelineGeneralGenerator,
29+
}
30+
gitlabGeneral = pipelineOption{
31+
defaultConfigLocation: "https://raw.githubusercontent.com/devstream-io/ci-template/main/gitlab-ci/.gitlab-ci.yml",
32+
optionGeneratorFunc: pipelineGeneralGenerator,
33+
}
34+
jenkinsGeneral = pipelineOption{
35+
defaultConfigLocation: "https://raw.githubusercontent.com/devstream-io/ci-template/main/jenkins-pipeline/general/Jenkinsfile",
36+
optionGeneratorFunc: pipelineGeneralGenerator,
37+
}
38+
argocdApp = pipelineOption{
39+
optionGeneratorFunc: pipelineArgocdAppGenerator,
40+
}
41+
)
42+
43+
// pipelineGeneralGenerator generate pipeline general options from RawOptions
44+
func pipelineGeneralGenerator(options RawOptions, app *app) RawOptions {
45+
if app.Spec != nil {
46+
app.Spec.updatePiplineOption(options)
47+
}
48+
// update image related config
49+
newOption := make(RawOptions)
50+
newOption["pipeline"] = options
51+
newOption["scm"] = RawOptions(app.Repo.Encode())
52+
return newOption
53+
}
54+
55+
// pipelineArgocdAppGenerator generate argocdApp options from RawOptions
56+
func pipelineArgocdAppGenerator(options RawOptions, app *app) RawOptions {
57+
// config app default options
58+
if _, exist := options["app"]; !exist {
59+
options["app"] = RawOptions{
60+
"name": app.Name,
61+
"namespace": "argocd",
62+
}
63+
}
64+
// config destination options
65+
if _, exist := options["destination"]; !exist {
66+
options["destination"] = RawOptions{
67+
"server": "https://kubernetes.default.svc",
68+
"namespace": "default",
69+
}
70+
}
71+
// config source default options
72+
repoInfo, err := app.Repo.BuildRepoInfo()
73+
if err != nil {
74+
log.Errorf("parse argocd repoInfo failed: %+v", err)
75+
return options
76+
}
77+
if source, sourceExist := options["source"]; sourceExist {
78+
sourceMap := source.(RawOptions)
79+
if _, repoURLExist := sourceMap["repoURL"]; !repoURLExist {
80+
sourceMap["repoURL"] = repoInfo.CloneURL
81+
}
82+
options["source"] = sourceMap
83+
} else {
84+
options["source"] = RawOptions{
85+
"valuefile": "values.yaml",
86+
"path": fmt.Sprintf("helm/%s", app.Name),
87+
"repoURL": repoInfo.CloneURL,
88+
}
89+
}
90+
return options
91+
}
92+
93+
// hasDefaultConfig check whether
94+
func (o *pipelineOption) hasDefaultConfig() bool {
95+
return o.defaultConfigLocation != ""
96+
}

0 commit comments

Comments
 (0)