-
Notifications
You must be signed in to change notification settings - Fork 9.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
kubernetes: Ignore internal K8S annotations in config_map + use PATCH #12945
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- v1.12.0-alpha20250213
- v1.11.0-rc1
- v1.11.0-beta2
- v1.11.0-beta1
- v1.11.0-alpha20250107
- v1.11.0-alpha20241218
- v1.11.0-alpha20241211
- v1.11.0-alpha20241106
- v1.10.5
- v1.10.4
- v1.10.3
- v1.10.2
- v1.10.1
- v1.10.0
- v1.10.0-rc3
- v1.10.0-rc2
- v1.10.0-rc1
- v1.10.0-beta1
- v1.10.0-alpha20241023
- v1.10.0-alpha20241009
- v1.10.0-alpha20240926
- v1.10.0-alpha20240918
- v1.10.0-alpha20240911
- v1.10.0-alpha20240828
- v1.10.0-alpha20240814
- v1.10.0-alpha20240807
- v1.10.0-alpha20240730
- v1.10.0-alpha20240717
- v1.10.0-alpha20240619
- v1.10.0-alpha20240606
- v1.9.8
- v1.9.7
- v1.9.6
- v1.9.5
- v1.9.4
- v1.9.3
- v1.9.2
- v1.9.1
- v1.9.0
- v1.9.0-rc3
- v1.9.0-rc2
- v1.9.0-rc1
- v1.9.0-beta1
- v1.9.0-alpha20240516
- v1.9.0-alpha20240501
- v1.9.0-alpha20240404
- v1.8.5
- v1.8.4
- v1.8.3
- v1.8.2
- v1.8.1
- v1.8.0
- v1.8.0-rc2
- v1.8.0-rc1
- v1.8.0-beta1
- v1.8.0-alpha20240228
- v1.8.0-alpha20240216
- v1.8.0-alpha20240214
- v1.8.0-alpha20240131
- v1.7.5
- v1.7.4
- v1.7.3
- v1.7.2
- v1.7.1
- v1.7.0
- v1.7.0-rc2
- v1.7.0-rc1
- v1.7.0-beta2
- v1.7.0-beta1
- v1.7.0-alpha20231130
- v1.7.0-alpha20231108
- v1.7.0-alpha20231025
- v1.6.6
- v1.6.5
- v1.6.4
- v1.6.3
- v1.6.2
- v1.6.1
- v1.6.0
- v1.6.0-rc1
- v1.6.0-beta3
- v1.6.0-beta2
- v1.6.0-beta1
- v1.6.0-alpha20230816
- v1.6.0-alpha20230802
- v1.6.0-alpha20230719
- v1.5.7
- v1.5.6
- v1.5.5
- v1.5.4
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.5.0-rc2
- v1.5.0-rc1
- v1.5.0-beta2
- v1.5.0-beta1
- v1.5.0-alpha20230504
- v1.5.0-alpha20230405
- v1.4.7
- v1.4.6
- v1.4.5
- v1.4.4
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
- v1.4.0-rc1
- v1.4.0-beta2
- v1.4.0-beta1
- v1.4.0-alpha20221207
- v1.4.0-alpha20221109
- v1.3.10
- v1.3.9
- v1.3.8
- v1.3.7
- v1.3.6
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.3.0-rc1
- v1.3.0-dev
- v1.3.0-beta1
- v1.3.0-alpha20220817
- v1.3.0-alpha20220803
- v1.3.0-alpha20220706
- v1.3.0-alpha20220622
- v1.3.0-alpha20220608
- v1.2.9
- v1.2.8
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.2.0-rc2
- v1.2.0-rc1
- v1.2.0-beta1
- v1.2.0-alpha-20220328
- v1.2.0-alpha20220413
- v1.1.9
- v1.1.8
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.1.0-rc1
- v1.1.0-beta2
- v1.1.0-beta1
- v1.1.0-alpha20211029
- v1.1.0-alpha20211020
- v1.1.0-alpha20211006
- v1.1.0-alpha20210922
- v1.1.0-alpha20210908
- v1.1.0-alpha20210811
- v1.1.0-alpha20210728
- v1.1.0-alpha20210714
- v1.1.0-alpha20210630
- v1.1.0-alpha20210616
- v1.0.11
- v1.0.10
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- v0.15.5
- v0.15.4
- v0.15.3
- v0.15.2
- v0.15.1
- v0.15.0
- v0.15.0-rc2
- v0.15.0-rc1
- v0.15.0-beta2
- v0.15.0-beta1
- v0.15.0-alpha20210210
- v0.15.0-alpha20210127
- v0.15.0-alpha20210107
- v0.14.11
- v0.14.10
- v0.14.9
- v0.14.8
- v0.14.7
- v0.14.6
- v0.14.5
- v0.14.4
- v0.14.3
- v0.14.2
- v0.14.1
- v0.14.0
- v0.14.0-rc1
- v0.14.0-beta2
- v0.14.0-beta1
- v0.14.0-alpha20201007
- v0.14.0-alpha20200923
- v0.14.0-alpha20200910
- v0.13.7
- v0.13.6
- v0.13.5
- v0.13.4
- v0.13.3
- v0.13.2
- v0.13.1
- v0.13.0
- v0.13.0-rc1
- v0.13.0-beta3
- v0.13.0-beta2
- v0.13.0-beta1
- v0.12.31
- v0.12.30
- v0.12.29
- v0.12.28
- v0.12.27
- v0.12.26
- v0.12.25
- v0.12.24
- v0.12.23
- v0.12.22
- v0.12.21
- v0.12.20
- v0.12.19
- v0.12.18
- v0.12.17
- v0.12.16
- v0.12.15
- v0.12.14
- v0.12.13
- v0.12.12
- v0.12.11
- v0.12.10
- v0.12.9
- v0.12.8
- v0.12.7
- v0.12.6
- v0.12.5
- v0.12.4
- v0.12.3
- v0.12.2
- v0.12.1
- v0.12.0
- v0.12.0-rc1
- v0.12.0-dev20190520H16
- v0.12.0-beta2
- v0.12.0-beta1
- v0.12.0-alpha4
- v0.12.0-alpha3
- v0.12.0-alpha2
- v0.12.0-alpha1
- v0.11.15
- v0.11.14
- v0.11.13
- v0.11.12
- v0.11.12-beta1
- v0.11.11
- v0.11.10
- v0.11.9
- v0.11.9-beta1
- v0.11.8
- v0.11.7
- v0.11.6
- v0.11.5
- v0.11.4
- v0.11.3
- v0.11.2
- v0.11.1
- v0.11.0
- v0.11.0-rc1
- v0.11.0-beta1
- v0.10.8
- v0.10.7
- v0.10.6
- v0.10.5
- v0.10.4
- v0.10.3
- v0.10.2
- v0.10.1
- v0.10.0
- v0.10.0-rc1
- v0.10.0-beta2
- v0.10.0-beta1
- v0.9.11
- v0.9.10
- v0.9.9
- v0.9.8
- v0.9.7
- v0.9.6
- v0.9.5
- v0.9.4
- v0.9.3
- v0.9.2
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package kubernetes | ||
|
||
import ( | ||
"encoding/json" | ||
"reflect" | ||
"sort" | ||
"strings" | ||
) | ||
|
||
func diffStringMap(pathPrefix string, oldV, newV map[string]interface{}) PatchOperations { | ||
ops := make([]PatchOperation, 0, 0) | ||
|
||
pathPrefix = strings.TrimRight(pathPrefix, "/") | ||
|
||
// This is suboptimal for adding whole new map from scratch | ||
// or deleting the whole map, but it's actually intention. | ||
// There may be some other map items managed outside of TF | ||
// and we don't want to touch these. | ||
|
||
for k, _ := range oldV { | ||
if _, ok := newV[k]; ok { | ||
continue | ||
} | ||
ops = append(ops, &RemoveOperation{Path: pathPrefix + "/" + k}) | ||
} | ||
|
||
for k, v := range newV { | ||
newValue := v.(string) | ||
|
||
if oldValue, ok := oldV[k].(string); ok { | ||
if oldValue == newValue { | ||
continue | ||
} | ||
|
||
ops = append(ops, &ReplaceOperation{ | ||
Path: pathPrefix + "/" + k, | ||
Value: newValue, | ||
}) | ||
continue | ||
} | ||
|
||
ops = append(ops, &AddOperation{ | ||
Path: pathPrefix + "/" + k, | ||
Value: newValue, | ||
}) | ||
} | ||
|
||
return ops | ||
} | ||
|
||
type PatchOperations []PatchOperation | ||
|
||
func (po PatchOperations) MarshalJSON() ([]byte, error) { | ||
var v []PatchOperation = po | ||
return json.Marshal(v) | ||
} | ||
|
||
func (po PatchOperations) Equal(ops []PatchOperation) bool { | ||
var v []PatchOperation = po | ||
|
||
sort.Slice(v, sortByPathAsc(ops)) | ||
sort.Slice(ops, sortByPathAsc(ops)) | ||
|
||
return reflect.DeepEqual(v, ops) | ||
} | ||
|
||
func sortByPathAsc(ops []PatchOperation) func(i, j int) bool { | ||
return func(i, j int) bool { | ||
return ops[i].GetPath() < ops[j].GetPath() | ||
} | ||
} | ||
|
||
type PatchOperation interface { | ||
MarshalJSON() ([]byte, error) | ||
GetPath() string | ||
} | ||
|
||
type ReplaceOperation struct { | ||
Path string `json:"path"` | ||
Value interface{} `json:"value"` | ||
Op string `json:"op"` | ||
} | ||
|
||
func (o *ReplaceOperation) GetPath() string { | ||
return o.Path | ||
} | ||
|
||
func (o *ReplaceOperation) MarshalJSON() ([]byte, error) { | ||
o.Op = "replace" | ||
return json.Marshal(*o) | ||
} | ||
|
||
func (o *ReplaceOperation) String() string { | ||
b, _ := o.MarshalJSON() | ||
return string(b) | ||
} | ||
|
||
type AddOperation struct { | ||
Path string `json:"path"` | ||
Value interface{} `json:"value"` | ||
Op string `json:"op"` | ||
} | ||
|
||
func (o *AddOperation) GetPath() string { | ||
return o.Path | ||
} | ||
|
||
func (o *AddOperation) MarshalJSON() ([]byte, error) { | ||
o.Op = "add" | ||
return json.Marshal(*o) | ||
} | ||
|
||
func (o *AddOperation) String() string { | ||
b, _ := o.MarshalJSON() | ||
return string(b) | ||
} | ||
|
||
type RemoveOperation struct { | ||
Path string `json:"path"` | ||
Op string `json:"op"` | ||
} | ||
|
||
func (o *RemoveOperation) GetPath() string { | ||
return o.Path | ||
} | ||
|
||
func (o *RemoveOperation) MarshalJSON() ([]byte, error) { | ||
o.Op = "remove" | ||
return json.Marshal(*o) | ||
} | ||
|
||
func (o *RemoveOperation) String() string { | ||
b, _ := o.MarshalJSON() | ||
return string(b) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package kubernetes | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestDiffStringMap(t *testing.T) { | ||
testCases := []struct { | ||
Path string | ||
Old map[string]interface{} | ||
New map[string]interface{} | ||
ExpectedOps PatchOperations | ||
}{ | ||
{ | ||
Path: "/parent/", | ||
Old: map[string]interface{}{ | ||
"one": "111", | ||
"two": "222", | ||
}, | ||
New: map[string]interface{}{ | ||
"one": "111", | ||
"two": "222", | ||
"three": "333", | ||
}, | ||
ExpectedOps: []PatchOperation{ | ||
&AddOperation{ | ||
Path: "/parent/three", | ||
Value: "333", | ||
}, | ||
}, | ||
}, | ||
{ | ||
Path: "/parent/", | ||
Old: map[string]interface{}{ | ||
"one": "111", | ||
"two": "222", | ||
}, | ||
New: map[string]interface{}{ | ||
"one": "111", | ||
"two": "abcd", | ||
}, | ||
ExpectedOps: []PatchOperation{ | ||
&ReplaceOperation{ | ||
Path: "/parent/two", | ||
Value: "abcd", | ||
}, | ||
}, | ||
}, | ||
{ | ||
Path: "/parent/", | ||
Old: map[string]interface{}{ | ||
"one": "111", | ||
"two": "222", | ||
}, | ||
New: map[string]interface{}{ | ||
"two": "abcd", | ||
"three": "333", | ||
}, | ||
ExpectedOps: []PatchOperation{ | ||
&RemoveOperation{Path: "/parent/one"}, | ||
&ReplaceOperation{ | ||
Path: "/parent/two", | ||
Value: "abcd", | ||
}, | ||
&AddOperation{ | ||
Path: "/parent/three", | ||
Value: "333", | ||
}, | ||
}, | ||
}, | ||
{ | ||
Path: "/parent/", | ||
Old: map[string]interface{}{ | ||
"one": "111", | ||
"two": "222", | ||
}, | ||
New: map[string]interface{}{ | ||
"two": "222", | ||
}, | ||
ExpectedOps: []PatchOperation{ | ||
&RemoveOperation{Path: "/parent/one"}, | ||
}, | ||
}, | ||
{ | ||
Path: "/parent/", | ||
Old: map[string]interface{}{ | ||
"one": "111", | ||
"two": "222", | ||
}, | ||
New: map[string]interface{}{}, | ||
ExpectedOps: []PatchOperation{ | ||
&RemoveOperation{Path: "/parent/one"}, | ||
&RemoveOperation{Path: "/parent/two"}, | ||
}, | ||
}, | ||
{ | ||
Path: "/parent/", | ||
Old: map[string]interface{}{}, | ||
New: map[string]interface{}{ | ||
"one": "111", | ||
"two": "222", | ||
}, | ||
ExpectedOps: []PatchOperation{ | ||
&AddOperation{ | ||
Path: "/parent/one", | ||
Value: "111", | ||
}, | ||
&AddOperation{ | ||
Path: "/parent/two", | ||
Value: "222", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for i, tc := range testCases { | ||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { | ||
ops := diffStringMap(tc.Path, tc.Old, tc.New) | ||
if !tc.ExpectedOps.Equal(ops) { | ||
t.Fatalf("Operations don't match.\nExpected: %v\nGiven: %v\n", tc.ExpectedOps, ops) | ||
} | ||
}) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
package kubernetes | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
pkgApi "k8s.io/kubernetes/pkg/api" | ||
"k8s.io/kubernetes/pkg/api/errors" | ||
api "k8s.io/kubernetes/pkg/api/v1" | ||
kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" | ||
|
@@ -73,19 +75,22 @@ func resourceKubernetesConfigMapRead(d *schema.ResourceData, meta interface{}) e | |
func resourceKubernetesConfigMapUpdate(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(*kubernetes.Clientset) | ||
|
||
metadata := expandMetadata(d.Get("metadata").([]interface{})) | ||
namespace, name := idParts(d.Id()) | ||
// This is necessary in case the name is generated | ||
metadata.Name = name | ||
|
||
cfgMap := api.ConfigMap{ | ||
ObjectMeta: metadata, | ||
Data: expandStringMap(d.Get("data").(map[string]interface{})), | ||
ops := patchMetadata("metadata.0.", "/metadata/", d) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we guaranteed to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question, I believe we are: &schema.Schema{
Type: schema.TypeList,
Required: true,
MaxItems: 1, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, LGTM |
||
if d.HasChange("data") { | ||
oldV, newV := d.GetChange("data") | ||
diffOps := diffStringMap("/data/", oldV.(map[string]interface{}), newV.(map[string]interface{})) | ||
ops = append(ops, diffOps...) | ||
} | ||
log.Printf("[INFO] Updating config map: %#v", cfgMap) | ||
out, err := conn.CoreV1().ConfigMaps(namespace).Update(&cfgMap) | ||
data, err := ops.MarshalJSON() | ||
if err != nil { | ||
return err | ||
return fmt.Errorf("Failed to marshal update operations: %s", err) | ||
} | ||
log.Printf("[INFO] Updating config map %q: %v", name, string(data)) | ||
out, err := conn.CoreV1().ConfigMaps(namespace).Patch(name, pkgApi.JSONPatchType, data) | ||
if err != nil { | ||
return fmt.Errorf("Failed to update Config Map: %s", err) | ||
} | ||
log.Printf("[INFO] Submitted updated config map: %#v", out) | ||
d.SetId(buildId(out.ObjectMeta)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit pick - should this file really be called patch_operations? It seems there are patch and replace operation funcs within :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the struct type is called
PatchOperation
and the word patch comes from PATCH HTTP method, or more specifically from JSON Patch - so I think it's the right name.I think eventually we could use these helpers cross providers, but it depends on the exact implementation of the SDK.