diff --git a/integration/update/basic/input/.ship/state.json b/integration/update/basic/input/.ship/state.json index e6ff0d514..c2cb08c8b 100644 --- a/integration/update/basic/input/.ship/state.json +++ b/integration/update/basic/input/.ship/state.json @@ -1 +1,16 @@ -{"v1":{"config":{},"helmValues":"# Default values for basic.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 5\n\nimage:\n repository: nginx\n tag: stable\n pullPolicy: IfNotPresent\n\nservice:\n type: ClusterIP\n port: 80\n\ningress:\n enabled: false\n annotations: {}\n # kubernetes.io/ingress.class: nginx\n # kubernetes.io/tls-acme: \"true\"\n path: /\n hosts:\n - chart-example.local\n tls: []\n # - secretName: chart-example-tls\n # hosts:\n # - chart-example.local\n\nresources: {}\n # We usually recommend not to specify default resources and to leave this as a conscious\n # choice for the user. This also increases chances charts run on environments with little\n # resources, such as Minikube. If you do want to specify resources, uncomment the following\n # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n # limits:\n # cpu: 100m\n # memory: 128Mi\n # requests:\n # cpu: 100m\n # memory: 128Mi\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n","kustomize":{"overlays":{"ship":{"patches":{"/templates/deployment.yaml":"--- \napiVersion: apps/v1beta2\nkind: Deployment\nmetadata:\n name: 'basic'\n"}}}},"upstream":"github.com/replicatedhq/test-charts/basic"}} \ No newline at end of file +{ + "v1": { + "config": {}, + "helmValues": "# Default values for basic.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 5\n\nimage:\n repository: nginx\n tag: stable\n pullPolicy: IfNotPresent\n\nservice:\n type: ClusterIP\n port: 80\n\ningress:\n enabled: false\n annotations: {}\n # kubernetes.io/ingress.class: nginx\n # kubernetes.io/tls-acme: \"true\"\n path: /\n hosts:\n - chart-example.local\n tls: []\n # - secretName: chart-example-tls\n # hosts:\n # - chart-example.local\n\nresources: {}\n # We usually recommend not to specify default resources and to leave this as a conscious\n # choice for the user. This also increases chances charts run on environments with little\n # resources, such as Minikube. If you do want to specify resources, uncomment the following\n # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n # limits:\n # cpu: 100m\n # memory: 128Mi\n # requests:\n # cpu: 100m\n # memory: 128Mi\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n", + "kustomize": { + "overlays": { + "ship": { + "patches": { + "/templates/deployment.yaml": "--- \napiVersion: apps/v1beta2\nkind: Deployment\nmetadata:\n name: 'basic'\n" + } + } + } + }, + "upstream": "github.com/replicatedhq/test-charts/basic" + } +} \ No newline at end of file diff --git a/integration/update/excluded-basic/expected/.ship/state.json b/integration/update/excluded-basic/expected/.ship/state.json new file mode 100755 index 000000000..ae8ba4f9f --- /dev/null +++ b/integration/update/excluded-basic/expected/.ship/state.json @@ -0,0 +1,28 @@ +{ + "v1": { + "config": {}, + "helmValues": "affinity: {}\nimage:\n pullPolicy: IfNotPresent\n repository: nginx\n tag: stable\ningress:\n annotations: {}\n enabled: false\n hosts:\n - chart-example.local\n path: /\n tls: []\nnodeSelector: {}\nreplicaCount: 5\nresources: {}\nservice:\n port: 80\n type: ClusterIP\ntolerations: []\n", + "releaseName": "basic", + "helmValuesDefaults": "# Default values for basic.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 1\n\nimage:\n repository: nginx\n tag: stable\n pullPolicy: IfNotPresent\n\nservice:\n type: ClusterIP\n port: 80\n\ningress:\n enabled: false\n annotations: {}\n # kubernetes.io/ingress.class: nginx\n # kubernetes.io/tls-acme: \"true\"\n path: /\n hosts:\n - chart-example.local\n tls: []\n # - secretName: chart-example-tls\n # hosts:\n # - chart-example.local\n\nresources: {}\n # We usually recommend not to specify default resources and to leave this as a conscious\n # choice for the user. This also increases chances charts run on environments with little\n # resources, such as Minikube. If you do want to specify resources, uncomment the following\n # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n # limits:\n # cpu: 100m\n # memory: 128Mi\n # requests:\n # cpu: 100m\n # memory: 128Mi\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n", + "kustomize": { + "overlays": { + "ship": { + "excludedBases": [ + "service.yaml" + ], + "patches": { + "/templates/deployment.yaml": "--- \napiVersion: apps/v1beta2\nkind: Deployment\nmetadata:\n name: 'basic'\n" + } + } + } + }, + "upstream": "github.com/replicatedhq/test-charts/basic", + "metadata": { + "applicationType": "helm", + "name": "basic", + "releaseNotes": "Update README.md", + "version": "0.1.0" + }, + "contentSHA": "94d255c48a20929dbfbe341109a7ab86dae1ecfab3b8f01151bb36a2ebea7bbe" + } +} \ No newline at end of file diff --git a/integration/update/excluded-basic/expected/base/deployment.yaml b/integration/update/excluded-basic/expected/base/deployment.yaml new file mode 100644 index 000000000..2e6b9535d --- /dev/null +++ b/integration/update/excluded-basic/expected/base/deployment.yaml @@ -0,0 +1,42 @@ +--- +# Source: basic/templates/deployment.yaml +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: basic + labels: + app: basic + chart: basic-0.1.0 + release: basic + heritage: Tiller +spec: + replicas: 5 + selector: + matchLabels: + app: basic + release: basic + template: + metadata: + labels: + app: basic + release: basic + spec: + containers: + - name: basic + image: "nginx:stable" + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {} + diff --git a/integration/update/excluded-basic/expected/base/kustomization.yaml b/integration/update/excluded-basic/expected/base/kustomization.yaml new file mode 100644 index 000000000..08e544692 --- /dev/null +++ b/integration/update/excluded-basic/expected/base/kustomization.yaml @@ -0,0 +1,4 @@ +kind: "" +apiversion: "" +resources: +- deployment.yaml diff --git a/integration/update/excluded-basic/expected/base/service.yaml b/integration/update/excluded-basic/expected/base/service.yaml new file mode 100644 index 000000000..6941b5e5c --- /dev/null +++ b/integration/update/excluded-basic/expected/base/service.yaml @@ -0,0 +1,21 @@ +--- +# Source: basic/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: basic + labels: + app: basic + chart: basic-0.1.0 + release: basic + heritage: Tiller +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: basic + release: basic diff --git a/integration/update/excluded-basic/expected/overlays/ship/kustomization.yaml b/integration/update/excluded-basic/expected/overlays/ship/kustomization.yaml new file mode 100644 index 000000000..74473f897 --- /dev/null +++ b/integration/update/excluded-basic/expected/overlays/ship/kustomization.yaml @@ -0,0 +1,6 @@ +kind: "" +apiversion: "" +bases: +- ../../base +patchesStrategicMerge: +- templates/deployment.yaml diff --git a/integration/update/excluded-basic/expected/overlays/ship/templates/deployment.yaml b/integration/update/excluded-basic/expected/overlays/ship/templates/deployment.yaml new file mode 100644 index 000000000..894372e49 --- /dev/null +++ b/integration/update/excluded-basic/expected/overlays/ship/templates/deployment.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: 'basic' diff --git a/integration/update/excluded-basic/expected/rendered.yaml b/integration/update/excluded-basic/expected/rendered.yaml new file mode 100644 index 000000000..f1d29dfa7 --- /dev/null +++ b/integration/update/excluded-basic/expected/rendered.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + labels: + app: basic + chart: basic-0.1.0 + heritage: Tiller + release: basic + name: basic +spec: + replicas: 5 + selector: + matchLabels: + app: basic + release: basic + template: + metadata: + labels: + app: basic + release: basic + spec: + containers: + - image: nginx:stable + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: / + port: http + name: basic + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: / + port: http + resources: {} diff --git a/integration/update/excluded-basic/input/.ship/state.json b/integration/update/excluded-basic/input/.ship/state.json new file mode 100644 index 000000000..3fed5bade --- /dev/null +++ b/integration/update/excluded-basic/input/.ship/state.json @@ -0,0 +1,19 @@ +{ + "v1": { + "config": {}, + "helmValues": "# Default values for basic.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 5\n\nimage:\n repository: nginx\n tag: stable\n pullPolicy: IfNotPresent\n\nservice:\n type: ClusterIP\n port: 80\n\ningress:\n enabled: false\n annotations: {}\n # kubernetes.io/ingress.class: nginx\n # kubernetes.io/tls-acme: \"true\"\n path: /\n hosts:\n - chart-example.local\n tls: []\n # - secretName: chart-example-tls\n # hosts:\n # - chart-example.local\n\nresources: {}\n # We usually recommend not to specify default resources and to leave this as a conscious\n # choice for the user. This also increases chances charts run on environments with little\n # resources, such as Minikube. If you do want to specify resources, uncomment the following\n # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n # limits:\n # cpu: 100m\n # memory: 128Mi\n # requests:\n # cpu: 100m\n # memory: 128Mi\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n", + "kustomize": { + "overlays": { + "ship": { + "excludedBases": [ + "service.yaml" + ], + "patches": { + "/templates/deployment.yaml": "--- \napiVersion: apps/v1beta2\nkind: Deployment\nmetadata:\n name: 'basic'\n" + } + } + } + }, + "upstream": "github.com/replicatedhq/test-charts/basic" + } +} \ No newline at end of file diff --git a/integration/update/excluded-basic/metadata.yaml b/integration/update/excluded-basic/metadata.yaml new file mode 100644 index 000000000..ccf314c3e --- /dev/null +++ b/integration/update/excluded-basic/metadata.yaml @@ -0,0 +1,2 @@ +args: ["--prefer-git"] +skip_cleanup: false diff --git a/pkg/filetree/api.go b/pkg/filetree/api.go index 66a666021..3fd868091 100644 --- a/pkg/filetree/api.go +++ b/pkg/filetree/api.go @@ -6,4 +6,5 @@ type Node struct { Path string `json:"path" yaml:"path"` HasOverlay bool `json:"hasOverlay" yaml:"hasOverlay"` IsSupported bool `json:"isSupported" yaml:"isSupported"` + IsExcluded bool `json:"isExcluded" yaml:"isExcluded"` } diff --git a/pkg/filetree/loader.go b/pkg/filetree/loader.go index 3d03f9fb2..75d63bf74 100644 --- a/pkg/filetree/loader.go +++ b/pkg/filetree/loader.go @@ -45,11 +45,12 @@ func NewLoader( } type aferoLoader struct { - Logger log.Logger - FS afero.Afero - StateManager state.Manager - patches map[string]string - resources map[string]string + Logger log.Logger + FS afero.Afero + StateManager state.Manager + excludedBases map[string]string + patches map[string]string + resources map[string]string } func (a *aferoLoader) loadShipOverlay() error { @@ -64,6 +65,11 @@ func (a *aferoLoader) loadShipOverlay() error { } shipOverlay := kustomize.Ship() + baseMap := make(map[string]string) + for _, base := range shipOverlay.ExcludedBases { + baseMap[base] = base + } + a.excludedBases = baseMap a.patches = shipOverlay.Patches a.resources = shipOverlay.Resources return nil @@ -155,11 +161,13 @@ func (a *aferoLoader) loadTree(fs afero.Afero, current Node, files []os.FileInfo return current, errors.Wrapf(err, "read file %s", file.Name()) } + _, exists := a.excludedBases[filePath] return a.loadTree(fs, current.withChild(Node{ Name: file.Name(), Path: filePath, HasOverlay: hasOverlay, IsSupported: isSupported(fileB), + IsExcluded: exists, }), rest) } diff --git a/pkg/lifecycle/daemon/routes_navcycle.go b/pkg/lifecycle/daemon/routes_navcycle.go index 56c85687f..c48d252d8 100644 --- a/pkg/lifecycle/daemon/routes_navcycle.go +++ b/pkg/lifecycle/daemon/routes_navcycle.go @@ -68,6 +68,7 @@ func (d *NavcycleRoutes) Register(group *gin.RouterGroup, release *api.Release) kustom.POST("patch", d.createOrMergePatch) kustom.DELETE("patch", d.deletePatch) kustom.DELETE("resource", d.deleteResource) + kustom.DELETE("base", d.deleteBase) kustom.POST("apply", d.applyPatch) conf := v1.Group("/config") diff --git a/pkg/lifecycle/daemon/routes_navcycle_kustomize.go b/pkg/lifecycle/daemon/routes_navcycle_kustomize.go index 118ec1f33..2b1b33bb8 100644 --- a/pkg/lifecycle/daemon/routes_navcycle_kustomize.go +++ b/pkg/lifecycle/daemon/routes_navcycle_kustomize.go @@ -208,6 +208,7 @@ func (d *NavcycleRoutes) applyPatch(c *gin.Context) { if err := c.BindJSON(&request); err != nil { level.Error(d.Logger).Log("event", "unmarshal request body failed", "err", err) c.AbortWithError(500, errors.New("internal_server_error")) + return } debug.Log("event", "getKustomizationStep") @@ -221,6 +222,7 @@ func (d *NavcycleRoutes) applyPatch(c *gin.Context) { if err != nil { level.Error(d.Logger).Log("event", "failed to merge patch with base", "err", err) c.AbortWithError(500, errors.New("internal_server_error")) + return } c.JSON(200, map[string]interface{}{ @@ -242,6 +244,7 @@ func (d *NavcycleRoutes) createOrMergePatch(c *gin.Context) { if err := c.BindJSON(&request); err != nil { level.Error(d.Logger).Log("event", "unmarshal request body failed", "err", err) c.AbortWithError(500, errors.New("internal_server_error")) + return } var stringPath []string @@ -254,6 +257,7 @@ func (d *NavcycleRoutes) createOrMergePatch(c *gin.Context) { default: level.Error(d.Logger).Log("event", "invalid path provided") c.AbortWithError(500, errors.New("internal_server_error")) + return } } @@ -267,6 +271,7 @@ func (d *NavcycleRoutes) createOrMergePatch(c *gin.Context) { if err != nil { level.Error(d.Logger).Log("event", "failed to read original file", "err", err) c.AbortWithError(500, errors.New("internal_server_error")) + return } debug.Log("event", "patcher.modifyField") @@ -274,6 +279,7 @@ func (d *NavcycleRoutes) createOrMergePatch(c *gin.Context) { if err != nil { level.Error(d.Logger).Log("event", "modify field", "err", err) c.AbortWithError(500, errors.New("internal_server_error")) + return } debug.Log("event", "patcher.CreatePatch") @@ -281,6 +287,7 @@ func (d *NavcycleRoutes) createOrMergePatch(c *gin.Context) { if err != nil { level.Error(d.Logger).Log("event", "create two way merge patch", "err", err) c.AbortWithError(500, errors.New("internal_server_error")) + return } if len(request.Current) > 0 { @@ -288,6 +295,7 @@ func (d *NavcycleRoutes) createOrMergePatch(c *gin.Context) { if err != nil { level.Error(d.Logger).Log("event", "merge current and new patch", "err", err) c.AbortWithError(500, errors.New("internal_server_error")) + return } c.JSON(200, map[string]interface{}{ "patch": string(out), @@ -298,6 +306,53 @@ func (d *NavcycleRoutes) createOrMergePatch(c *gin.Context) { }) } } + +func (d *NavcycleRoutes) deleteBase(c *gin.Context) { + debug := level.Debug(log.With(d.Logger, "struct", "daemon", "handler", "deleteBase")) + pathQueryParam := c.Query("path") + if pathQueryParam == "" { + c.AbortWithError(http.StatusBadRequest, errors.New("bad delete request")) + return + } + + currentState, err := d.StateManager.TryLoad() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "delete base")) + return + } + + kustomize := currentState.CurrentKustomize() + if kustomize == nil { + kustomize = &state.Kustomize{} + } + + shipOverlay := kustomize.Ship() + for _, base := range shipOverlay.ExcludedBases { + if base == pathQueryParam { + debug.Log("event", "base", pathQueryParam, "exists in excluded") + c.AbortWithError(http.StatusInternalServerError, errors.New("internal_server_error")) + return + } + } + shipOverlay.ExcludedBases = append(shipOverlay.ExcludedBases, pathQueryParam) + + if _, exists := shipOverlay.Patches[pathQueryParam]; exists { + delete(shipOverlay.Patches, pathQueryParam) + } + + if kustomize.Overlays == nil { + kustomize.Overlays = map[string]state.Overlay{} + } + kustomize.Overlays["ship"] = shipOverlay + + if err := d.StateManager.SaveKustomize(kustomize); err != nil { + c.AbortWithError(500, errors.Wrap(err, "delete base")) + return + } + + c.JSON(200, map[string]string{"status": "success"}) +} + func (d *NavcycleRoutes) deleteResource(c *gin.Context) { debug := level.Debug(log.With(d.Logger, "struct", "daemon", "handler", "deleteResource")) pathQueryParam := c.Query("path") @@ -314,6 +369,7 @@ func (d *NavcycleRoutes) deleteResource(c *gin.Context) { if err != nil { level.Error(d.Logger).Log("event", "resource.delete.fail", "path", pathQueryParam, "err", err) c.AbortWithError(500, errors.Wrap(err, "delete resource")) + return } c.JSON(200, map[string]string{"status": "success"}) } @@ -334,6 +390,7 @@ func (d *NavcycleRoutes) deletePatch(c *gin.Context) { if err != nil { level.Error(d.Logger).Log("event", "resource.delete.fail", "path", pathQueryParam, "err", err) c.AbortWithError(500, errors.Wrap(err, "delete resource")) + return } c.JSON(200, map[string]string{"status": "success"}) diff --git a/pkg/lifecycle/daemon/routes_navcycle_kustomize_test.go b/pkg/lifecycle/daemon/routes_navcycle_kustomize_test.go index 2ac829441..26b3c5dda 100644 --- a/pkg/lifecycle/daemon/routes_navcycle_kustomize_test.go +++ b/pkg/lifecycle/daemon/routes_navcycle_kustomize_test.go @@ -33,6 +33,7 @@ func TestV2KustomizeSaveFile(t *testing.T) { ExpectState: state.Kustomize{ Overlays: map[string]state.Overlay{ "ship": { + ExcludedBases: []string{}, Patches: map[string]string{ "deployment.yaml": "foo/bar/baz", }, @@ -51,7 +52,8 @@ func TestV2KustomizeSaveFile(t *testing.T) { ExpectState: state.Kustomize{ Overlays: map[string]state.Overlay{ "ship": { - Patches: map[string]string{}, + ExcludedBases: []string{}, + Patches: map[string]string{}, Resources: map[string]string{ "deployment.yaml": "foo/bar/baz", }, @@ -70,6 +72,7 @@ func TestV2KustomizeSaveFile(t *testing.T) { Kustomize: &state.Kustomize{ Overlays: map[string]state.Overlay{ "ship": { + ExcludedBases: []string{}, Patches: map[string]string{ "deployment.yaml": "foo/bar/baz", }, @@ -80,6 +83,7 @@ func TestV2KustomizeSaveFile(t *testing.T) { ExpectState: state.Kustomize{ Overlays: map[string]state.Overlay{ "ship": { + ExcludedBases: []string{}, Patches: map[string]string{ "deployment.yaml": "foo/bar/baz", }, @@ -101,6 +105,7 @@ func TestV2KustomizeSaveFile(t *testing.T) { Kustomize: &state.Kustomize{ Overlays: map[string]state.Overlay{ "ship": { + ExcludedBases: []string{}, Resources: map[string]string{ "deployment.yaml": "foo/bar/baz", }, @@ -114,6 +119,7 @@ func TestV2KustomizeSaveFile(t *testing.T) { ExpectState: state.Kustomize{ Overlays: map[string]state.Overlay{ "ship": { + ExcludedBases: []string{}, Resources: map[string]string{ "deployment.yaml": "foo/bar/baz", "service.yaml": "foo/bar/baz", diff --git a/pkg/lifecycle/kustomize/kustomizer.go b/pkg/lifecycle/kustomize/kustomizer.go index c1c070378..6a6aea478 100644 --- a/pkg/lifecycle/kustomize/kustomizer.go +++ b/pkg/lifecycle/kustomize/kustomizer.go @@ -189,6 +189,17 @@ func (l *Kustomizer) writeOverlay( func (l *Kustomizer) writeBase(step api.Kustomize) error { debug := level.Debug(log.With(l.Logger, "method", "writeBase")) + currentState, err := l.State.TryLoad() + if err != nil { + return errors.Wrap(err, "load state") + } + + currentKustomize := currentState.CurrentKustomize() + if currentKustomize == nil { + currentKustomize = &state.Kustomize{} + } + shipOverlay := currentKustomize.Ship() + baseKustomization := ktypes.Kustomization{} if err := l.FS.Walk( step.Base, @@ -197,12 +208,12 @@ func (l *Kustomizer) writeBase(step api.Kustomize) error { debug.Log("event", "walk.fail", "path", targetPath) return errors.Wrap(err, "failed to walk path") } - if l.shouldAddFileToBase(targetPath) { - relativePath, err := filepath.Rel(step.Base, targetPath) - if err != nil { - debug.Log("event", "relativepath.fail", "base", step.Base, "target", targetPath) - return errors.Wrap(err, "failed to get relative path") - } + relativePath, err := filepath.Rel(step.Base, targetPath) + if err != nil { + debug.Log("event", "relativepath.fail", "base", step.Base, "target", targetPath) + return errors.Wrap(err, "failed to get relative path") + } + if l.shouldAddFileToBase(shipOverlay.ExcludedBases, relativePath) { baseKustomization.Resources = append(baseKustomization.Resources, relativePath) } return nil @@ -229,11 +240,18 @@ func (l *Kustomizer) writeBase(step api.Kustomize) error { return nil } -func (l *Kustomizer) shouldAddFileToBase(targetPath string) bool { +func (l *Kustomizer) shouldAddFileToBase(excludedBases []string, targetPath string) bool { if filepath.Ext(targetPath) != ".yaml" && filepath.Ext(targetPath) != ".yml" { return false } + for _, base := range excludedBases { + basePathWOLeading := strings.TrimPrefix(base, "/") + if basePathWOLeading == targetPath { + return false + } + } + return !strings.HasSuffix(targetPath, "kustomization.yaml") && !strings.HasSuffix(targetPath, "Chart.yaml") && !strings.HasSuffix(targetPath, "values.yaml") diff --git a/pkg/lifecycle/kustomize/kustomizer_test.go b/pkg/lifecycle/kustomize/kustomizer_test.go index c6a0ed8d8..19d8e7950 100644 --- a/pkg/lifecycle/kustomize/kustomizer_test.go +++ b/pkg/lifecycle/kustomize/kustomizer_test.go @@ -185,10 +185,11 @@ func Test_kustomizer_writeBase(t *testing.T) { GetFS func() (afero.Afero, error) } tests := []struct { - name string - fields fields - expectFile string - wantErr bool + name string + fields fields + expectFile string + wantErr bool + excludedBases []string }{ { name: "No base files", @@ -274,6 +275,45 @@ resources: - deployment.yaml `, }, + { + name: "Base files with nested and excluded chart", + fields: fields{ + GetFS: func() (afero.Afero, error) { + fs := afero.Afero{Fs: afero.NewMemMapFs()} + nestedChartPath := path.Join( + constants.KustomizeBasePath, + "charts/kube-stats-metrics/templates", + ) + if err := fs.MkdirAll(nestedChartPath, 0777); err != nil { + return afero.Afero{}, err + } + + files := []string{ + "deployment.yaml", + "clusterrole.yaml", + "charts/kube-stats-metrics/templates/deployment.yaml", + } + for _, file := range files { + if err := fs.WriteFile( + path.Join(constants.KustomizeBasePath, file), + []byte{}, + 0777, + ); err != nil { + return afero.Afero{}, err + } + } + + return fs, nil + }, + }, + expectFile: `kind: "" +apiversion: "" +resources: +- charts/kube-stats-metrics/templates/deployment.yaml +- deployment.yaml +`, + excludedBases: []string{"/clusterrole.yaml"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -283,6 +323,18 @@ resources: mockDaemon := daemon2.NewMockDaemon(mc) mockState := state2.NewMockManager(mc) + mockState.EXPECT().TryLoad().Return(state.VersionedState{ + V1: &state.V1{ + Kustomize: &state.Kustomize{ + Overlays: map[string]state.Overlay{ + "ship": state.Overlay{ + ExcludedBases: tt.excludedBases, + }, + }, + }, + }, + }, nil).AnyTimes() + fs, err := tt.fields.GetFS() req.NoError(err) @@ -452,7 +504,7 @@ resources: mockDaemon.EXPECT().KustomizeSavedChan().Return(saveChan) mockState.EXPECT().TryLoad().Return(state.VersionedState{V1: &state.V1{ Kustomize: test.kustomize, - }}, nil) + }}, nil).Times(2) k := &daemonkustomizer{ Kustomizer: Kustomizer{ @@ -487,31 +539,35 @@ func TestKustomizer_shouldAddFile(t *testing.T) { k := daemonkustomizer{} tests := []struct { - name string - targetPath string - want bool + name string + targetPath string + want bool + excludedPaths []string }{ - {name: "empty", targetPath: "", want: false}, - {name: "no extension", targetPath: "file", want: false}, - {name: "wrong extension", targetPath: "file.txt", want: false}, - {name: "yaml file", targetPath: "file.yaml", want: true}, - {name: "yml file", targetPath: "file.yml", want: true}, - {name: "kustomization yaml", targetPath: "kustomization.yaml", want: false}, - {name: "Chart yaml", targetPath: "Chart.yaml", want: false}, - {name: "values yaml", targetPath: "values.yaml", want: false}, - {name: "no extension in dir", targetPath: "dir/file", want: false}, - {name: "wrong extension in dir", targetPath: "dir/file.txt", want: false}, - {name: "yaml in dir", targetPath: "dir/file.yaml", want: true}, - {name: "yml in dir", targetPath: "dir/file.yml", want: true}, - {name: "kustomization yaml in dir", targetPath: "dir/kustomization.yaml", want: false}, - {name: "Chart yaml in dir", targetPath: "dir/Chart.yaml", want: false}, - {name: "values yaml in dir", targetPath: "dir/values.yaml", want: false}, + {name: "empty", targetPath: "", want: false, excludedPaths: []string{}}, + {name: "no extension", targetPath: "file", want: false, excludedPaths: []string{}}, + {name: "wrong extension", targetPath: "file.txt", want: false, excludedPaths: []string{}}, + {name: "yaml file", targetPath: "file.yaml", want: true, excludedPaths: []string{}}, + {name: "yml file", targetPath: "file.yml", want: true, excludedPaths: []string{}}, + {name: "kustomization yaml", targetPath: "kustomization.yaml", want: false, excludedPaths: []string{}}, + {name: "Chart yaml", targetPath: "Chart.yaml", want: false, excludedPaths: []string{}}, + {name: "values yaml", targetPath: "values.yaml", want: false, excludedPaths: []string{}}, + {name: "no extension in dir", targetPath: "dir/file", want: false, excludedPaths: []string{}}, + {name: "wrong extension in dir", targetPath: "dir/file.txt", want: false, excludedPaths: []string{}}, + {name: "yaml in dir", targetPath: "dir/file.yaml", want: true, excludedPaths: []string{}}, + {name: "yml in dir", targetPath: "dir/file.yml", want: true, excludedPaths: []string{}}, + {name: "kustomization yaml in dir", targetPath: "dir/kustomization.yaml", want: false, excludedPaths: []string{}}, + {name: "Chart yaml in dir", targetPath: "dir/Chart.yaml", want: false, excludedPaths: []string{}}, + {name: "values yaml in dir", targetPath: "dir/values.yaml", want: false, excludedPaths: []string{}}, + {name: "path in excluded", targetPath: "deployment.yaml", want: false, excludedPaths: []string{"/deployment.yaml"}}, + {name: "path not in excluded", targetPath: "service.yaml", want: true, excludedPaths: []string{"/deployment.yaml"}}, + {name: "similar path in excluded", targetPath: "dir/service.yaml", want: true, excludedPaths: []string{"/service.yaml"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := require.New(t) - got := k.shouldAddFileToBase(tt.targetPath) + got := k.shouldAddFileToBase(tt.excludedPaths, tt.targetPath) req.Equal(tt.want, got, "expected %t for path %s, got %t", tt.want, tt.targetPath, got) }) diff --git a/pkg/state/models.go b/pkg/state/models.go index 11da22b08..2c45f3411 100644 --- a/pkg/state/models.go +++ b/pkg/state/models.go @@ -130,6 +130,7 @@ func (l *Lifeycle) WithCompletedStep(step api.Step) *Lifeycle { } type Overlay struct { + ExcludedBases []string `json:"excludedBases,omitempty" yaml:"excludedBases,omitempty" hcl:"excludedBases,omitempty"` Patches map[string]string `json:"patches,omitempty" yaml:"patches,omitempty" hcl:"patches,omitempty"` Resources map[string]string `json:"resources,omitempty" yaml:"resources,omitempty" hcl:"resources,omitempty"` KustomizationYAML string `json:"kustomization_yaml,omitempty" yaml:"kustomization_yaml,omitempty" hcl:"kustomization_yaml,omitempty"` @@ -137,8 +138,9 @@ type Overlay struct { func NewOverlay() Overlay { return Overlay{ - Patches: map[string]string{}, - Resources: map[string]string{}, + ExcludedBases: []string{}, + Patches: map[string]string{}, + Resources: map[string]string{}, } } diff --git a/web/init/src/components/kustomize/kustomize_overlay/FileTree.jsx b/web/init/src/components/kustomize/kustomize_overlay/FileTree.jsx index 5021295d2..ccfcb22e0 100644 --- a/web/init/src/components/kustomize/kustomize_overlay/FileTree.jsx +++ b/web/init/src/components/kustomize/kustomize_overlay/FileTree.jsx @@ -1,4 +1,5 @@ import * as React from "react"; +import PropTypes from "prop-types"; export default class FileTree extends React.Component { @@ -11,8 +12,13 @@ export default class FileTree extends React.Component { this.props.handleDeleteOverlay(path); } + handleDeleteBase = (e, path) => { + e.stopPropagation(); + this.props.handleDeleteBase(path); + } + render() { - const { files, basePath, isRoot, selectedFile, handleFileSelect, handleDeleteOverlay, isOverlayTree, isResourceTree } = this.props; + const { files, basePath, isRoot, selectedFile, handleFileSelect, handleDeleteOverlay, isOverlayTree, isResourceTree, isBaseTree } = this.props; return (
{ message }
+It will not be applied to the kustomization.yaml file that is generated for you.
+Are you sure you want to discard this patch?
-It will not be applied to the kustomization.yaml file that is generated for you.
-