diff --git a/controller/appcontroller_test.go b/controller/appcontroller_test.go index 6290c5281e4c5..b86b020120052 100644 --- a/controller/appcontroller_test.go +++ b/controller/appcontroller_test.go @@ -245,6 +245,7 @@ func newFakeControllerWithResync(ctx context.Context, data *fakeData, appResyncP clusterCacheMock := &mocks.ClusterCache{} clusterCacheMock.EXPECT().IsNamespaced(mock.Anything).Return(true, nil) clusterCacheMock.EXPECT().GetGVKParser().Return(nil) + clusterCacheMock.EXPECT().GetOpenAPISchema().Return(nil).Maybe() mockStateCache := &mockstatecache.LiveStateCache{} ctrl.appStateManager.(*appStateManager).liveStateCache = mockStateCache @@ -3188,6 +3189,35 @@ func TestProcessRequestedAppOperation_Successful(t *testing.T) { assert.Equal(t, CompareWithLatestForceResolve, level) } +func TestProcessRequestedAppOperation_Successful_RespectIgnoreDifferences(t *testing.T) { + app := newFakeApp() + app.Spec.Project = "default" + app.Operation = &v1alpha1.Operation{ + Sync: &v1alpha1.SyncOperation{ + SyncOptions: v1alpha1.SyncOptions{"RespectIgnoreDifferences=true"}, + }, + } + ctrl := newFakeController(t.Context(), &fakeData{ + apps: []runtime.Object{app, &defaultProj}, + manifestResponses: []*apiclient.ManifestResponse{{ + Manifests: []string{}, + }}, + }, nil) + fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset) + receivedPatch := map[string]any{} + fakeAppCs.PrependReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + if patchAction, ok := action.(kubetesting.PatchAction); ok { + require.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch)) + } + return true, &v1alpha1.Application{}, nil + }) + + ctrl.processRequestedAppOperation(app) + + phase, _, _ := unstructured.NestedString(receivedPatch, "status", "operationState", "phase") + assert.Equal(t, string(synccommon.OperationSucceeded), phase) +} + func TestProcessRequestedAppAutomatedOperation_Successful(t *testing.T) { app := newFakeApp() app.Spec.Project = "default" @@ -4091,3 +4121,17 @@ func TestPersistAppStatus_AnnotationManagement(t *testing.T) { assert.Equal(t, "other-value", otherValue) }) } + +func TestGetOpenAPISchema_ClusterCacheError(t *testing.T) { + mockStateCache := &mockstatecache.LiveStateCache{} + mockStateCache.EXPECT().GetClusterCache(mock.Anything).Return(nil, errors.New("cluster not found")) + + m := &appStateManager{ + liveStateCache: mockStateCache, + } + + cluster := &v1alpha1.Cluster{Server: "https://localhost:6443"} + _, err := m.getOpenAPISchema(cluster) + require.Error(t, err) + assert.Contains(t, err.Error(), "cluster not found") +} diff --git a/controller/sync.go b/controller/sync.go index d8c606fc66f90..7b2941aa95564 100644 --- a/controller/sync.go +++ b/controller/sync.go @@ -2,6 +2,7 @@ package controller import ( "context" + "encoding/json" stderrors "errors" "fmt" "os" @@ -9,6 +10,7 @@ import ( "time" "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/kubectl/pkg/util/openapi" cdcommon "github.com/argoproj/argo-cd/v3/common" @@ -43,6 +45,14 @@ const ( EnvVarSyncWaveDelay = "ARGOCD_SYNC_WAVE_DELAY" ) +func (m *appStateManager) getOpenAPISchema(server *v1alpha1.Cluster) (openapi.Resources, error) { + cluster, err := m.liveStateCache.GetClusterCache(server) + if err != nil { + return nil, err + } + return cluster.GetOpenAPISchema(), nil +} + func (m *appStateManager) getGVKParser(server *v1alpha1.Cluster) (*managedfields.GvkParser, error) { cluster, err := m.liveStateCache.GetClusterCache(server) if err != nil { @@ -236,7 +246,13 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, project *v1alp // resources which in this case applies the live values in the configured // ignore differences fields. if syncOp.SyncOptions.HasOption("RespectIgnoreDifferences=true") { - patchedTargets, err := normalizeTargetResources(compareResult) + openAPISchema, err := m.getOpenAPISchema(destCluster) + if err != nil { + state.Phase = common.OperationError + state.Message = fmt.Sprintf("failed to load openAPISchema: %v", err) + return + } + patchedTargets, err := normalizeTargetResources(openAPISchema, compareResult) if err != nil { state.Phase = common.OperationError state.Message = fmt.Sprintf("Failed to normalize target resources: %s", err) @@ -396,33 +412,42 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, project *v1alp // - applies normalization to the target resources based on the live resources // - copies ignored fields from the matching live resources: apply normalizer to the live resource, // calculates the patch performed by normalizer and applies the patch to the target resource -func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructured, error) { - // normalize live and target resources +func normalizeTargetResources(openAPISchema openapi.Resources, cr *comparisonResult) ([]*unstructured.Unstructured, error) { + // Normalize live and target resources (cleaning or aligning them) normalized, err := diff.Normalize(cr.reconciliationResult.Live, cr.reconciliationResult.Target, cr.diffConfig) if err != nil { return nil, err } + patchedTargets := []*unstructured.Unstructured{} + for idx, live := range cr.reconciliationResult.Live { normalizedTarget := normalized.Targets[idx] if normalizedTarget == nil { patchedTargets = append(patchedTargets, nil) continue } + gvk := normalizedTarget.GroupVersionKind() + originalTarget := cr.reconciliationResult.Target[idx] if live == nil { + // No live resource, just use target patchedTargets = append(patchedTargets, originalTarget) continue } - var lookupPatchMeta *strategicpatch.PatchMetaFromStruct - versionedObject, err := scheme.Scheme.New(normalizedTarget.GroupVersionKind()) - if err == nil { - meta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject) - if err != nil { + var ( + lookupPatchMeta strategicpatch.LookupPatchMeta + versionedObject any + ) + + // Load patch meta struct or OpenAPI schema for CRDs + if versionedObject, err = scheme.Scheme.New(gvk); err == nil { + if lookupPatchMeta, err = strategicpatch.NewPatchMetaFromStruct(versionedObject); err != nil { return nil, err } - lookupPatchMeta = &meta + } else if crdSchema := openAPISchema.LookupResource(gvk); crdSchema != nil { + lookupPatchMeta = strategicpatch.NewPatchMetaFromOpenAPI(crdSchema) } // RespectIgnoreDifferences preserves ignored fields by copying their live @@ -447,19 +472,21 @@ func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructure return nil, err } - normalizedTarget, err = applyMergePatch(normalizedTarget, livePatch, versionedObject) + // Apply the patch to the normalized target + // This ensures ignored fields in live are restored into the target before syncing + normalizedTarget, err = applyMergePatch(normalizedTarget, livePatch, versionedObject, lookupPatchMeta) if err != nil { return nil, err } - patchedTargets = append(patchedTargets, normalizedTarget) } + return patchedTargets, nil } // getMergePatch calculates and returns the patch between the original and the // modified unstructures. -func getMergePatch(original, modified *unstructured.Unstructured, lookupPatchMeta *strategicpatch.PatchMetaFromStruct) ([]byte, error) { +func getMergePatch(original, modified *unstructured.Unstructured, lookupPatchMeta strategicpatch.LookupPatchMeta) ([]byte, error) { originalJSON, err := original.MarshalJSON() if err != nil { return nil, err @@ -475,18 +502,35 @@ func getMergePatch(original, modified *unstructured.Unstructured, lookupPatchMet return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON) } -// applyMergePatch will apply the given patch in the obj and return the patched -// unstructure. -func applyMergePatch(obj *unstructured.Unstructured, patch []byte, versionedObject any) (*unstructured.Unstructured, error) { +// applyMergePatch will apply the given patch in the obj and return the patched unstructure. +func applyMergePatch(obj *unstructured.Unstructured, patch []byte, versionedObject any, meta strategicpatch.LookupPatchMeta) (*unstructured.Unstructured, error) { originalJSON, err := obj.MarshalJSON() if err != nil { return nil, err } var patchedJSON []byte - if versionedObject == nil { - patchedJSON, err = jsonpatch.MergePatch(originalJSON, patch) - } else { + switch { + case versionedObject != nil: patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patch, versionedObject) + case meta != nil: + var originalMap, patchMap map[string]any + if err := json.Unmarshal(originalJSON, &originalMap); err != nil { + return nil, err + } + if err := json.Unmarshal(patch, &patchMap); err != nil { + return nil, err + } + + patchedMap, err := strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, meta) + if err != nil { + return nil, err + } + patchedJSON, err = json.Marshal(patchedMap) + if err != nil { + return nil, err + } + default: + patchedJSON, err = jsonpatch.MergePatch(originalJSON, patch) } if err != nil { return nil, err diff --git a/controller/sync_test.go b/controller/sync_test.go index f9c3529ca4989..829c361d1a164 100644 --- a/controller/sync_test.go +++ b/controller/sync_test.go @@ -1,9 +1,16 @@ package controller import ( + "fmt" + "os" "strconv" "testing" + openapi_v2 "github.com/google/gnostic-models/openapiv2" + "k8s.io/kubectl/pkg/util/openapi" + + "sigs.k8s.io/yaml" + "github.com/argoproj/argo-cd/gitops-engine/pkg/sync" synccommon "github.com/argoproj/argo-cd/gitops-engine/pkg/sync/common" "github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube" @@ -25,6 +32,29 @@ import ( "github.com/argoproj/argo-cd/v3/util/settings" ) +type fakeDiscovery struct { + schema *openapi_v2.Document +} + +func (f *fakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) { + return f.schema, nil +} + +func loadCRDSchema(t *testing.T, path string) *openapi_v2.Document { + t.Helper() + + data, err := os.ReadFile(path) + require.NoError(t, err) + + jsonData, err := yaml.YAMLToJSON(data) + require.NoError(t, err) + + doc, err := openapi_v2.ParseDocument(jsonData) + require.NoError(t, err) + + return doc +} + func TestPersistRevisionHistory(t *testing.T) { app := newFakeApp() app.Status.OperationState = nil @@ -390,7 +420,7 @@ func TestNormalizeTargetResources(t *testing.T) { f := setup(t, ignores) // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then require.NoError(t, err) @@ -413,7 +443,7 @@ func TestNormalizeTargetResources(t *testing.T) { live.Object, "Running", "status", "operationState", "phase")) // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then: live status must not be merged into the target that gets applied, // otherwise the SSA sync manager co-owns and freezes operationState.phase. @@ -428,7 +458,7 @@ func TestNormalizeTargetResources(t *testing.T) { f := setup(t, []v1alpha1.ResourceIgnoreDifferences{}) // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then require.NoError(t, err) @@ -448,7 +478,7 @@ func TestNormalizeTargetResources(t *testing.T) { unstructured.RemoveNestedField(live.Object, "metadata", "annotations", "iksm-version") // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then require.NoError(t, err) @@ -473,7 +503,7 @@ func TestNormalizeTargetResources(t *testing.T) { f := setup(t, ignores) // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then require.NoError(t, err) @@ -488,7 +518,6 @@ func TestNormalizeTargetResources(t *testing.T) { assert.Equal(t, int64(4), replicas) }) t.Run("will keep new array entries not found in live state if not ignored", func(t *testing.T) { - t.Skip("limitation in the current implementation") // given ignores := []v1alpha1.ResourceIgnoreDifferences{ { @@ -502,7 +531,7 @@ func TestNormalizeTargetResources(t *testing.T) { f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target} // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then require.NoError(t, err) @@ -539,6 +568,11 @@ func TestNormalizeTargetResourcesWithList(t *testing.T) { } t.Run("will properly ignore nested fields within arrays", func(t *testing.T) { + doc := loadCRDSchema(t, "testdata/schemas/httpproxy_openapi_v2.yaml") + disco := &fakeDiscovery{schema: doc} + oapiGetter := openapi.NewOpenAPIGetter(disco) + oapiResources, err := openapi.NewOpenAPIParser(oapiGetter).Parse() + require.NoError(t, err) // given ignores := []v1alpha1.ResourceIgnoreDifferences{ { @@ -552,8 +586,11 @@ func TestNormalizeTargetResourcesWithList(t *testing.T) { target := test.YamlToUnstructured(testdata.TargetHTTPProxy) f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target} + gvk := schema.GroupVersionKind{Group: "projectcontour.io", Version: "v1", Kind: "HTTPProxy"} + fmt.Printf("LookupResource result: %+v\n", oapiResources.LookupResource(gvk)) + // when - patchedTargets, err := normalizeTargetResources(f.comparisonResult) + patchedTargets, err := normalizeTargetResources(oapiResources, f.comparisonResult) // then require.NoError(t, err) @@ -592,7 +629,7 @@ func TestNormalizeTargetResourcesWithList(t *testing.T) { f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target} // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then require.NoError(t, err) @@ -644,7 +681,7 @@ func TestNormalizeTargetResourcesWithList(t *testing.T) { f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target} // when - targets, err := normalizeTargetResources(f.comparisonResult) + targets, err := normalizeTargetResources(nil, f.comparisonResult) // then require.NoError(t, err) @@ -698,6 +735,175 @@ func TestNormalizeTargetResourcesWithList(t *testing.T) { assert.Equal(t, "EV", env0["name"]) assert.Equal(t, "here", env0["value"]) }) + + t.Run("patches ignored differences in individual array elements of HTTPProxy CRD", func(t *testing.T) { + doc := loadCRDSchema(t, "testdata/schemas/httpproxy_openapi_v2.yaml") + disco := &fakeDiscovery{schema: doc} + oapiGetter := openapi.NewOpenAPIGetter(disco) + oapiResources, err := openapi.NewOpenAPIParser(oapiGetter).Parse() + require.NoError(t, err) + + ignores := []v1alpha1.ResourceIgnoreDifferences{ + { + Group: "projectcontour.io", + Kind: "HTTPProxy", + JQPathExpressions: []string{".spec.routes[].rateLimitPolicy.global.descriptors[].entries[]"}, + }, + } + + f := setupHTTPProxy(t, ignores) + + target := test.YamlToUnstructured(testdata.TargetHTTPProxy) + f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target} + + live := test.YamlToUnstructured(testdata.LiveHTTPProxy) + f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live} + + patchedTargets, err := normalizeTargetResources(oapiResources, f.comparisonResult) + require.NoError(t, err) + require.Len(t, patchedTargets, 1) + patched := patchedTargets[0] + + // verify descriptors array in patched target + descriptors := dig(patched.Object, "spec", "routes", 0, "rateLimitPolicy", "global", "descriptors").([]any) + require.Len(t, descriptors, 1) // Only the descriptors with ignored entries should remain + + // verify individual entries array inside the descriptor + entriesArr := dig(patched.Object, "spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries").([]any) + require.Len(t, entriesArr, 1) // Only the ignored entry should be patched + + // verify the content of the entry is preserved correctly + entry := entriesArr[0].(map[string]any) + requestHeader := entry["requestHeader"].(map[string]any) + assert.Equal(t, "sample-header", requestHeader["headerName"]) + assert.Equal(t, "sample-key", requestHeader["descriptorKey"]) + }) +} + +func TestNormalizeTargetResourcesCRDs(t *testing.T) { + type fixture struct { + comparisonResult *comparisonResult + } + setupHTTPProxy := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture { + t.Helper() + dc, err := diff.NewDiffConfigBuilder(). + WithDiffSettings(ignores, nil, true, normalizers.IgnoreNormalizerOpts{}). + WithNoCache(). + Build() + require.NoError(t, err) + live := test.YamlToUnstructured(testdata.SimpleAppLiveYaml) + target := test.YamlToUnstructured(testdata.SimpleAppTargetYaml) + return &fixture{ + &comparisonResult{ + reconciliationResult: sync.ReconciliationResult{ + Live: []*unstructured.Unstructured{live}, + Target: []*unstructured.Unstructured{target}, + }, + diffConfig: dc, + }, + } + } + + t.Run("sample-app", func(t *testing.T) { + doc := loadCRDSchema(t, "testdata/schemas/simple-app.yaml") + disco := &fakeDiscovery{schema: doc} + oapiGetter := openapi.NewOpenAPIGetter(disco) + oapiResources, err := openapi.NewOpenAPIParser(oapiGetter).Parse() + require.NoError(t, err) + + ignores := []v1alpha1.ResourceIgnoreDifferences{ + { + Group: "example.com", + Kind: "SimpleApp", + JQPathExpressions: []string{".spec.servers[1].enabled", ".spec.servers[0].port"}, + }, + } + + f := setupHTTPProxy(t, ignores) + + target := test.YamlToUnstructured(testdata.SimpleAppTargetYaml) + f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target} + + live := test.YamlToUnstructured(testdata.SimpleAppLiveYaml) + f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live} + + patchedTargets, err := normalizeTargetResources(oapiResources, f.comparisonResult) + require.NoError(t, err) + require.Len(t, patchedTargets, 1) + + patched := patchedTargets[0] + require.NotNil(t, patched) + + // 'spec.servers' array has length 2 + servers := dig(patched.Object, "spec", "servers").([]any) + require.Len(t, servers, 2) + + // first server's 'name' is 'server1' + name1 := dig(patched.Object, "spec", "servers", 0, "name").(string) + assert.Equal(t, "server1", name1) + + assert.Equal(t, int64(8081), dig(patched.Object, "spec", "servers", 0, "port").(int64)) + assert.Equal(t, int64(9090), dig(patched.Object, "spec", "servers", 1, "port").(int64)) + + // first server's 'enabled' should be true + enabled1 := dig(patched.Object, "spec", "servers", 0, "enabled").(bool) + assert.True(t, enabled1) + + // second server's 'name' should be 'server2' + name2 := dig(patched.Object, "spec", "servers", 1, "name").(string) + assert.Equal(t, "server2", name2) + + // second server's 'enabled' should be true (respected from live due to ignoreDifferences) + enabled2 := dig(patched.Object, "spec", "servers", 1, "enabled").(bool) + assert.True(t, enabled2) + }) + t.Run("rollout-obj", func(t *testing.T) { + // Load Rollout CRD schema like SimpleApp + doc := loadCRDSchema(t, "testdata/schemas/rollout-schema.yaml") + disco := &fakeDiscovery{schema: doc} + oapiGetter := openapi.NewOpenAPIGetter(disco) + oapiResources, err := openapi.NewOpenAPIParser(oapiGetter).Parse() + require.NoError(t, err) + + ignores := []v1alpha1.ResourceIgnoreDifferences{ + { + Group: "argoproj.io", + Kind: "Rollout", + JQPathExpressions: []string{`.spec.template.spec.containers[] | select(.name == "init") | .image`}, + }, + } + + f := setupHTTPProxy(t, ignores) + + live := test.YamlToUnstructured(testdata.LiveRolloutYaml) + target := test.YamlToUnstructured(testdata.TargetRolloutYaml) + f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live} + f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target} + + targets, err := normalizeTargetResources(oapiResources, f.comparisonResult) + require.NoError(t, err) + require.Len(t, targets, 1) + + patched := targets[0] + require.NotNil(t, patched) + + containers := dig(patched.Object, "spec", "template", "spec", "containers").([]any) + require.Len(t, containers, 2) + + initContainer := containers[0].(map[string]any) + mainContainer := containers[1].(map[string]any) + + // Assert init container image is preserved (ignoreDifferences works) + initImage := dig(initContainer, "image").(string) + assert.Equal(t, "init-container:v1", initImage) + + // Assert main container fields as expected + mainName := dig(mainContainer, "name").(string) + assert.Equal(t, "main", mainName) + + mainImage := dig(mainContainer, "image").(string) + assert.Equal(t, "main-container:v1", mainImage) + }) } func TestDeriveServiceAccountMatchingNamespaces(t *testing.T) { diff --git a/controller/testdata/data.go b/controller/testdata/data.go index 6bb0d5ed320b4..c71c2018fe729 100644 --- a/controller/testdata/data.go +++ b/controller/testdata/data.go @@ -32,4 +32,16 @@ var ( //go:embed additional-image-replicas-deployment.yaml AdditionalImageReplicaDeploymentYaml string + + //go:embed simple-app-live.yaml + SimpleAppLiveYaml string + + //go:embed simple-app-target.yaml + SimpleAppTargetYaml string + + //go:embed target-rollout.yaml + TargetRolloutYaml string + + //go:embed live-rollout.yaml + LiveRolloutYaml string ) diff --git a/controller/testdata/live-rollout.yaml b/controller/testdata/live-rollout.yaml new file mode 100644 index 0000000000000..a8d52fd743b46 --- /dev/null +++ b/controller/testdata/live-rollout.yaml @@ -0,0 +1,25 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-sample +spec: + replicas: 2 + strategy: + canary: + steps: + - setWeight: 20 + selector: + matchLabels: + app: rollout-sample + template: + metadata: + labels: + app: rollout-sample + spec: + containers: + - name: init + image: init-container:v1 + livenessProbe: + initialDelaySeconds: 10 + - name: main + image: main-container:v1 diff --git a/controller/testdata/schemas/httpproxy_openapi_v2.yaml b/controller/testdata/schemas/httpproxy_openapi_v2.yaml new file mode 100644 index 0000000000000..00a42f54ed5b4 --- /dev/null +++ b/controller/testdata/schemas/httpproxy_openapi_v2.yaml @@ -0,0 +1,62 @@ +swagger: "2.0" +info: + title: HTTPProxy + version: "v1" +paths: {} +definitions: + io.projectcontour.v1.HTTPProxy: + type: object + x-kubernetes-group-version-kind: + - group: projectcontour.io + version: v1 + kind: HTTPProxy + properties: + spec: + type: object + properties: + routes: + type: array + items: + type: object + properties: + rateLimitPolicy: + type: object + properties: + global: + type: object + properties: + descriptors: + type: array + x-kubernetes-list-map-keys: + - entries + items: + type: object + properties: + entries: + type: array + x-kubernetes-list-map-keys: + - headerName + items: + type: object + properties: + requestHeader: + type: object + properties: + descriptorKey: + type: string + headerName: + type: string + requestHeaderValueMatch: + type: object + properties: + headers: + type: array + items: + type: object + properties: + name: + type: string + contains: + type: string + value: + type: string diff --git a/controller/testdata/schemas/rollout-schema.yaml b/controller/testdata/schemas/rollout-schema.yaml new file mode 100644 index 0000000000000..0edd9b18837ff --- /dev/null +++ b/controller/testdata/schemas/rollout-schema.yaml @@ -0,0 +1,67 @@ +swagger: "2.0" +info: + title: Rollout + version: "v1alpha1" +paths: {} +definitions: + argoproj.io.v1alpha1.Rollout: + type: object + x-kubernetes-group-version-kind: + - group: argoproj.io + version: v1alpha1 + kind: Rollout + properties: + spec: + type: object + properties: + replicas: + type: integer + strategy: + type: object + properties: + canary: + type: object + properties: + steps: + type: array + items: + type: object + properties: + setWeight: + type: integer + selector: + type: object + properties: + matchLabels: + type: object + additionalProperties: + type: string + template: + type: object + properties: + metadata: + type: object + properties: + labels: + type: object + additionalProperties: + type: string + spec: + type: object + properties: + containers: + type: array + x-kubernetes-list-map-keys: + - name + items: + type: object + properties: + name: + type: string + image: + type: string + livenessProbe: + type: object + properties: + initialDelaySeconds: + type: integer diff --git a/controller/testdata/schemas/simple-app.yaml b/controller/testdata/schemas/simple-app.yaml new file mode 100644 index 0000000000000..73277c7b4e49b --- /dev/null +++ b/controller/testdata/schemas/simple-app.yaml @@ -0,0 +1,29 @@ +swagger: "2.0" +info: + title: SimpleApp + version: "v1" +paths: {} +definitions: + example.com.v1.SimpleApp: + type: object + x-kubernetes-group-version-kind: + - group: example.com + version: v1 + kind: SimpleApp + properties: + spec: + type: object + properties: + servers: + type: array + x-kubernetes-list-map-keys: + - name + items: + type: object + properties: + name: + type: string + port: + type: integer + enabled: + type: boolean diff --git a/controller/testdata/simple-app-live.yaml b/controller/testdata/simple-app-live.yaml new file mode 100644 index 0000000000000..1f910a3d60df3 --- /dev/null +++ b/controller/testdata/simple-app-live.yaml @@ -0,0 +1,12 @@ +apiVersion: example.com/v1 +kind: SimpleApp +metadata: + name: simpleapp-sample +spec: + servers: + - name: server1 + port: 8081 # port changed in live from 8080 + enabled: true + - name: server2 + port: 9090 + enabled: true # enabled changed in live from false diff --git a/controller/testdata/simple-app-target.yaml b/controller/testdata/simple-app-target.yaml new file mode 100644 index 0000000000000..cfcf692743c3d --- /dev/null +++ b/controller/testdata/simple-app-target.yaml @@ -0,0 +1,12 @@ +apiVersion: example.com/v1 +kind: SimpleApp +metadata: + name: simpleapp-sample +spec: + servers: + - name: server1 + port: 8080 + enabled: true + - name: server2 + port: 9090 + enabled: false diff --git a/controller/testdata/target-rollout.yaml b/controller/testdata/target-rollout.yaml new file mode 100644 index 0000000000000..a42d2abd2088f --- /dev/null +++ b/controller/testdata/target-rollout.yaml @@ -0,0 +1,25 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-sample +spec: + replicas: 2 + strategy: + canary: + steps: + - setWeight: 20 + selector: + matchLabels: + app: rollout-sample + template: + metadata: + labels: + app: rollout-sample + spec: + containers: + - name: init + image: init-container:v1 + livenessProbe: + initialDelaySeconds: 15 + - name: main + image: main-container:v1 diff --git a/go.mod b/go.mod index 3417f5c0d8111..6716762424d84 100644 --- a/go.mod +++ b/go.mod @@ -214,7 +214,7 @@ require ( github.com/gobwas/ws v1.2.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/glog v1.2.5 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.0 github.com/google/go-github/v84 v84.0.0 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect