Skip to content

Commit

Permalink
fix: map SDK NodeAffinity operators to canonical provider values
Browse files Browse the repository at this point in the history
Before this commit, there was a difference
in how the SDK and the Terraform provider render the K8s Operator `In`.
The SDK returned uppercase `IN`, while the Terraform provider expected `In`.
This led to Terraform showing a difference in an infrastructure's state
even when there was none.

This commit maps SDK Operator values to those defined in the provider.
  • Loading branch information
vladklokun committed Aug 19, 2024
1 parent ef41130 commit fb17e56
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 5 deletions.
40 changes: 35 additions & 5 deletions castai/resource_node_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,30 @@ const (
NodeSelectorOperationLt = "Lt"
)

type nodeSelectorOperatorsSlice []string

var nodeSelectorOperators = nodeSelectorOperatorsSlice{NodeSelectorOperationIn,
NodeSelectorOperationNotIn,
NodeSelectorOperationExists,
NodeSelectorOperationDoesNot,
NodeSelectorOperationGt,
NodeSelectorOperationLt,
}

// Get returns the provider-specific representation of a given K8s selector
func (m nodeSelectorOperatorsSlice) Get(k sdk.K8sSelectorV1Operator) (string, bool) {
for _, v := range m {
if strings.EqualFold(string(k), v) {
return v, true
}
}
return "", false
}

func resourceNodeTemplate() *schema.Resource {
supportedArchitectures := []string{ArchAMD64, ArchARM64}
supportedOs := []string{OsLinux, OsWindows}
supportedSelectorOperations := []string{NodeSelectorOperationIn, NodeSelectorOperationNotIn, NodeSelectorOperationExists, NodeSelectorOperationDoesNot, NodeSelectorOperationGt, NodeSelectorOperationLt}
supportedSelectorOperations := nodeSelectorOperators

return &schema.Resource{
CreateContext: resourceNodeTemplateCreate,
Expand Down Expand Up @@ -623,7 +643,11 @@ func flattenConstraints(c *sdk.NodetemplatesV1TemplateConstraints) ([]map[string
out[FieldNodeTemplateCustomPriority] = flattenCustomPriority(*c.CustomPriority)
}
if c.DedicatedNodeAffinity != nil && len(*c.DedicatedNodeAffinity) > 0 {
out[FieldNodeTemplateDedicatedNodeAffinity] = flattenNodeAffinity(*c.DedicatedNodeAffinity)
flatNodeAffinity, err := flattenNodeAffinity(*c.DedicatedNodeAffinity)
if err != nil {
return []map[string]any{}, err
}
out[FieldNodeTemplateDedicatedNodeAffinity] = flatNodeAffinity
}
if c.InstanceFamilies != nil {
out[FieldNodeTemplateInstanceFamilies] = flattenInstanceFamilies(c.InstanceFamilies)
Expand Down Expand Up @@ -757,7 +781,8 @@ func flattenCustomPriority(priorities []sdk.NodetemplatesV1TemplateConstraintsCu
})
}

func flattenNodeAffinity(affinities []sdk.NodetemplatesV1TemplateConstraintsDedicatedNodeAffinity) any {
func flattenNodeAffinity(affinities []sdk.NodetemplatesV1TemplateConstraintsDedicatedNodeAffinity) (any, error) {
var err error
return lo.Map(affinities, func(item sdk.NodetemplatesV1TemplateConstraintsDedicatedNodeAffinity, index int) map[string]any {
result := map[string]any{}
if item.InstanceTypes != nil {
Expand All @@ -768,17 +793,22 @@ func flattenNodeAffinity(affinities []sdk.NodetemplatesV1TemplateConstraintsDedi
result[FieldNodeTemplateAzName] = lo.FromPtr(item.AzName)

if item.Affinity != nil && len(*item.Affinity) > 0 {

result[FieldNodeTemplateAffinityName] = lo.Map(*item.Affinity, func(affinity sdk.K8sSelectorV1KubernetesNodeAffinity, index int) map[string]any {
affinityOperator, ok := nodeSelectorOperators.Get(affinity.Operator)
if !ok {
err = fmt.Errorf("found unknown node selector operator: %q", affinity.Operator)
}
return map[string]any{
FieldNodeTemplateAffinityKeyName: affinity.Key,
FieldNodeTemplateAffinityOperatorName: affinity.Operator,
FieldNodeTemplateAffinityOperatorName: affinityOperator,
FieldNodeTemplateAffinityValuesName: affinity.Values,
}
})
}

return result
})
}), err
}

func resourceNodeTemplateDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
Expand Down
50 changes: 50 additions & 0 deletions castai/resource_node_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,56 @@ Tainted = false
)
}

func Test_flattenNodeAffinity(t *testing.T) {
makeSDKNodeAffinityWithOperator := func(op string) []sdk.NodetemplatesV1TemplateConstraintsDedicatedNodeAffinity {
return []sdk.NodetemplatesV1TemplateConstraintsDedicatedNodeAffinity{
{
Affinity: &[]sdk.K8sSelectorV1KubernetesNodeAffinity{{
Key: "kubernetes.io/os",
Operator: sdk.K8sSelectorV1Operator(op),
Values: []string{"linux"},
}},
AzName: lo.ToPtr("us-central1-c"),
InstanceTypes: &[]string{"e2"},
Name: lo.ToPtr("linux-only"),
},
}
}

makeMappedNodeAffinityWithOperator := func(op string) []map[string]any {
wantNA := []map[string]any{
{
FieldNodeTemplateInstanceTypes: []string{"e2"},
FieldNodeTemplateAzName: "us-central1-c",
FieldNodeTemplateName: "linux-only",
FieldNodeTemplateAffinityName: []map[string]any{
{
FieldNodeTemplateAffinityKeyName: "kubernetes.io/os",
FieldNodeTemplateAffinityOperatorName: op,
FieldNodeTemplateAffinityValuesName: []string{"linux"},
},
},
},
}
return wantNA
}

for _, canonical := range nodeSelectorOperators {
testedVariants := []string{canonical, strings.ToLower(canonical), strings.ToUpper(canonical)}
for _, variant := range testedVariants {
name := fmt.Sprintf("should map %q to %q", variant, canonical)
t.Run(name, func(t *testing.T) {
r := require.New(t)
input := makeSDKNodeAffinityWithOperator(variant)
want := makeMappedNodeAffinityWithOperator(canonical)

got, _ := flattenNodeAffinity(input)
r.Equal(want, got)
})
}
}
}

func TestNodeTemplateResourceReadContextEmptyList(t *testing.T) {
r := require.New(t)
mockctrl := gomock.NewController(t)
Expand Down

0 comments on commit fb17e56

Please sign in to comment.