diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 8e56d2b7232f0..ebfd8afddab52 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -29,6 +29,7 @@ import ( argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/server/application" "github.com/argoproj/argo-cd/util" + "github.com/argoproj/argo-cd/util/argo" "github.com/argoproj/argo-cd/util/cli" "github.com/argoproj/argo-cd/util/diff" "github.com/argoproj/argo-cd/util/ksonnet" @@ -312,17 +313,33 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com os.Exit(1) } setParameterOverrides(app, appOpts.parameters) - _, err = appIf.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{ + oldOverrides := app.Spec.Source.ComponentParameterOverrides + updatedSpec, err := appIf.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{ Name: &app.Name, Spec: app.Spec, }) errors.CheckError(err) + + newOverrides := updatedSpec.Source.ComponentParameterOverrides + checkDroppedParams(newOverrides, oldOverrides) }, } addAppFlags(command, &appOpts) return command } +func checkDroppedParams(newOverrides []argoappv1.ComponentParameter, oldOverrides []argoappv1.ComponentParameter) { + newOverrideMap := argo.ParamToMap(newOverrides) + + if len(oldOverrides) > len(newOverrides) { + for _, oldOverride := range oldOverrides { + if !argo.CheckValidParam(newOverrideMap, oldOverride) { + log.Warnf("Parameter %s in %s does not exist in ksonnet, parameter override dropped", oldOverride.Name, oldOverride.Component) + } + } + } +} + type appOptions struct { repoURL string appPath string diff --git a/server/application/application.go b/server/application/application.go index e33af277b6b16..53364586b9fd2 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/argoproj/argo-cd/util/argo" + "github.com/ghodss/yaml" "github.com/ksonnet/ksonnet/pkg/app" log "github.com/sirupsen/logrus" @@ -250,7 +252,47 @@ func (s *Server) Update(ctx context.Context, q *ApplicationUpdateRequest) (*appv return s.appclientset.ArgoprojV1alpha1().Applications(s.ns).Update(a) } -// UpdateSpec updates an application spec +// removeInvalidOverrides removes any prameter overrides that are no longer valid +// drops old overrides that are invalid +// throws an error is passed override is invalid +// if passed override and old overrides are invalid, throws error, old overrides not dropped +func (s *Server) removeInvalidOverrides(a *appv1.Application, q *ApplicationUpdateSpecRequest) (*ApplicationUpdateSpecRequest, error) { + validAppSet := argo.ParamToMap(a.Status.Parameters) + invalidOldParams := make(map[string]map[string]bool) + var overrideParams []appv1.ComponentParameter + for _, unvettedExistingParam := range a.Spec.Source.ComponentParameterOverrides { + if argo.CheckValidParam(validAppSet, unvettedExistingParam) { + overrideParams = append(overrideParams, unvettedExistingParam) + } else { + if invalidOldParams[unvettedExistingParam.Component] == nil { + invalidOldParams[unvettedExistingParam.Component] = make(map[string]bool) + } + invalidOldParams[unvettedExistingParam.Component][unvettedExistingParam.Name] = true + } + } + for _, unvettedRequestedParam := range q.Spec.Source.ComponentParameterOverrides { + if argo.CheckValidParam(validAppSet, unvettedRequestedParam) { + paramExists := false + for _, overrideParam := range overrideParams { + if unvettedRequestedParam.Component == overrideParam.Component && unvettedRequestedParam.Name == overrideParam.Name && unvettedRequestedParam.Value == overrideParam.Value { + paramExists = true + } + } + if !paramExists { + overrideParams = append(overrideParams, unvettedRequestedParam) + } + } else { + if !argo.CheckValidParam(invalidOldParams, unvettedRequestedParam) { + return nil, status.Errorf(codes.InvalidArgument, "Parameter '%s' in '%s' does not exist in ksonnet", unvettedRequestedParam.Name, unvettedRequestedParam.Component) + + } + } + } + q.Spec.Source.ComponentParameterOverrides = overrideParams + return q, nil +} + +// UpdateSpec updates an application spec and filters out any invalid parameter overrides func (s *Server) UpdateSpec(ctx context.Context, q *ApplicationUpdateSpecRequest) (*appv1.ApplicationSpec, error) { if !q.Spec.BelongsToDefaultProject() { @@ -269,6 +311,10 @@ func (s *Server) UpdateSpec(ctx context.Context, q *ApplicationUpdateSpecRequest if err != nil { return nil, err } + q, err = s.removeInvalidOverrides(a, q) + if err != nil { + return nil, err + } patch, err := json.Marshal(map[string]appv1.ApplicationSpec{ "spec": q.Spec, }) diff --git a/util/argo/argo.go b/util/argo/argo.go index aaa3059349631..e9452a3585466 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -36,6 +36,29 @@ func FilterByProjects(apps []argoappv1.Application, projects []string) []argoapp } } return items + +} + +//ParamToMap converts a ComponentParameter list to a map for easy filtering +func ParamToMap(params []argoappv1.ComponentParameter) map[string]map[string]bool { + validAppSet := make(map[string]map[string]bool) + for _, p := range params { + if validAppSet[p.Component] == nil { + validAppSet[p.Component] = make(map[string]bool) + } + validAppSet[p.Component][p.Name] = true + } + return validAppSet +} + +// CheckValidParam checks if the parameter passed is overridable for the given appMap +func CheckValidParam(appMap map[string]map[string]bool, newParam argoappv1.ComponentParameter) bool { + if val, ok := appMap[newParam.Component]; ok { + if _, ok2 := val[newParam.Name]; ok2 { + return true + } + } + return false } // RefreshApp updates the refresh annotation of an application to coerce the controller to process it diff --git a/util/argo/argo_test.go b/util/argo/argo_test.go index a07de8dc3e26e..eed572955c988 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -29,6 +29,24 @@ func TestRefreshApp(t *testing.T) { //assert.True(t, ok) } +func TestCheckValidParam(t *testing.T) { + oldAppSet := make(map[string]map[string]bool) + oldAppSet["testComponent"] = make(map[string]bool) + oldAppSet["testComponent"]["overrideParam"] = true + newParam := argoappv1.ComponentParameter{ + Component: "testComponent", + Name: "overrideParam", + Value: "new-value", + } + badParam := argoappv1.ComponentParameter{ + Component: "testComponent", + Name: "badParam", + Value: "new-value", + } + assert.True(t, CheckValidParam(oldAppSet, newParam)) + assert.False(t, CheckValidParam(oldAppSet, badParam)) +} + func TestWaitForRefresh(t *testing.T) { appClientset := appclientset.NewSimpleClientset()