-
Notifications
You must be signed in to change notification settings - Fork 234
/
config.go
229 lines (202 loc) · 7.41 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package cmd
import (
"fmt"
"io/ioutil"
"strings"
"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/CircleCI-Public/circleci-cli/filetree"
"github.com/CircleCI-Public/circleci-cli/local"
"github.com/CircleCI-Public/circleci-cli/pipeline"
"github.com/CircleCI-Public/circleci-cli/proxy"
"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"gopkg.in/yaml.v3"
)
type configOptions struct {
cfg *settings.Config
cl *graphql.Client
args []string
}
// Path to the config.yml file to operate on.
// Used to for compatibility with `circleci config validate --path`
var configPath string
var ignoreDeprecatedImages bool // should we ignore deprecated images warning
var configAnnotations = map[string]string{
"<path>": "The path to your config (use \"-\" for STDIN)",
}
func newConfigCommand(config *settings.Config) *cobra.Command {
opts := configOptions{
cfg: config,
}
configCmd := &cobra.Command{
Use: "config",
Short: "Operate on build config files",
}
packCommand := &cobra.Command{
Use: "pack <path>",
Short: "Pack up your CircleCI configuration into a single file.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
},
RunE: func(_ *cobra.Command, _ []string) error {
return packConfig(opts)
},
Args: cobra.ExactArgs(1),
Annotations: make(map[string]string),
}
packCommand.Annotations["<path>"] = configAnnotations["<path>"]
validateCommand := &cobra.Command{
Use: "validate <path>",
Aliases: []string{"check"},
Short: "Check that the config file is well formed.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
opts.cl = graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return validateConfig(opts, cmd.Flags())
},
Args: cobra.MaximumNArgs(1),
Annotations: make(map[string]string),
}
validateCommand.Annotations["<path>"] = configAnnotations["<path>"]
validateCommand.PersistentFlags().StringVarP(&configPath, "config", "c", ".circleci/config.yml", "path to config file")
validateCommand.PersistentFlags().BoolVar(&ignoreDeprecatedImages, "ignore-deprecated-images", false, "ignores the deprecated images error")
if err := validateCommand.PersistentFlags().MarkHidden("config"); err != nil {
panic(err)
}
validateCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org")
validateCommand.Flags().String("org-id", "", "organization id used when a config depends on private orbs belonging to that org")
processCommand := &cobra.Command{
Use: "process <path>",
Short: "Validate config and display expanded configuration.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
opts.cl = graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return processConfig(opts, cmd.Flags())
},
Args: cobra.ExactArgs(1),
Annotations: make(map[string]string),
}
processCommand.Annotations["<path>"] = configAnnotations["<path>"]
processCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org")
processCommand.Flags().String("org-id", "", "organization id used when a config depends on private orbs belonging to that org")
processCommand.Flags().StringP("pipeline-parameters", "", "", "YAML/JSON map of pipeline parameters, accepts either YAML/JSON directly or file path (for example: my-params.yml)")
migrateCommand := &cobra.Command{
Use: "migrate",
Short: "Migrate a pre-release 2.0 config to the official release version",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
},
RunE: func(_ *cobra.Command, _ []string) error {
return migrateConfig(opts)
},
Hidden: true,
DisableFlagParsing: true,
}
// These flags are for documentation and not actually parsed
migrateCommand.PersistentFlags().StringP("config", "c", ".circleci/config.yml", "path to config file")
migrateCommand.PersistentFlags().BoolP("in-place", "i", false, "whether to update file in place. If false, emits to stdout")
configCmd.AddCommand(packCommand)
configCmd.AddCommand(validateCommand)
configCmd.AddCommand(processCommand)
configCmd.AddCommand(migrateCommand)
return configCmd
}
// The <path> arg is actually optional, in order to support compatibility with the --path flag.
func validateConfig(opts configOptions, flags *pflag.FlagSet) error {
var err error
var response *api.ConfigResponse
path := local.DefaultConfigPath
// First, set the path to configPath set by --path flag for compatibility
if configPath != "" {
path = configPath
}
// Then, if an arg is passed in, choose that instead
if len(opts.args) == 1 {
path = opts.args[0]
}
//if no orgId provided use org slug
orgID, _ := flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
response, err = api.ConfigQuery(opts.cl, path, orgID, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
} else {
orgSlug, _ := flags.GetString("org-slug")
response, err = api.ConfigQueryLegacy(opts.cl, path, orgSlug, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
}
// check if a deprecated Linux VM image is being used
// link here to blog post when available
// returns an error if a deprecated image is used
if !ignoreDeprecatedImages {
err := deprecatedImageCheck(response)
if err != nil {
return err
}
}
if path == "-" {
fmt.Printf("Config input is valid.\n")
} else {
fmt.Printf("Config file at %s is valid.\n", path)
}
return nil
}
func processConfig(opts configOptions, flags *pflag.FlagSet) error {
paramsYaml, _ := flags.GetString("pipeline-parameters")
var response *api.ConfigResponse
var params pipeline.Parameters
var err error
if len(paramsYaml) > 0 {
// The 'src' value can be a filepath, or a yaml string. If the file cannot be read successfully,
// proceed with the assumption that the value is already valid yaml.
raw, err := ioutil.ReadFile(paramsYaml)
if err != nil {
raw = []byte(paramsYaml)
}
err = yaml.Unmarshal(raw, ¶ms)
if err != nil {
return fmt.Errorf("invalid 'pipeline-parameters' provided: %s", err.Error())
}
}
//if no orgId provided use org slug
orgID, _ := flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
response, err = api.ConfigQuery(opts.cl, opts.args[0], orgID, params, pipeline.LocalPipelineValues())
if err != nil {
return err
}
} else {
orgSlug, _ := flags.GetString("org-slug")
response, err = api.ConfigQueryLegacy(opts.cl, opts.args[0], orgSlug, params, pipeline.LocalPipelineValues())
if err != nil {
return err
}
}
fmt.Print(response.OutputYaml)
return nil
}
func packConfig(opts configOptions) error {
tree, err := filetree.NewTree(opts.args[0])
if err != nil {
return errors.Wrap(err, "An error occurred trying to build the tree")
}
y, err := yaml.Marshal(&tree)
if err != nil {
return errors.Wrap(err, "Failed trying to marshal the tree to YAML ")
}
fmt.Printf("%s\n", string(y))
return nil
}
func migrateConfig(opts configOptions) error {
return proxy.Exec([]string{"config", "migrate"}, opts.args)
}