Skip to content

Commit d5ff12c

Browse files
pdecatalexsomesan
authored andcommitted
Add pod metadata to replication controller spec template (#193)
* Add pod metadata to replication controller spec template * Mark affected attributes as deprecated to allow for a migration path
1 parent bd7821f commit d5ff12c

10 files changed

+1413
-747
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
IMPROVEMENTS:
44

55
* `resource/kubernetes_deployment`, `resource/kubernetes_pod`, `resource/kubernetes_replication_controller`, `resource/kubernetes_stateful_set`: Add `allow_privilege_escalation` to container security contexts attributes ([#249](https://github.com/terraform-providers/terraform-provider-kubernetes/issues/249))
6+
* Add pod metadata to replication controller spec template ([#193](https://github.com/terraform-providers/terraform-provider-kubernetes/issues/193))
67

78
BUG FIXES:
89

kubernetes/resource_kubernetes_deployment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func resourceKubernetesDeployment() *schema.Resource {
172172
Required: true,
173173
MaxItems: 1,
174174
Elem: &schema.Resource{
175-
Schema: podSpecFields(true),
175+
Schema: podSpecFields(true, false, false),
176176
},
177177
},
178178
},

kubernetes/resource_kubernetes_pod.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func resourceKubernetesPod() *schema.Resource {
3838
Required: true,
3939
MaxItems: 1,
4040
Elem: &schema.Resource{
41-
Schema: podSpecFields(false),
41+
Schema: podSpecFields(false, false, false),
4242
},
4343
},
4444
},

kubernetes/resource_kubernetes_replication_controller.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func resourceKubernetesReplicationController() *schema.Resource {
6363
Required: true,
6464
MaxItems: 1,
6565
Elem: &schema.Resource{
66-
Schema: podSpecFields(true),
66+
Schema: replicationControllerTemplateFieldSpec(),
6767
},
6868
},
6969
},
@@ -73,11 +73,48 @@ func resourceKubernetesReplicationController() *schema.Resource {
7373
}
7474
}
7575

76+
func replicationControllerTemplateFieldSpec() map[string]*schema.Schema {
77+
metadata := namespacedMetadataSchema("replication controller's template", true)
78+
// TODO: make this required once the legacy fields are removed
79+
metadata.Computed = true
80+
metadata.Required = false
81+
metadata.Optional = true
82+
83+
templateFields := map[string]*schema.Schema{
84+
"metadata": metadata,
85+
"spec": {
86+
Type: schema.TypeList,
87+
Description: "Spec of the pods managed by the replication controller",
88+
Optional: true, // TODO: make this required once the legacy fields are removed
89+
Computed: true,
90+
MaxItems: 1,
91+
Elem: &schema.Resource{
92+
Schema: podSpecFields(false, false, true),
93+
},
94+
},
95+
}
96+
97+
// Merge deprecated fields and mark them conflicting with the ones to avoid complex mixed use-cases
98+
for k, v := range podSpecFields(true, true, true) {
99+
v.ConflictsWith = []string{"spec.0.template.0.spec", "spec.0.template.0.metadata"}
100+
templateFields[k] = v
101+
}
102+
103+
return templateFields
104+
}
105+
106+
func useDeprecatedSpecFields(d *schema.ResourceData) (deprecatedSpecFieldsExist bool) {
107+
// Check which replication controller template spec fields are used
108+
_, deprecatedSpecFieldsExist = d.GetOkExists("spec.0.template.0.container.0.name")
109+
return
110+
}
111+
76112
func resourceKubernetesReplicationControllerCreate(d *schema.ResourceData, meta interface{}) error {
77113
conn := meta.(*kubernetes.Clientset)
78114

79115
metadata := expandMetadata(d.Get("metadata").([]interface{}))
80-
spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{}))
116+
117+
spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{}), useDeprecatedSpecFields(d))
81118
if err != nil {
82119
return err
83120
}
@@ -135,7 +172,7 @@ func resourceKubernetesReplicationControllerRead(d *schema.ResourceData, meta in
135172
return err
136173
}
137174

138-
spec, err := flattenReplicationControllerSpec(rc.Spec)
175+
spec, err := flattenReplicationControllerSpec(rc.Spec, useDeprecatedSpecFields(d))
139176
if err != nil {
140177
return err
141178
}
@@ -159,7 +196,7 @@ func resourceKubernetesReplicationControllerUpdate(d *schema.ResourceData, meta
159196
ops := patchMetadata("metadata.0.", "/metadata/", d)
160197

161198
if d.HasChange("spec") {
162-
spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{}))
199+
spec, err := expandReplicationControllerSpec(d.Get("spec").([]interface{}), useDeprecatedSpecFields(d))
163200
if err != nil {
164201
return err
165202
}

kubernetes/resource_kubernetes_replication_controller_deprecated_test.go

Lines changed: 887 additions & 0 deletions
Large diffs are not rendered by default.

kubernetes/resource_kubernetes_replication_controller_test.go

Lines changed: 320 additions & 196 deletions
Large diffs are not rendered by default.

kubernetes/schema_pod_spec.go

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,86 @@ import (
44
"github.com/hashicorp/terraform/helper/schema"
55
)
66

7-
func podSpecFields(isUpdatable bool) map[string]*schema.Schema {
7+
func podSpecFields(isUpdatable, isDeprecated, isComputed bool) map[string]*schema.Schema {
8+
var deprecatedMessage string
9+
if isDeprecated {
10+
deprecatedMessage = "This field is deprecated because template was incorrectly defined as a PodSpec preventing the definition of metadata. Please use the one under the spec field"
11+
}
812
s := map[string]*schema.Schema{
913
"active_deadline_seconds": {
1014
Type: schema.TypeInt,
1115
Optional: true,
16+
Computed: isComputed,
1217
ValidateFunc: validatePositiveInteger,
1318
Description: "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.",
19+
Deprecated: deprecatedMessage,
1420
},
1521
"container": {
1622
Type: schema.TypeList,
1723
Optional: true,
24+
Computed: isComputed,
1825
Description: "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers",
26+
Deprecated: deprecatedMessage,
1927
Elem: &schema.Resource{
2028
Schema: containerFields(isUpdatable, false),
2129
},
2230
},
2331
"init_container": {
2432
Type: schema.TypeList,
2533
Optional: true,
34+
Computed: isComputed,
2635
ForceNew: true,
2736
Description: "List of init containers belonging to the pod. Init containers always run to completion and each must complete successfully before the next is started. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/",
37+
Deprecated: deprecatedMessage,
2838
Elem: &schema.Resource{
2939
Schema: containerFields(isUpdatable, true),
3040
},
3141
},
3242
"dns_policy": {
3343
Type: schema.TypeString,
3444
Optional: true,
35-
Default: "ClusterFirst",
45+
Computed: isComputed,
46+
DefaultFunc: defaultIfNotComputed(isComputed, "ClusterFirst"),
3647
Description: "Set DNS policy for containers within the pod. One of 'ClusterFirst' or 'Default'. Defaults to 'ClusterFirst'.",
48+
Deprecated: deprecatedMessage,
3749
},
3850
"host_ipc": {
3951
Type: schema.TypeBool,
4052
Optional: true,
41-
Default: false,
53+
Computed: isComputed,
54+
DefaultFunc: defaultIfNotComputed(isComputed, false),
4255
Description: "Use the host's ipc namespace. Optional: Default to false.",
56+
Deprecated: deprecatedMessage,
4357
},
4458
"host_network": {
4559
Type: schema.TypeBool,
4660
Optional: true,
47-
Default: false,
61+
Computed: isComputed,
62+
DefaultFunc: defaultIfNotComputed(isComputed, false),
4863
Description: "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified.",
64+
Deprecated: deprecatedMessage,
4965
},
5066

5167
"host_pid": {
5268
Type: schema.TypeBool,
5369
Optional: true,
54-
Default: false,
70+
Computed: isComputed,
71+
DefaultFunc: defaultIfNotComputed(isComputed, false),
5572
Description: "Use the host's pid namespace.",
73+
Deprecated: deprecatedMessage,
5674
},
5775

5876
"hostname": {
5977
Type: schema.TypeString,
6078
Optional: true,
6179
Computed: true,
6280
Description: "Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value.",
81+
Deprecated: deprecatedMessage,
6382
},
6483
"image_pull_secrets": {
6584
Type: schema.TypeList,
6685
Description: "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: http://kubernetes.io/docs/user-guide/images#specifying-imagepullsecrets-on-a-pod",
86+
Deprecated: deprecatedMessage,
6787
Optional: true,
6888
Computed: true,
6989
Elem: &schema.Resource{
@@ -81,23 +101,30 @@ func podSpecFields(isUpdatable bool) map[string]*schema.Schema {
81101
Optional: true,
82102
Computed: true,
83103
Description: "NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.",
104+
Deprecated: deprecatedMessage,
84105
},
85106
"node_selector": {
86107
Type: schema.TypeMap,
87108
Optional: true,
109+
Computed: isComputed,
88110
Description: "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: http://kubernetes.io/docs/user-guide/node-selection.",
111+
Deprecated: deprecatedMessage,
89112
},
90113
"restart_policy": {
91114
Type: schema.TypeString,
92115
Optional: true,
93-
Default: "Always",
116+
Computed: isComputed,
117+
DefaultFunc: defaultIfNotComputed(isComputed, "Always"),
94118
Description: "Restart policy for all containers within the pod. One of Always, OnFailure, Never. More info: http://kubernetes.io/docs/user-guide/pod-states#restartpolicy.",
119+
Deprecated: deprecatedMessage,
95120
},
96121
"security_context": {
97122
Type: schema.TypeList,
98123
Optional: true,
124+
Computed: isComputed,
99125
MaxItems: 1,
100126
Description: "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty",
127+
Deprecated: deprecatedMessage,
101128
Elem: &schema.Resource{
102129
Schema: map[string]*schema.Schema{
103130
"fs_group": {
@@ -140,24 +167,31 @@ func podSpecFields(isUpdatable bool) map[string]*schema.Schema {
140167
Optional: true,
141168
Computed: true,
142169
Description: "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: http://releases.k8s.io/HEAD/docs/design/service_accounts.md.",
170+
Deprecated: deprecatedMessage,
143171
},
144172
"subdomain": {
145173
Type: schema.TypeString,
146174
Optional: true,
175+
Computed: isComputed,
147176
Description: `If specified, the fully qualified Pod hostname will be "...svc.". If not specified, the pod will not have a domainname at all..`,
177+
Deprecated: deprecatedMessage,
148178
},
149179
"termination_grace_period_seconds": {
150180
Type: schema.TypeInt,
151181
Optional: true,
152-
Default: 30,
182+
Computed: isComputed,
183+
DefaultFunc: defaultIfNotComputed(isComputed, 30),
153184
ValidateFunc: validateTerminationGracePeriodSeconds,
154185
Description: "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process.",
186+
Deprecated: deprecatedMessage,
155187
},
156188

157189
"volume": {
158190
Type: schema.TypeList,
159191
Optional: true,
192+
Computed: isComputed,
160193
Description: "List of volumes that can be mounted by containers belonging to the pod. More info: http://kubernetes.io/docs/user-guide/volumes",
194+
Deprecated: deprecatedMessage,
161195
Elem: volumeSchema(),
162196
},
163197
}
@@ -179,6 +213,16 @@ func podSpecFields(isUpdatable bool) map[string]*schema.Schema {
179213
return s
180214
}
181215

216+
func defaultIfNotComputed(isComputed bool, defaultValue interface{}) schema.SchemaDefaultFunc {
217+
return func() (interface{}, error) {
218+
if isComputed {
219+
return nil, nil
220+
}
221+
222+
return defaultValue, nil
223+
}
224+
}
225+
182226
func volumeSchema() *schema.Resource {
183227
v := commonVolumeSources()
184228

kubernetes/schema_pod_template.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func podTemplateFields(isUpdatable bool) map[string]*schema.Schema {
1313
Optional: true,
1414
MaxItems: 1,
1515
Elem: &schema.Resource{
16-
Schema: podSpecFields(false),
16+
Schema: podSpecFields(false, false, false),
1717
},
1818
},
1919
}

kubernetes/structures_replication_controller.go

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,48 @@
11
package kubernetes
22

33
import (
4+
"errors"
5+
46
"k8s.io/api/core/v1"
5-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
67
)
78

8-
func flattenReplicationControllerSpec(in v1.ReplicationControllerSpec) ([]interface{}, error) {
9+
func flattenReplicationControllerSpec(in v1.ReplicationControllerSpec, useDeprecatedSpecFields bool) ([]interface{}, error) {
910
att := make(map[string]interface{})
1011
att["min_ready_seconds"] = in.MinReadySeconds
1112

1213
if in.Replicas != nil {
1314
att["replicas"] = *in.Replicas
1415
}
1516

16-
att["selector"] = in.Selector
17-
podSpec, err := flattenPodSpec(in.Template.Spec)
18-
if err != nil {
19-
return nil, err
17+
if in.Selector != nil {
18+
att["selector"] = in.Selector
19+
}
20+
21+
if in.Template != nil {
22+
podSpec, err := flattenPodSpec(in.Template.Spec)
23+
if err != nil {
24+
return nil, err
25+
}
26+
template := make(map[string]interface{})
27+
28+
if useDeprecatedSpecFields {
29+
// Use deprecated fields
30+
for k, v := range podSpec[0].(map[string]interface{}) {
31+
template[k] = v
32+
}
33+
} else {
34+
// Use new fields
35+
template["spec"] = podSpec
36+
template["metadata"] = flattenMetadata(in.Template.ObjectMeta)
37+
}
38+
39+
att["template"] = []interface{}{template}
2040
}
21-
att["template"] = podSpec
2241

2342
return []interface{}{att}, nil
2443
}
2544

26-
func expandReplicationControllerSpec(rc []interface{}) (*v1.ReplicationControllerSpec, error) {
45+
func expandReplicationControllerSpec(rc []interface{}, useDeprecatedSpecFields bool) (*v1.ReplicationControllerSpec, error) {
2746
obj := &v1.ReplicationControllerSpec{}
2847
if len(rc) == 0 || rc[0] == nil {
2948
return obj, nil
@@ -32,15 +51,49 @@ func expandReplicationControllerSpec(rc []interface{}) (*v1.ReplicationControlle
3251
obj.MinReadySeconds = int32(in["min_ready_seconds"].(int))
3352
obj.Replicas = ptrToInt32(int32(in["replicas"].(int)))
3453
obj.Selector = expandStringMap(in["selector"].(map[string]interface{}))
35-
podSpec, err := expandPodSpec(in["template"].([]interface{}))
54+
55+
template, err := expandReplicationControllerTemplate(in["template"].([]interface{}), obj.Selector, useDeprecatedSpecFields)
3656
if err != nil {
3757
return obj, err
3858
}
39-
obj.Template = &v1.PodTemplateSpec{
40-
ObjectMeta: metav1.ObjectMeta{
41-
Labels: obj.Selector,
42-
},
43-
Spec: *podSpec,
59+
60+
obj.Template = template
61+
62+
return obj, nil
63+
}
64+
65+
func expandReplicationControllerTemplate(rct []interface{}, selector map[string]string, useDeprecatedSpecFields bool) (*v1.PodTemplateSpec, error) {
66+
obj := &v1.PodTemplateSpec{}
67+
68+
if useDeprecatedSpecFields {
69+
// Add labels from selector to ensure proper selection of pods by the replication controller for deprecated use case
70+
obj.ObjectMeta.Labels = selector
71+
72+
// Get pod spec from deprecated fields
73+
podSpecDeprecated, err := expandPodSpec(rct)
74+
if err != nil {
75+
return obj, err
76+
}
77+
obj.Spec = *podSpecDeprecated
78+
} else {
79+
in := rct[0].(map[string]interface{})
80+
metadata := in["metadata"].([]interface{})
81+
82+
// Return an error if new spec fields are used but no metadata is defined to preserve the Required property of the metadata field
83+
// cf. https://www.terraform.io/docs/extend/best-practices/deprecations.html#renaming-a-required-attribute
84+
if len(metadata) < 1 {
85+
return obj, errors.New("`spec.template.metadata` is Required when new 'spec.template.spec' fields are used.")
86+
}
87+
88+
// Get user defined metadata
89+
obj.ObjectMeta = expandMetadata(metadata)
90+
91+
// Get pod spec from new fields
92+
podSpec, err := expandPodSpec(in["spec"].([]interface{}))
93+
if err != nil {
94+
return obj, err
95+
}
96+
obj.Spec = *podSpec
4497
}
4598

4699
return obj, nil

0 commit comments

Comments
 (0)