Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions pkg/app/pipectl/cmd/migrate/application_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,12 @@ func (c *applicationConfig) migrateApplicationConfig(_ context.Context, configFi
"driftDetection",
}

oldSpec := cfg["spec"].(map[string]any)
oldSpec, ok := cfg["spec"].(map[string]any)
if !ok {
err := fmt.Errorf("spec must be a mapping, got %T", cfg["spec"])
logger.Error("invalid application config", zap.String("config-file", configFile), zap.Error(err))
return err
}
for _, key := range keys {
if _, ok := oldSpec[key]; ok {
spec[key] = oldSpec[key]
Expand All @@ -151,7 +156,19 @@ func (c *applicationConfig) migrateApplicationConfig(_ context.Context, configFi
if oldPipelineCfg, ok := oldSpec["pipeline"]; ok {
pipelineCfg := make(map[string][]any)

for _, oldStage := range oldPipelineCfg.(map[string]any)["stages"].([]any) {
oldPipeline, ok := oldPipelineCfg.(map[string]any)
if !ok {
err := fmt.Errorf("spec.pipeline must be a mapping, got %T", oldPipelineCfg)
logger.Error("invalid application config", zap.String("config-file", configFile), zap.Error(err))
return err
}
oldStages, ok := oldPipeline["stages"].([]any)
if !ok && oldPipeline["stages"] != nil {
err := fmt.Errorf("spec.pipeline.stages must be a list, got %T", oldPipeline["stages"])
logger.Error("invalid application config", zap.String("config-file", configFile), zap.Error(err))
return err
}
for _, oldStage := range oldStages {
if oldStageCfg, ok := oldStage.(map[string]any); ok {
// Check if the stage is the analysis stage to determine if we need to fill plugins.analysis config
if oldStageCfg["name"] == string(model.StageAnalysis) {
Expand All @@ -177,7 +194,13 @@ func (c *applicationConfig) migrateApplicationConfig(_ context.Context, configFi
spec["pipeline"] = pipelineCfg
}

switch config.Kind(cfg["kind"].(string)) {
kind, ok := cfg["kind"].(string)
if !ok {
err := fmt.Errorf("kind must be a string, got %T", cfg["kind"])
logger.Error("invalid application config", zap.String("config-file", configFile), zap.Error(err))
return err
}
switch config.Kind(kind) {
case config.KindKubernetesApp:
logger.Info("migrating kubernetes application config", zap.String("config-file", configFile))
keys := []string{
Expand Down Expand Up @@ -272,8 +295,8 @@ func (c *applicationConfig) migrateApplicationConfig(_ context.Context, configFi
}
spec["plugins"] = pluginCfg
default:
logger.Error("unsupported application kind", zap.String("config-file", configFile), zap.String("kind", cfg["kind"].(string)))
return fmt.Errorf("unsupported application kind: %s", cfg["kind"])
logger.Error("unsupported application kind", zap.String("config-file", configFile), zap.String("kind", kind))
return fmt.Errorf("unsupported application kind: %s", kind)
}

migrated["spec"] = spec
Expand Down
56 changes: 56 additions & 0 deletions pkg/app/pipectl/cmd/migrate/malformed_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 The PipeCD Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package migrate

import (
"context"
"os"
"path/filepath"
"testing"

"go.uber.org/zap"
)

// migrateApplicationConfig reads a user-supplied YAML file into map[string]any,
// so every lookup out of it can hold something other than the expected shape.
// A malformed file has to be reported, not panic the CLI.
func TestMigrateApplicationConfigMalformed(t *testing.T) {
cases := map[string]string{
"spec is a string": "apiVersion: pipecd.dev/v1beta1\nkind: KubernetesApp\nspec: oops\n",
"spec missing": "apiVersion: pipecd.dev/v1beta1\nkind: KubernetesApp\n",
"kind missing": "apiVersion: pipecd.dev/v1beta1\nspec:\n name: x\n",
"kind is a number": "apiVersion: pipecd.dev/v1beta1\nkind: 42\nspec:\n name: x\n",
"pipeline is a list": "apiVersion: pipecd.dev/v1beta1\nkind: KubernetesApp\nspec:\n pipeline:\n - a\n",
"stages is a string": "apiVersion: pipecd.dev/v1beta1\nkind: KubernetesApp\nspec:\n pipeline:\n stages: nope\n",
}
for name, body := range cases {
t.Run(name, func(t *testing.T) {
dir := t.TempDir()
f := filepath.Join(dir, "app.pipecd.yaml")
if err := os.WriteFile(f, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
c := &applicationConfig{}
defer func() {
if r := recover(); r != nil {
t.Fatalf("panicked on a user-supplied config: %v", r)
}
}()
if err := c.migrateApplicationConfig(context.Background(), f, zap.NewNop()); err == nil {
t.Error("expected an error for a malformed config, got nil")
}
})
}
}