@@ -18,20 +18,25 @@ import (
1818 "context"
1919 "errors"
2020 "fmt"
21+ "io/fs"
22+ "maps"
2123 "os"
24+ "path/filepath"
2225
2326 "github.com/spf13/cobra"
2427 "go.uber.org/zap"
2528 "sigs.k8s.io/yaml"
2629
2730 "github.com/pipe-cd/pipecd/pkg/cli"
2831 "github.com/pipe-cd/pipecd/pkg/config"
32+ "github.com/pipe-cd/pipecd/pkg/model"
2933)
3034
3135type applicationConfig struct {
3236 root * command
3337
3438 configFiles []string
39+ directories []string
3540}
3641
3742func newApplicationConfigCommand (root * command ) * cobra.Command {
@@ -46,11 +51,15 @@ func newApplicationConfigCommand(root *command) *cobra.Command {
4651 }
4752
4853 cmd .Flags ().StringSliceVar (& c .configFiles , "config-files" , c .configFiles , "The list of application config files to migrate." )
49- cmd .MarkFlagRequired ("config-files" )
54+ cmd .Flags ().StringSliceVar (& c .directories , "dirs" , c .directories , "The list of application config directories to migrate." )
55+
56+ cmd .MarkFlagsOneRequired ("config-files" , "dirs" )
57+ cmd .MarkFlagsMutuallyExclusive ("config-files" , "dirs" )
5058 return cmd
5159}
5260
5361func (c * applicationConfig ) run (ctx context.Context , input cli.Input ) error {
62+
5463 for _ , configFile := range c .configFiles {
5564 input .Logger .Info ("migrating application config" , zap .String ("config-file" , configFile ))
5665 if err := c .migrateApplicationConfig (ctx , configFile , input .Logger ); err != nil {
@@ -60,6 +69,37 @@ func (c *applicationConfig) run(ctx context.Context, input cli.Input) error {
6069 input .Logger .Info ("successfully migrated application config" , zap .String ("config-file" , configFile ))
6170 }
6271
72+ for _ , directory := range c .directories {
73+ input .Logger .Info ("migrating application configs in directory" , zap .String ("directory" , directory ))
74+
75+ fileSystem := os .DirFS (directory )
76+ // Scan all files under the repository.
77+ err := fs .WalkDir (fileSystem , "." , func (path string , d fs.DirEntry , err error ) error {
78+ if err != nil {
79+ return err
80+ }
81+ if d .IsDir () {
82+ return nil
83+ }
84+ if ! model .IsApplicationConfigFile (d .Name ()) {
85+ return nil
86+ }
87+
88+ input .Logger .Info ("migrating application config" , zap .String ("config-file" , path ))
89+ if err := c .migrateApplicationConfig (ctx , filepath .Join (directory , path ), input .Logger ); err != nil {
90+ input .Logger .Error ("failed to migrate application config" , zap .String ("config-file" , path ), zap .Error (err ))
91+ // Continue to migrate other application configs.
92+ return nil
93+ }
94+ input .Logger .Info ("successfully migrated application config" , zap .String ("config-file" , path ))
95+ return nil
96+ })
97+ if err != nil {
98+ input .Logger .Error ("failed to migrate application configs in directory" , zap .String ("directory" , directory ), zap .Error (err ))
99+ return err
100+ }
101+ input .Logger .Info ("successfully migrated application configs in directory" , zap .String ("directory" , directory ))
102+ }
63103 return nil
64104}
65105
@@ -89,7 +129,6 @@ func (c *applicationConfig) migrateApplicationConfig(_ context.Context, configFi
89129 "description" ,
90130 "planner" ,
91131 "commitMatcher" ,
92- "pipeline" ,
93132 "trigger" ,
94133 "postSync" ,
95134 "timeout" ,
@@ -107,6 +146,37 @@ func (c *applicationConfig) migrateApplicationConfig(_ context.Context, configFi
107146 }
108147 }
109148
149+ var hasAnalysisStage bool
150+
151+ if oldPipelineCfg , ok := oldSpec ["pipeline" ]; ok {
152+ pipelineCfg := make (map [string ][]any )
153+
154+ for _ , oldStage := range oldPipelineCfg .(map [string ]any )["stages" ].([]any ) {
155+ if oldStageCfg , ok := oldStage .(map [string ]any ); ok {
156+ // Check if the stage is the analysis stage to determine if we need to fill plugins.analysis config
157+ if oldStageCfg ["name" ] == string (model .StageAnalysis ) {
158+ hasAnalysisStage = true
159+ }
160+
161+ // Copy STAGE `timeout` and `skipOn` config under pipeline.stages[].with to pipeline.stages[]
162+ // NOTE: We keep the original `timeout` and `skipOn` config under pipeline.stages[].with. for backward compatibility,
163+ // in case user want to downgrade pipedv1 to pipedv0.
164+ // `pipeline.stages[].with.{timeout, skipOn}` will be marked as deprecated in v1.
165+ stageCfg := maps .Clone (oldStageCfg )
166+ if withCfg , ok := stageCfg ["with" ].(map [string ]any ); ok {
167+ if _ , ok := withCfg ["timeout" ]; ok {
168+ stageCfg ["timeout" ] = withCfg ["timeout" ]
169+ }
170+ if _ , ok := withCfg ["skipOn" ]; ok {
171+ stageCfg ["skipOn" ] = withCfg ["skipOn" ]
172+ }
173+ }
174+ pipelineCfg ["stages" ] = append (pipelineCfg ["stages" ], stageCfg )
175+ }
176+ }
177+ spec ["pipeline" ] = pipelineCfg
178+ }
179+
110180 switch config .Kind (cfg ["kind" ].(string )) {
111181 case config .KindKubernetesApp :
112182 logger .Info ("migrating kubernetes application config" , zap .String ("config-file" , configFile ))
@@ -126,6 +196,24 @@ func (c *applicationConfig) migrateApplicationConfig(_ context.Context, configFi
126196 pluginCfg ["kubernetes" ][key ] = oldSpec [key ]
127197 }
128198 }
199+
200+ // Add analysis stage configuration if it exists
201+ // namespace value will be set using spec.input.namespace
202+ if hasAnalysisStage {
203+ namespace := "default"
204+ if input , ok := oldSpec ["input" ].(map [string ]any ); ok {
205+ if ns , ok := input ["namespace" ].(string ); ok && ns != "" {
206+ namespace = ns
207+ }
208+ }
209+
210+ pluginCfg ["analysis" ] = map [string ]any {
211+ "appCustomArgs" : map [string ]string {
212+ "k8sNamespace" : namespace ,
213+ },
214+ }
215+ }
216+
129217 spec ["plugins" ] = pluginCfg
130218 case config .KindTerraformApp :
131219 logger .Info ("migrating terraform application config" , zap .String ("config-file" , configFile ))
0 commit comments