diff --git a/docs/fields.md b/docs/fields.md index c922245ed..68b73cbfa 100644 --- a/docs/fields.md +++ b/docs/fields.md @@ -355,6 +355,7 @@ Replicas modified the number of replicas for a resource. E.g. Given this kubernetes Deployment fragment: ``` +kind: Deployment metadata: name: deployment-name spec: @@ -374,6 +375,17 @@ This field accepts a list, so many resources can be modified at the same time. +#### Limitation +As this declaration does not take in a `kind:` nor a `group:` +it will match any `group` and `kind` that has a matching name and +that is one of: +- `Deployment` +- `ReplicationController` +- `ReplicaSet` +- `StatefulSet` + +For more complex use cases, revert to using a patch. + ### resources diff --git a/pkg/target/kusttarget_configplugin.go b/pkg/target/kusttarget_configplugin.go index d1b830eb9..7a72472f0 100644 --- a/pkg/target/kusttarget_configplugin.go +++ b/pkg/target/kusttarget_configplugin.go @@ -239,10 +239,12 @@ func (kt *KustTarget) configureBuiltinReplicaCountTransformer( tConfig *config.TransformerConfig) ( result []transformers.Transformer, err error) { var c struct { - Replica types.Replica + Replica types.Replica + FieldSpecs []config.FieldSpec } for _, args := range kt.kustomization.Replicas { c.Replica = args + c.FieldSpecs = tConfig.Replicas p := builtin.NewReplicaCountTransformerPlugin() err = kt.configureBuiltinPlugin(p, c, "replica") if err != nil { diff --git a/pkg/transformers/config/defaultconfig/defaultconfig.go b/pkg/transformers/config/defaultconfig/defaultconfig.go index d34d9a934..78719f372 100644 --- a/pkg/transformers/config/defaultconfig/defaultconfig.go +++ b/pkg/transformers/config/defaultconfig/defaultconfig.go @@ -32,6 +32,7 @@ func GetDefaultFieldSpecs() []byte { []byte(varReferenceFieldSpecs), []byte(nameReferenceFieldSpecs), []byte(imagesFieldSpecs), + []byte(replicasFieldSpecs), } return bytes.Join(configData, []byte("\n")) } @@ -47,5 +48,6 @@ func GetDefaultFieldSpecsAsMap() map[string]string { result["varreference"] = varReferenceFieldSpecs result["namereference"] = nameReferenceFieldSpecs result["images"] = imagesFieldSpecs + result["replicas"] = replicasFieldSpecs return result } diff --git a/pkg/transformers/config/defaultconfig/replicas.go b/pkg/transformers/config/defaultconfig/replicas.go new file mode 100644 index 000000000..1ab8d0079 --- /dev/null +++ b/pkg/transformers/config/defaultconfig/replicas.go @@ -0,0 +1,20 @@ +package defaultconfig + +const replicasFieldSpecs = ` +replicas: +- path: spec/replicas + create: true + kind: Deployment + +- path: spec/replicas + create: true + kind: ReplicationController + +- path: spec/replicas + create: true + kind: ReplicaSet + +- path: spec/replicas + create: true + kind: StatefulSet +` diff --git a/pkg/transformers/config/transformerconfig.go b/pkg/transformers/config/transformerconfig.go index a00c6da66..bede48b72 100644 --- a/pkg/transformers/config/transformerconfig.go +++ b/pkg/transformers/config/transformerconfig.go @@ -35,6 +35,7 @@ type TransformerConfig struct { NameReference nbrSlice `json:"nameReference,omitempty" yaml:"nameReference,omitempty"` VarReference fsSlice `json:"varReference,omitempty" yaml:"varReference,omitempty"` Images fsSlice `json:"images,omitempty" yaml:"images,omitempty"` + Replicas fsSlice `json:"replicas,omitempty" yaml:"replicas,omitempty"` } // MakeEmptyConfig returns an empty TransformerConfig object @@ -61,6 +62,7 @@ func (t *TransformerConfig) sortFields() { sort.Sort(t.NameReference) sort.Sort(t.VarReference) sort.Sort(t.Images) + sort.Sort(t.Replicas) } // AddPrefixFieldSpec adds a FieldSpec to NamePrefix @@ -135,6 +137,10 @@ func (t *TransformerConfig) Merge(input *TransformerConfig) ( if err != nil { return nil, err } + merged.Replicas, err = t.Replicas.mergeAll(input.Replicas) + if err != nil { + return nil, err + } merged.sortFields() return merged, nil } diff --git a/pkg/types/kustomization.go b/pkg/types/kustomization.go index fb889c50e..fae15dcc1 100644 --- a/pkg/types/kustomization.go +++ b/pkg/types/kustomization.go @@ -369,5 +369,5 @@ type Replica struct { Name string `json:"name,omitempty" yaml:"name,omitempty"` // The number of replicas required. - Count uint `json:"count,omitempty" yaml:"count,omitempty"` + Count int64 `json:"count,omitempty" yaml:"count,omitempty"` } diff --git a/plugin/builtin/ReplicaCountTransformer.go b/plugin/builtin/ReplicaCountTransformer.go index 30a085f4e..6bc6a1ac2 100644 --- a/plugin/builtin/ReplicaCountTransformer.go +++ b/plugin/builtin/ReplicaCountTransformer.go @@ -7,19 +7,17 @@ import ( "sigs.k8s.io/kustomize/pkg/ifc" "sigs.k8s.io/kustomize/pkg/resid" "sigs.k8s.io/kustomize/pkg/resmap" + "sigs.k8s.io/kustomize/pkg/transformers" + "sigs.k8s.io/kustomize/pkg/transformers/config" "sigs.k8s.io/kustomize/pkg/types" "sigs.k8s.io/yaml" ) -const ( - fldReplica = "replicas" - fldSpec = "spec" -) - // Find matching replicas declarations and replace the count. // Eases the kustomization configuration of replica changes. type ReplicaCountTransformerPlugin struct { - Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"` + Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"` + FieldSpecs []config.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` } //noinspection GoUnusedGlobalVariable @@ -31,30 +29,46 @@ func (p *ReplicaCountTransformerPlugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.Replica = types.Replica{} + p.FieldSpecs = nil return yaml.Unmarshal(c, p) } func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error { - matcher := func(r resid.ResId) bool { - return r.Name == p.Replica.Name - } - - for _, r := range m.GetMatchingResourcesByOriginalId(matcher) { - kMap := r.Map() - - specInterface, ok := kMap[fldSpec] - if !ok { - return fmt.Errorf("object %s missing field %s, cannot update %s", - p.Replica.Name, fldSpec, fldReplica) - } - - if spec, ok := specInterface.(map[string]interface{}); ok { - spec[fldReplica] = p.Replica.Count - kMap[fldSpec] = spec - } else { - return fmt.Errorf("object %s has a malformed %s", p.Replica.Name, fldSpec) + for i, replicaSpec := range p.FieldSpecs { + for _, res := range m.GetMatchingResourcesByOriginalId(p.createMatcher(i)) { + err := transformers.MutateField( + res.Map(), replicaSpec.PathSlice(), + replicaSpec.CreateIfNotPresent, p.addReplicas) + if err != nil { + return err + } } } return nil } + +// Match Replica.Name and FieldSpec +func (p *ReplicaCountTransformerPlugin) createMatcher(i int) resmap.IdMatcher { + return func(r resid.ResId) bool { + return r.Name == p.Replica.Name && + r.Gvk.IsSelected(&p.FieldSpecs[i].Gvk) + } +} + +func (p *ReplicaCountTransformerPlugin) addReplicas(in interface{}) (interface{}, error) { + switch m := in.(type) { + case int64: + // Was already in the field. + case map[string]interface{}: + if len(m) != 0 { + // A map was already in the replicas field, don't want to + // discard this data silently. + return nil, fmt.Errorf("%#v is expected to be %T", in, m) + } + // Just got added, default type is map, but we can return anything. + default: + return nil, fmt.Errorf("%#v is expected to be %T", in, m) + } + return p.Replica.Count, nil +} diff --git a/plugin/builtin/replicacounttransformer/ReplicaCountTransformer.go b/plugin/builtin/replicacounttransformer/ReplicaCountTransformer.go index 34bd4adf2..fe264ee61 100644 --- a/plugin/builtin/replicacounttransformer/ReplicaCountTransformer.go +++ b/plugin/builtin/replicacounttransformer/ReplicaCountTransformer.go @@ -10,19 +10,17 @@ import ( "sigs.k8s.io/kustomize/pkg/ifc" "sigs.k8s.io/kustomize/pkg/resid" "sigs.k8s.io/kustomize/pkg/resmap" + "sigs.k8s.io/kustomize/pkg/transformers" + "sigs.k8s.io/kustomize/pkg/transformers/config" "sigs.k8s.io/kustomize/pkg/types" "sigs.k8s.io/yaml" ) -const ( - fldReplica = "replicas" - fldSpec = "spec" -) - // Find matching replicas declarations and replace the count. // Eases the kustomization configuration of replica changes. type plugin struct { - Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"` + Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"` + FieldSpecs []config.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` } //noinspection GoUnusedGlobalVariable @@ -32,30 +30,46 @@ func (p *plugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.Replica = types.Replica{} + p.FieldSpecs = nil return yaml.Unmarshal(c, p) } func (p *plugin) Transform(m resmap.ResMap) error { - matcher := func(r resid.ResId) bool { - return r.Name == p.Replica.Name - } - - for _, r := range m.GetMatchingResourcesByOriginalId(matcher) { - kMap := r.Map() - - specInterface, ok := kMap[fldSpec] - if !ok { - return fmt.Errorf("object %s missing field %s, cannot update %s", - p.Replica.Name, fldSpec, fldReplica) - } - - if spec, ok := specInterface.(map[string]interface{}); ok { - spec[fldReplica] = p.Replica.Count - kMap[fldSpec] = spec - } else { - return fmt.Errorf("object %s has a malformed %s", p.Replica.Name, fldSpec) + for i, replicaSpec := range p.FieldSpecs { + for _, res := range m.GetMatchingResourcesByOriginalId(p.createMatcher(i)) { + err := transformers.MutateField( + res.Map(), replicaSpec.PathSlice(), + replicaSpec.CreateIfNotPresent, p.addReplicas) + if err != nil { + return err + } } } return nil } + +// Match Replica.Name and FieldSpec +func (p *plugin) createMatcher(i int) resmap.IdMatcher { + return func(r resid.ResId) bool { + return r.Name == p.Replica.Name && + r.Gvk.IsSelected(&p.FieldSpecs[i].Gvk) + } +} + +func (p *plugin) addReplicas(in interface{}) (interface{}, error) { + switch m := in.(type) { + case int64: + // Was already in the field. + case map[string]interface{}: + if len(m) != 0 { + // A map was already in the replicas field, don't want to + // discard this data silently. + return nil, fmt.Errorf("%#v is expected to be %T", in, m) + } + // Just got added, default type is map, but we can return anything. + default: + return nil, fmt.Errorf("%#v is expected to be %T", in, m) + } + return p.Replica.Count, nil +} diff --git a/plugin/builtin/replicacounttransformer/ReplicaCountTransformer_test.go b/plugin/builtin/replicacounttransformer/ReplicaCountTransformer_test.go index 3d22c30f5..1a96f1b27 100644 --- a/plugin/builtin/replicacounttransformer/ReplicaCountTransformer_test.go +++ b/plugin/builtin/replicacounttransformer/ReplicaCountTransformer_test.go @@ -24,26 +24,130 @@ apiVersion: builtin kind: ReplicaCountTransformer metadata: name: notImportantHere + replica: - name: deploy1 + name: myapp count: 23 +fieldSpecs: +- path: spec/replicas + create: true + kind: Deployment + +- path: spec/replicas + create: true + kind: ReplicationController + +- path: spec/replicas + create: true + kind: ReplicaSet + +- path: spec/replicas + create: true + kind: StatefulSet `, ` -group: apps -apiVersion: v1 +apiVersion: builtin +kind: Service +metadata: + name: myapp +spec: + ports: + - port: 1111 + targetport: 1111 +--- +apiVersion: builtin kind: Deployment metadata: - name: deploy1 + name: otherapp spec: - replicas: 1 + replicas: 5 +--- +apiVersion: builtin +kind: Deployment +metadata: + name: myapp +spec: + replicas: 5 +--- +apiVersion: builtin +kind: StatefulSet +metadata: + name: myapp +spec: + selector: + matchLabels: + app: app +--- +apiVersion: builtin +kind: ReplicaSet +metadata: + name: myapp +spec: + selector: + matchLabels: + app: app +--- +apiVersion: builtin +kind: ReplicationController +metadata: + name: myapp +spec: + selector: + matchLabels: + app: app `) th.AssertActualEqualsExpected(rm, ` -apiVersion: v1 -group: apps +apiVersion: builtin +kind: Service +metadata: + name: myapp +spec: + ports: + - port: 1111 + targetport: 1111 +--- +apiVersion: builtin kind: Deployment metadata: - name: deploy1 + name: myapp spec: replicas: 23 +--- +apiVersion: builtin +kind: Deployment +metadata: + name: otherapp +spec: + replicas: 5 +--- +apiVersion: builtin +kind: StatefulSet +metadata: + name: myapp +spec: + replicas: 23 + selector: + matchLabels: + app: app +--- +apiVersion: builtin +kind: ReplicaSet +metadata: + name: myapp +spec: + replicas: 23 + selector: + matchLabels: + app: app +--- +apiVersion: builtin +kind: ReplicationController +metadata: + name: myapp +spec: + replicas: 23 + selector: + matchLabels: + app: app `) }