From 2508ab07fe6a4923e053f6cf7003214562a397ba Mon Sep 17 00:00:00 2001 From: Tiberiu Gal Date: Mon, 25 Sep 2023 10:45:12 +0300 Subject: [PATCH 1/2] feat: add evictor advanced config resource --- Makefile | 8 +- castai/provider.go | 1 + castai/resource_eviction_config.go | 579 ++++++++++++++++++ castai/resource_eviction_config_test.go | 410 +++++++++++++ castai/sdk/api.gen.go | 159 ++++- castai/sdk/client.gen.go | 271 ++++++++ castai/sdk/mock/client.go | 105 ++++ docs/resources/evictor_advanced_config.md | 97 +++ .../gke/evictor_advanced_config/README.MD | 28 + .../gke/evictor_advanced_config/castai.tf | 39 ++ .../gke/evictor_advanced_config/variables.tf | 44 ++ .../gke_cluster_zonal_autoscaler/version.tf | 2 +- 12 files changed, 1730 insertions(+), 13 deletions(-) create mode 100644 castai/resource_eviction_config.go create mode 100644 castai/resource_eviction_config_test.go create mode 100644 docs/resources/evictor_advanced_config.md create mode 100644 examples/gke/evictor_advanced_config/README.MD create mode 100644 examples/gke/evictor_advanced_config/castai.tf create mode 100644 examples/gke/evictor_advanced_config/variables.tf diff --git a/Makefile b/Makefile index 0db9fd99..55518194 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,12 @@ + + default: build init-examples: @echo "==> Creating symlinks for example/ projects to terraform-provider-castai binary"; \ TF_PROVIDER_FILENAME=terraform-provider-castai; \ - GOOS=`go tool dist env | awk -F'=' '/^GOOS/ { print $$2}' | tr -d '"'`; \ - GOARCH=`go tool dist env | awk -F'=' '/^GOARCH/ { print $$2}' | tr -d '"'`; \ + GOOS=`go tool dist env | awk -F'=' '/^GOOS/ { print $$2}' | tr -d '";'`; \ + GOARCH=`go tool dist env | awk -F'=' '/^GOARCH/ { print $$2}' | tr -d '";'`; \ for examples in examples/eks examples/gke examples/aks ; do \ for tfproject in $$examples/* ; do \ TF_PROJECT_PLUGIN_PATH="$${tfproject}/terraform.d/plugins/registry.terraform.io/castai/castai/0.0.0-local/$${GOOS}_$${GOARCH}"; \ @@ -16,7 +18,7 @@ init-examples: generate-sdk: @echo "==> Generating castai sdk client" - @API_TAGS=ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI,OperationsAPI go generate castai/sdk/generate.go + @API_TAGS=ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI,OperationsAPI,EvictorAPI go generate castai/sdk/generate.go # The following command also rewrites existing documentation generate-docs: diff --git a/castai/provider.go b/castai/provider.go index a6c87cc0..5c807b46 100644 --- a/castai/provider.go +++ b/castai/provider.go @@ -39,6 +39,7 @@ func Provider(version string) *schema.Provider { "castai_gke_cluster": resourceGKECluster(), "castai_aks_cluster": resourceAKSCluster(), "castai_autoscaler": resourceAutoscaler(), + "castai_evictor_advanced_config": resourceEvictionConfig(), "castai_node_template": resourceNodeTemplate(), "castai_rebalancing_schedule": resourceRebalancingSchedule(), "castai_rebalancing_job": resourceRebalancingJob(), diff --git a/castai/resource_eviction_config.go b/castai/resource_eviction_config.go new file mode 100644 index 00000000..27494e10 --- /dev/null +++ b/castai/resource_eviction_config.go @@ -0,0 +1,579 @@ +package castai + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/castai/terraform-provider-castai/castai/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/samber/lo" + "log" + "time" +) + +const ( + FieldEvictorAdvancedConfig = "evictor_advanced_config" + FieldEvictionConfig = "eviction_config" + FieldNodeSelector = "node_selector" + FieldPodSelector = "pod_selector" + FieldEvictionSettings = "settings" + FieldEvictionOptionDisabled = "removal_disabled" + FieldEvictionOptionAggressive = "aggressive" + FieldEvictionOptionDisposable = "disposable" + FieldPodSelectorKind = "kind" + FieldPodSelectorNamespace = "namespace" + FieldMatchLabels = "match_labels" + FieldMatchExpressions = "match_expressions" + FieldMatchExpressionKey = "key" + FieldMatchExpressionOp = "operator" + FieldMatchExpressionVal = "values" +) + +func resourceEvictionConfig() *schema.Resource { + return &schema.Resource{ + ReadContext: resourceEvictionConfigRead, + CreateContext: resourceEvictionConfigCreate, + UpdateContext: resourceEvictionConfigUpdate, + DeleteContext: resourceEvictionConfigDelete, + Description: "CAST AI eviction config resource to manage evictor properties ", + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(2 * time.Minute), + Update: schema.DefaultTimeout(2 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + FieldClusterId: { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsUUID), + Description: "CAST AI cluster id.", + }, + FieldEvictorAdvancedConfig: { + Type: schema.TypeList, + Description: "evictor advanced configuration to target specific node/pod", + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + FieldPodSelector: { + Type: schema.TypeList, + Description: "pod selector", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + FieldPodSelectorNamespace: { + Type: schema.TypeString, + Optional: true, + }, + FieldPodSelectorKind: { + Type: schema.TypeString, + Optional: true, + }, + FieldMatchLabels: { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + FieldMatchExpressions: { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + FieldMatchExpressionKey: {Type: schema.TypeString, Required: true}, + FieldMatchExpressionOp: {Type: schema.TypeString, Required: true}, + FieldMatchExpressionVal: { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + FieldNodeSelector: { + Type: schema.TypeList, + Description: "node selector", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + FieldMatchLabels: { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + FieldMatchExpressions: { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + FieldMatchExpressionKey: {Type: schema.TypeString, Required: true}, + FieldMatchExpressionOp: {Type: schema.TypeString, Required: true}, + FieldMatchExpressionVal: { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + FieldEvictionOptionDisabled: { + Type: schema.TypeBool, + Optional: true, + }, + FieldEvictionOptionAggressive: { + Type: schema.TypeBool, + Optional: true, + }, + FieldEvictionOptionDisposable: { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func resourceEvictionConfigRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + err := readAdvancedEvictorConfig(ctx, data, meta) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func readAdvancedEvictorConfig(ctx context.Context, data *schema.ResourceData, meta interface{}) error { + clusterId := getClusterId(data) + if clusterId == "" { + log.Print("[INFO] ClusterId is missing. Will skip operation.") + return nil + } + client := meta.(*ProviderConfig).api + + resp, err := client.EvictorAPIGetAdvancedConfigWithResponse(ctx, clusterId) + if err != nil { + log.Printf("[ERROR] Failed to set read evictor advanced config: %v", err) + return err + } + err = data.Set(FieldEvictorAdvancedConfig, flattenEvictionConfig(resp.JSON200.EvictionConfig)) + if err != nil { + log.Printf("[ERROR] Failed to set field: %v", err) + return err + } + + return nil +} + +func resourceEvictionConfigCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + if err := upsertEvictionConfigs(ctx, data, meta); err != nil { + return diag.FromErr(err) + } + + data.SetId(getClusterId(data)) + return nil +} + +func getEvictorAdvancedConfigAsJson(data *schema.ResourceData) ([]byte, error) { + eac, ok := data.GetOk(FieldEvictorAdvancedConfig) + if !ok { + return nil, fmt.Errorf("failed to extract evictor advanced config [%v], [%+v]", eac, data.GetRawState()) + } + + evictionConfigs, err := toEvictionConfig(eac) + if err != nil { + return nil, err + } + ccd := sdk.CastaiEvictorV1AdvancedConfig{EvictionConfig: evictionConfigs} + return json.Marshal(ccd) +} + +func upsertEvictionConfigs(ctx context.Context, data *schema.ResourceData, meta interface{}) error { + clusterId := getClusterId(data) + if clusterId == "" { + log.Print("[INFO] ClusterId is missing. Will skip operation.") + return nil + } + evictorAdvancedConfigJson, err := getEvictorAdvancedConfigAsJson(data) + if err != nil { + log.Printf("[ERROR] Failed to extract evictor advanced config: %v", err) + return err + } + client := meta.(*ProviderConfig).api + resp, err := client.EvictorAPIUpsertAdvancedConfigWithBodyWithResponse( + ctx, + clusterId, + "application/json", + bytes.NewReader(evictorAdvancedConfigJson), + ) + if err != nil || resp.JSON200 == nil { + log.Printf("[ERROR] Failed to upsert evictor advanced config: %v", err) + return err + } + err = data.Set(FieldEvictorAdvancedConfig, flattenEvictionConfig(resp.JSON200.EvictionConfig)) + if err != nil { + log.Printf("[ERROR] Failed to set field: %v", err) + return err + } + + return nil +} + +func resourceEvictionConfigUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + err := upsertEvictionConfigs(ctx, data, meta) + if err != nil { + return diag.FromErr(err) + } + + data.SetId(getClusterId(data)) + return nil +} + +func resourceEvictionConfigDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + err := deleteEvictionConfigs(ctx, data, meta) + if err != nil { + return diag.FromErr(err) + } + + data.SetId(getClusterId(data)) + return nil +} + +func deleteEvictionConfigs(ctx context.Context, data *schema.ResourceData, meta interface{}) error { + + clusterId := getClusterId(data) + if clusterId == "" { + log.Print("[INFO] ClusterId is missing. Will skip operation.") + return nil + } + client := meta.(*ProviderConfig).api + resp, err := client.EvictorAPIUpsertAdvancedConfigWithBodyWithResponse( + ctx, + clusterId, + "application/json", + bytes.NewReader([]byte("{}")), + ) + if err != nil || resp.JSON200 == nil { + log.Printf("[ERROR] Failed to upsert evictor advanced config: %v", err) + return err + } + err = data.Set(FieldEvictorAdvancedConfig, flattenEvictionConfig(resp.JSON200.EvictionConfig)) + if err != nil { + log.Printf("[ERROR] Failed to set field: %v", err) + return err + } + + return nil +} + +func toEvictionConfig(ii interface{}) ([]sdk.CastaiEvictorV1EvictionConfig, error) { + in, ok := ii.([]interface{}) + if !ok { + return nil, fmt.Errorf("expecting []interface, got %T", ii) + } + if len(in) < 1 { + return nil, nil + } + out := make([]sdk.CastaiEvictorV1EvictionConfig, len(in)) + var err error + for i, c := range in { + + ec := sdk.CastaiEvictorV1EvictionConfig{} + cc, ok := c.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("mapping evictionConfig expecting map[string]interface, got %T, %+v", c, c) + } + + for k, v := range cc { + + switch k { + case FieldPodSelector: + ec.PodSelector, err = toPodSelector(v) + if err != nil { + return nil, err + } + case FieldNodeSelector: + ec.NodeSelector, err = toNodeSelector(v) + if err != nil { + return nil, err + } + case FieldEvictionOptionAggressive: + enabled, ok := v.(bool) + if !ok { + return nil, fmt.Errorf("mapping eviction aggressive expecing bool, got %T, %+v", v, v) + } + if enabled { + ec.Settings.Aggressive = &sdk.CastaiEvictorV1EvictionSettingsSettingEnabled{Enabled: enabled} + + } + case FieldEvictionOptionDisabled: + enabled, ok := v.(bool) + if !ok { + return nil, fmt.Errorf("mapping eviction disabled expecing bool, got %T, %+v", v, v) + } + if enabled { + ec.Settings.RemovalDisabled = &sdk.CastaiEvictorV1EvictionSettingsSettingEnabled{Enabled: enabled} + } + case FieldEvictionOptionDisposable: + enabled, ok := v.(bool) + if !ok { + return nil, fmt.Errorf("mapping eviction aggressive expecing bool, got %T, %+v", v, v) + } + if enabled { + ec.Settings.Disposable = &sdk.CastaiEvictorV1EvictionSettingsSettingEnabled{Enabled: enabled} + } + default: + return nil, fmt.Errorf("unexpected field %s, %T, %+v", k, v, v) + } + } + out[i] = ec + } + return out, nil +} +func flattenEvictionConfig(ecs []sdk.CastaiEvictorV1EvictionConfig) []map[string]any { + if ecs == nil { + return nil + } + res := make([]map[string]any, len(ecs)) + for i, c := range ecs { + out := map[string]any{} + if c.PodSelector != nil { + out[FieldPodSelector] = flattenPodSelector(c.PodSelector) + } + if c.NodeSelector != nil { + out[FieldNodeSelector] = flattenNodeSelector(c.NodeSelector) + } + if c.Settings.Aggressive != nil { + out[FieldEvictionOptionAggressive] = c.Settings.Aggressive.Enabled + } + + if c.Settings.Disposable != nil { + out[FieldEvictionOptionDisposable] = c.Settings.Disposable.Enabled + } + + if c.Settings.RemovalDisabled != nil { + out[FieldEvictionOptionDisabled] = c.Settings.RemovalDisabled.Enabled + } + res[i] = out + } + + return res +} + +func toPodSelector(in interface{}) (*sdk.CastaiEvictorV1PodSelector, error) { + iii, ok := in.([]interface{}) + if !ok { + return nil, fmt.Errorf("mapping podselector expecting []interface, got %T, %+v", in, in) + } + if len(iii) < 1 { + return nil, nil + } + ii := iii[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("mapping podselector expecting map[string]interface, got %T, %+v", in, in) + } + out := sdk.CastaiEvictorV1PodSelector{} + for k, v := range ii { + switch k { + case FieldPodSelectorKind: + if kind, ok := v.(string); ok { + out.Kind = lo.ToPtr(kind) + } else { + return nil, fmt.Errorf("expecting bool, got %T", v) + } + case FieldPodSelectorNamespace: + if namespace, ok := v.(string); ok { + if len(namespace) == 0 { + continue + } + out.Namespace = lo.ToPtr(namespace) + } else { + return nil, fmt.Errorf("expecting bool, got %T", v) + } + case FieldMatchExpressions: + if mes, ok := v.([]interface{}); ok { + me, err := toMatchExpressions(mes) + if err != nil { + return nil, err + } + if len(me) < 1 { + continue + } + out.LabelSelector.MatchExpressions = &me + } else { + return nil, fmt.Errorf("mapping match_expressions expecting map[string]interface, got %T, %+v", v, v) + } + case FieldMatchLabels: + mls, err := toMatchLabels(v) + if err != nil { + return nil, err + } + + out.LabelSelector.MatchLabels = mls + } + } + return &out, nil +} + +func toNodeSelector(in interface{}) (*sdk.CastaiEvictorV1NodeSelector, error) { + iii, ok := in.([]interface{}) + if !ok { + return nil, fmt.Errorf("mapping nodeselector expecting []interface, got %T, %+v", in, in) + } + if len(iii) < 1 { + return nil, nil + } + ii := iii[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("mapping podselector expecting map[string]interface, got %T, %+v", in, in) + } + out := sdk.CastaiEvictorV1NodeSelector{} + for k, v := range ii { + switch k { + + case FieldMatchExpressions: + if mes, ok := v.([]interface{}); ok { + me, err := toMatchExpressions(mes) + if err != nil { + return nil, err + } + out.LabelSelector.MatchExpressions = &me + } else { + return nil, fmt.Errorf("mapping match_expressions expecting map[string]interface, got %T, %+v", v, v) + } + case FieldMatchLabels: + mls, err := toMatchLabels(v) + if err != nil { + return nil, err + } + out.LabelSelector.MatchLabels = mls + } + } + return &out, nil +} + +func toMatchLabels(in interface{}) (*sdk.CastaiEvictorV1LabelSelector_MatchLabels, error) { + mls, ok := in.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("mapping match_labels expecting map[string]interface, got %T %+v", in, in) + } + if len(mls) == 0 { + return nil, nil + } + out := sdk.CastaiEvictorV1LabelSelector_MatchLabels{AdditionalProperties: map[string]string{}} + for k, v := range mls { + value, ok := v.(string) + if !ok { + return nil, fmt.Errorf("mapping match_labels expecting string, got %T %+v", v, v) + } + out.AdditionalProperties[k] = value + } + + return &out, nil +} + +func flattenPodSelector(ps *sdk.CastaiEvictorV1PodSelector) []map[string]any { + if ps == nil { + return nil + } + out := map[string]any{} + if ps.Kind != nil { + out[FieldPodSelectorKind] = *ps.Kind + } + if ps.Namespace != nil { + out[FieldPodSelectorNamespace] = *ps.Namespace + } + if ps.LabelSelector.MatchLabels != nil { + out[FieldMatchLabels] = ps.LabelSelector.MatchLabels.AdditionalProperties + } + if ps.LabelSelector.MatchExpressions != nil { + out[FieldMatchExpressions] = flattenMatchExpressions(*ps.LabelSelector.MatchExpressions) + } + return []map[string]any{out} +} + +func flattenNodeSelector(ns *sdk.CastaiEvictorV1NodeSelector) []map[string]any { + if ns == nil { + return nil + } + out := map[string]any{} + if ns.LabelSelector.MatchLabels != nil { + out[FieldMatchLabels] = ns.LabelSelector.MatchLabels.AdditionalProperties + } + if ns.LabelSelector.MatchExpressions != nil { + out[FieldMatchExpressions] = flattenMatchExpressions(*ns.LabelSelector.MatchExpressions) + } + + return []map[string]any{out} +} + +func flattenMatchExpressions(mes []sdk.CastaiEvictorV1LabelSelectorExpression) []map[string]any { + if mes == nil { + return nil + } + + out := make([]map[string]any, len(mes)) + for i, me := range mes { + out[i] = map[string]any{ + FieldMatchExpressionKey: me.Key, + FieldMatchExpressionOp: string(me.Operator), + } + if me.Values != nil && len(*me.Values) > 0 { + out[i][FieldMatchExpressionVal] = *me.Values + } + } + + return out +} + +func toMatchExpressions(in []interface{}) ([]sdk.CastaiEvictorV1LabelSelectorExpression, error) { + out := make([]sdk.CastaiEvictorV1LabelSelectorExpression, len(in)) + for i, mei := range in { + if me, ok := mei.(map[string]interface{}); ok { + out[i] = sdk.CastaiEvictorV1LabelSelectorExpression{} + for k, v := range me { + switch k { + case FieldMatchExpressionKey: + if key, ok := v.(string); ok { + out[i].Key = key + } else { + return nil, fmt.Errorf("mapping match_expression key expecting string, got %T %+v", v, v) + } + case FieldMatchExpressionOp: + if op, ok := v.(string); ok { + out[i].Operator = sdk.CastaiEvictorV1LabelSelectorExpressionOperator(op) + } else { + return nil, fmt.Errorf("mapping match_expression operator expecting string, got %T %+v", v, v) + } + case FieldMatchExpressionVal: + if vals, ok := v.([]interface{}); ok { + outVals := make([]string, len(vals)) + for vi, vv := range vals { + outVals[vi], ok = vv.(string) + if !ok { + return nil, fmt.Errorf("mapping match_expression values expecting string, got %T %+v", vv, vv) + } + } + out[i].Values = &outVals + } else { + return nil, fmt.Errorf("mapping match_expression values expecting []interface{}, got %T %+v", v, v) + } + + } + + } + } else { + return nil, fmt.Errorf("mapping match_expressions expecting map[string]interface, got %T, %+v", mei, mei) + } + + } + return out, nil +} diff --git a/castai/resource_eviction_config_test.go b/castai/resource_eviction_config_test.go new file mode 100644 index 00000000..2590eb18 --- /dev/null +++ b/castai/resource_eviction_config_test.go @@ -0,0 +1,410 @@ +package castai + +import ( + "bytes" + "context" + "fmt" + "github.com/castai/terraform-provider-castai/castai/sdk" + mock_sdk "github.com/castai/terraform-provider-castai/castai/sdk/mock" + "github.com/golang/mock/gomock" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/samber/lo" + "github.com/stretchr/testify/require" + "io" + "net/http" + "testing" +) + +func TestEvictionConfig_ReadContext(t *testing.T) { + + mockctrl := gomock.NewController(t) + mockClient := mock_sdk.NewMockClientInterface(mockctrl) + + ctx := context.Background() + provider := &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + } + clusterId := "b6bfc074-a267-400f-b8f1-db0850c369b1" + + resource := resourceEvictionConfig() + val := cty.ObjectVal(map[string]cty.Value{ + FieldClusterId: cty.StringVal(clusterId), + }) + initialState := terraform.NewInstanceStateShimmedFromValue(val, 0) + + tests := map[string]struct { + data string + testFunc func(*testing.T, diag.Diagnostics, *schema.ResourceData) + }{ + "should work with empty config": { + data: `{"evictionConfig":[]}`, + testFunc: func(t *testing.T, res diag.Diagnostics, data *schema.ResourceData) { + r := require.New(t) + r.Nil(res) + r.False(res.HasError()) + eac, isOK := data.GetOk(FieldEvictorAdvancedConfig) + r.False(isOK) + fmt.Printf("is not %T, %+v", eac, eac) + d, ok := eac.([]interface{}) + r.True(ok) + r.Len(d, 0) + + }, + }, + "should read config": { + data: `{"evictionConfig":[{"podSelector":{"kind":"Job","labelSelector":{"matchLabels":{"key1":"value1"}}},"settings":{"aggressive":{"enabled":true}}}]}`, + testFunc: func(t *testing.T, res diag.Diagnostics, data *schema.ResourceData) { + r := require.New(t) + r.Nil(res) + r.False(res.HasError()) + eac, isOK := data.GetOk(FieldEvictorAdvancedConfig) + r.True(isOK) + r.NotNil(eac) + podSelectorKind, isOK := data.GetOk(fmt.Sprintf("%s.0.%s.0.kind", FieldEvictorAdvancedConfig, FieldPodSelector)) + r.True(isOK) + r.NotNil(podSelectorKind) + r.Equal("Job", podSelectorKind) + podSelectorLabelValue, isOK := data.GetOk(fmt.Sprintf("%s.0.%s.0.%s.key1", FieldEvictorAdvancedConfig, FieldPodSelector, FieldMatchLabels)) + r.True(isOK) + r.NotNil(podSelectorLabelValue) + r.Equal("value1", podSelectorLabelValue) + }, + }, + "should handle multiple evictionConfig objects": { + data: `{"evictionConfig":[ + {"podSelector":{"kind":"Job","labelSelector":{"matchLabels":{"key1":"value1"}}},"settings":{"aggressive":{"enabled":true}}}, + {"nodeSelector":{"labelSelector":{"matchLabels":{"node-label":"value1"}}},"settings":{"disposable":{"enabled":true}}}]}`, + testFunc: func(t *testing.T, res diag.Diagnostics, data *schema.ResourceData) { + r := require.New(t) + r.Nil(res) + r.False(res.HasError()) + eac, isOK := data.GetOk(FieldEvictorAdvancedConfig) + r.True(isOK) + r.NotNil(eac) + podSelectorKind, isOK := data.GetOk(fmt.Sprintf("%s.0.%s.0.kind", FieldEvictorAdvancedConfig, FieldPodSelector)) + r.True(isOK) + r.NotNil(podSelectorKind) + r.Equal("Job", podSelectorKind) + podSelectorLabelValue, isOK := data.GetOk(fmt.Sprintf("%s.0.%s.0.%s.key1", FieldEvictorAdvancedConfig, FieldPodSelector, FieldMatchLabels)) + r.True(isOK) + r.NotNil(podSelectorLabelValue) + r.Equal("value1", podSelectorLabelValue) + nodeSelectorLabelValue, isOK := data.GetOk(fmt.Sprintf("%s.1.%s.0.%s.node-label", FieldEvictorAdvancedConfig, FieldNodeSelector, FieldMatchLabels)) + r.True(isOK) + r.NotNil(nodeSelectorLabelValue) + r.Equal("value1", nodeSelectorLabelValue) + }, + }, + "should handle label expressions": { + data: `{"evictionConfig":[ {"podSelector":{"kind":"Job","labelSelector":{"matchExpressions":[{"key":"value1", "operator":"In", "values":["v1", "v2"]}]}},"settings":{"aggressive":{"enabled":true}}} ]}`, + testFunc: func(t *testing.T, res diag.Diagnostics, data *schema.ResourceData) { + r := require.New(t) + r.Nil(res) + r.False(res.HasError()) + eac, isOK := data.GetOk(FieldEvictorAdvancedConfig) + r.True(isOK) + r.NotNil(eac) + podSelectorKeyValue, isOK := data.GetOk(fmt.Sprintf("%s.0.%s.0.%s.0.key", FieldEvictorAdvancedConfig, FieldPodSelector, FieldMatchExpressions)) + r.True(isOK) + r.NotNil(podSelectorKeyValue) + r.Equal("value1", podSelectorKeyValue) + podSelectorValues, isOK := data.GetOk(fmt.Sprintf("%s.0.%s.0.%s.0.values", FieldEvictorAdvancedConfig, FieldPodSelector, FieldMatchExpressions)) + r.True(isOK) + r.NotNil(podSelectorValues) + r.Equal([]interface{}{"v1", "v2"}, podSelectorValues) + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + body := io.NopCloser(bytes.NewReader([]byte(test.data))) + + mockClient.EXPECT(). + EvictorAPIGetAdvancedConfig(gomock.Any(), clusterId). + Return(&http.Response{StatusCode: 200, Body: body, Header: map[string][]string{"Content-Type": {"json"}}}, nil) + data := resource.Data(initialState) + + result := resource.ReadContext(ctx, data, provider) + test.testFunc(t, result, data) + + }) + } + +} + +func TestEvictionConfig_CreateContext(t *testing.T) { + r := require.New(t) + mockctrl := gomock.NewController(t) + mockClient := mock_sdk.NewMockClientInterface(mockctrl) + + ctx := context.Background() + provider := &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + } + clusterId := "b6bfc074-a267-400f-b8f1-db0850c369b1" + evictionConfigResponse := `{ + "evictionConfig": [ + { + "podSelector": { + "kind": "Job", + "labelSelector": { + "matchLabels": { + "key1": "value1" + } + } + }, + "settings": { + "aggressive": { + "enabled": true + } + } + } + ] +}` + + resource := resourceEvictionConfig() + + val := cty.ObjectVal(map[string]cty.Value{ + FieldClusterId: cty.StringVal(clusterId), + FieldEvictorAdvancedConfig: cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "pod_selector": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ + "kind": cty.StringVal("Job"), + "match_labels": cty.MapVal(map[string]cty.Value{ + "key1": cty.StringVal("value1"), + }), + }), + }), + "aggressive": cty.BoolVal(true), + }), + }), + }) + + state := terraform.NewInstanceStateShimmedFromValue(val, 0) + data := resource.Data(state) + + mockClient.EXPECT().EvictorAPIUpsertAdvancedConfigWithBody(gomock.Any(), clusterId, "application/json", gomock.Any()). + DoAndReturn(func(ctx context.Context, clusterId string, contentType string, body io.Reader) (*http.Response, error) { + + got, _ := io.ReadAll(body) + expected := []byte(evictionConfigResponse) + + eq, err := JSONBytesEqual(got, expected) + r.NoError(err) + r.True(eq, fmt.Sprintf("got: %v\n"+ + "expected: %v\n", string(got), string(expected))) + + return &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Content-Type": {"json"}}, + Body: io.NopCloser(bytes.NewReader([]byte(evictionConfigResponse))), + }, nil + }).Times(1) + + result := resource.CreateContext(ctx, data, provider) + + r.Nil(result) + r.False(result.HasError()) + eac, isOK := data.GetOk(FieldEvictorAdvancedConfig) + r.True(isOK) + r.NotNil(eac) + podSelectorKind, isOK := data.GetOk(fmt.Sprintf("%s.0.%s.0.kind", FieldEvictorAdvancedConfig, FieldPodSelector)) + r.True(isOK) + r.NotNil(podSelectorKind) + r.Equal("Job", podSelectorKind) +} + +func TestEvictionConfig_UpdateContext(t *testing.T) { + r := require.New(t) + mockctrl := gomock.NewController(t) + mockClient := mock_sdk.NewMockClientInterface(mockctrl) + + ctx := context.Background() + provider := &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + } + clusterId := "b6bfc074-a267-400f-b8f1-db0850c369b1" + initialConfigJson := ` + { + "evictionConfig": [ + { + "podSelector": { + "kind": "Job", + "labelSelector": { + "matchLabels": { + "key1": "value1" + } + } + }, + "settings": { + "aggressive": { + "enabled": true + } + } + } + ] +}` + evictionConfigJson := ` + { + "evictionConfig": [ + { + "podSelector": { + "kind": "Job", + "labelSelector": { + "matchLabels": { + "key1": "value1" + } + } + }, + "settings": { + "aggressive": { + "enabled": true + } + } + }, + { + "nodeSelector": { + "labelSelector": { + "matchExpressions": [ + { + "key": "key1", + "operator": "In", + "values": [ + "val1", + "val2" + ] + } + ]} + }, + "settings": { + "disposable": { + "enabled": true + } + } + } + ] +}` + + initialConfig := sdk.CastaiEvictorV1EvictionConfig{ + Settings: sdk.CastaiEvictorV1EvictionSettings{Aggressive: &sdk.CastaiEvictorV1EvictionSettingsSettingEnabled{Enabled: true}}, + PodSelector: &sdk.CastaiEvictorV1PodSelector{ + Kind: lo.ToPtr("Job"), + LabelSelector: sdk.CastaiEvictorV1LabelSelector{ + MatchLabels: &sdk.CastaiEvictorV1LabelSelector_MatchLabels{AdditionalProperties: map[string]string{ + "key1": "value1", + }}}}} + + newConfig := sdk.CastaiEvictorV1EvictionConfig{ + Settings: sdk.CastaiEvictorV1EvictionSettings{Disposable: &sdk.CastaiEvictorV1EvictionSettingsSettingEnabled{Enabled: true}}, + NodeSelector: &sdk.CastaiEvictorV1NodeSelector{ + LabelSelector: sdk.CastaiEvictorV1LabelSelector{MatchExpressions: &[]sdk.CastaiEvictorV1LabelSelectorExpression{{ + Key: "key1", + Operator: "In", + Values: &[]string{"val1", "val2"}, + }}}}} + finalConfiuration := []sdk.CastaiEvictorV1EvictionConfig{initialConfig, newConfig} + resource := resourceEvictionConfig() + + val := cty.ObjectVal(map[string]cty.Value{ + FieldClusterId: cty.StringVal(clusterId), + }) + + state := terraform.NewInstanceStateShimmedFromValue(val, 0) + data := resource.Data(state) + + body := io.NopCloser(bytes.NewReader([]byte(initialConfigJson))) + mockClient.EXPECT(). + EvictorAPIGetAdvancedConfig(gomock.Any(), clusterId). + Return(&http.Response{StatusCode: 200, Body: body, Header: map[string][]string{"Content-Type": {"json"}}}, nil) + + result := resource.ReadContext(ctx, data, provider) + r.Nil(result) + r.False(result.HasError()) + + mockClient.EXPECT().EvictorAPIUpsertAdvancedConfigWithBody(gomock.Any(), clusterId, "application/json", gomock.Any()). + DoAndReturn(func(ctx context.Context, clusterId string, contentType string, body io.Reader) (*http.Response, error) { + got, _ := io.ReadAll(body) + expected := []byte(evictionConfigJson) + + eq, err := JSONBytesEqual(got, expected) + r.NoError(err) + r.True(eq, fmt.Sprintf("got: %v\n"+ + "expected: %v\n", string(got), string(expected))) + + return &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Content-Type": {"json"}}, + Body: io.NopCloser(bytes.NewReader([]byte(evictionConfigJson))), + }, nil + }).Times(1) + err := data.Set(FieldEvictorAdvancedConfig, flattenEvictionConfig(finalConfiuration)) + r.NoError(err) + updateResult := resource.UpdateContext(ctx, data, provider) + + r.Nil(updateResult) + r.False(result.HasError()) + eac, isOK := data.GetOk(FieldEvictorAdvancedConfig) + r.True(isOK) + r.NotNil(eac) +} + +func TestEvictionConfig_DeleteContext(t *testing.T) { + r := require.New(t) + mockctrl := gomock.NewController(t) + mockClient := mock_sdk.NewMockClientInterface(mockctrl) + + ctx := context.Background() + provider := &ProviderConfig{ + api: &sdk.ClientWithResponses{ + ClientInterface: mockClient, + }, + } + clusterId := "b6bfc074-a267-400f-b8f1-db0850c369b1" + evictionConfigJson := `{"evictionConfig": []}` + + resource := resourceEvictionConfig() + + val := cty.ObjectVal(map[string]cty.Value{ + FieldClusterId: cty.StringVal(clusterId), + FieldEvictorAdvancedConfig: cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "pod_selector": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ + "match_labels": cty.MapVal(map[string]cty.Value{ + "key1": cty.StringVal("val1"), + }), + }), + }), + "aggressive": cty.BoolVal(true), + }), + }), + }) + + state := terraform.NewInstanceStateShimmedFromValue(val, 0) + data := resource.Data(state) + + mockClient.EXPECT().EvictorAPIUpsertAdvancedConfigWithBody(gomock.Any(), clusterId, "application/json", gomock.Any()). + DoAndReturn(func(ctx context.Context, clusterId string, contentType string, body io.Reader) (*http.Response, error) { + return &http.Response{ + StatusCode: 200, + Header: map[string][]string{"Content-Type": {"json"}}, + Body: io.NopCloser(bytes.NewReader([]byte(evictionConfigJson))), + }, nil + }).Times(1) + + result := resource.DeleteContext(ctx, data, provider) + + r.Nil(result) + r.False(result.HasError()) + eac, isOK := data.GetOk(FieldEvictorAdvancedConfig) + r.False(isOK) + r.Equal([]interface{}{}, eac) +} diff --git a/castai/sdk/api.gen.go b/castai/sdk/api.gen.go index eddba1f3..421eeaea 100644 --- a/castai/sdk/api.gen.go +++ b/castai/sdk/api.gen.go @@ -23,6 +23,15 @@ const ( Viewer OrganizationRole = "viewer" ) +// Defines values for CastaiEvictorV1LabelSelectorExpressionOperator. +const ( + CastaiEvictorV1LabelSelectorExpressionOperatorDoesNotExist CastaiEvictorV1LabelSelectorExpressionOperator = "DoesNotExist" + CastaiEvictorV1LabelSelectorExpressionOperatorExists CastaiEvictorV1LabelSelectorExpressionOperator = "Exists" + CastaiEvictorV1LabelSelectorExpressionOperatorIn CastaiEvictorV1LabelSelectorExpressionOperator = "In" + CastaiEvictorV1LabelSelectorExpressionOperatorInvalid CastaiEvictorV1LabelSelectorExpressionOperator = "Invalid" + CastaiEvictorV1LabelSelectorExpressionOperatorNotIn CastaiEvictorV1LabelSelectorExpressionOperator = "NotIn" +) + // Defines values for CastaiInventoryV1beta1AttachableGPUDeviceManufacturer. const ( CastaiInventoryV1beta1AttachableGPUDeviceManufacturerAMD CastaiInventoryV1beta1AttachableGPUDeviceManufacturer = "AMD" @@ -53,14 +62,14 @@ const ( // Defines values for CastaiV1Cloud. const ( - AWS CastaiV1Cloud = "AWS" - AZURE CastaiV1Cloud = "AZURE" - Aws CastaiV1Cloud = "aws" - Azure CastaiV1Cloud = "azure" - GCP CastaiV1Cloud = "GCP" - Gcp CastaiV1Cloud = "gcp" - INVALID CastaiV1Cloud = "INVALID" - Invalid CastaiV1Cloud = "invalid" + CastaiV1CloudAWS CastaiV1Cloud = "AWS" + CastaiV1CloudAZURE CastaiV1Cloud = "AZURE" + CastaiV1CloudAws CastaiV1Cloud = "aws" + CastaiV1CloudAzure CastaiV1Cloud = "azure" + CastaiV1CloudGCP CastaiV1Cloud = "GCP" + CastaiV1CloudGcp CastaiV1Cloud = "gcp" + CastaiV1CloudINVALID CastaiV1Cloud = "INVALID" + CastaiV1CloudInvalid CastaiV1Cloud = "invalid" ) // Defines values for ExternalclusterV1NodeType. @@ -261,6 +270,78 @@ type CastaiAuthtokenV1beta1ListAuthTokensResponse struct { Items *[]CastaiAuthtokenV1beta1AuthToken `json:"items,omitempty"` } +// AdvancedConfig the evictor advanced configuration. +type CastaiEvictorV1AdvancedConfig struct { + EvictionConfig []CastaiEvictorV1EvictionConfig `json:"evictionConfig"` +} + +// EvictionConfig used to specify more granular settings per node/pod filters. +type CastaiEvictorV1EvictionConfig struct { + NodeSelector *CastaiEvictorV1NodeSelector `json:"nodeSelector,omitempty"` + PodSelector *CastaiEvictorV1PodSelector `json:"podSelector,omitempty"` + Settings CastaiEvictorV1EvictionSettings `json:"settings"` +} + +// CastaiEvictorV1EvictionSettings defines model for castai.evictor.v1.EvictionSettings. +type CastaiEvictorV1EvictionSettings struct { + Aggressive *CastaiEvictorV1EvictionSettingsSettingEnabled `json:"aggressive,omitempty"` + Disposable *CastaiEvictorV1EvictionSettingsSettingEnabled `json:"disposable,omitempty"` + RemovalDisabled *CastaiEvictorV1EvictionSettingsSettingEnabled `json:"removalDisabled,omitempty"` +} + +// CastaiEvictorV1EvictionSettingsSettingEnabled defines model for castai.evictor.v1.EvictionSettings.SettingEnabled. +type CastaiEvictorV1EvictionSettingsSettingEnabled struct { + Enabled bool `json:"enabled"` +} + +// LabelSelector is a proto mirror of the metav1.LabelSelector K8s API object. Properties `match_labels` and +// `match_expressions` are ANDed. +type CastaiEvictorV1LabelSelector struct { + // A more advanced label query with operators. Multiple expressions are ANDed. + MatchExpressions *[]CastaiEvictorV1LabelSelectorExpression `json:"matchExpressions,omitempty"` + + // Used to query resource labels. + MatchLabels *CastaiEvictorV1LabelSelector_MatchLabels `json:"matchLabels,omitempty"` +} + +// Used to query resource labels. +type CastaiEvictorV1LabelSelector_MatchLabels struct { + AdditionalProperties map[string]string `json:"-"` +} + +// Expression is a proto mirror of the metav1.LabelSelectorRequirement K8s API object. +type CastaiEvictorV1LabelSelectorExpression struct { + // Key is a label. + Key string `json:"key"` + + // Operator set of operators which can be used in the label selector expressions. + Operator CastaiEvictorV1LabelSelectorExpressionOperator `json:"operator"` + + // Values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the + // operator is Exists or DoesNotExist, the values array must be empty. + Values *[]string `json:"values,omitempty"` +} + +// Operator set of operators which can be used in the label selector expressions. +type CastaiEvictorV1LabelSelectorExpressionOperator string + +// CastaiEvictorV1NodeSelector defines model for castai.evictor.v1.NodeSelector. +type CastaiEvictorV1NodeSelector struct { + // LabelSelector is a proto mirror of the metav1.LabelSelector K8s API object. Properties `match_labels` and + // `match_expressions` are ANDed. + LabelSelector CastaiEvictorV1LabelSelector `json:"labelSelector"` +} + +// CastaiEvictorV1PodSelector defines model for castai.evictor.v1.PodSelector. +type CastaiEvictorV1PodSelector struct { + Kind *string `json:"kind,omitempty"` + + // LabelSelector is a proto mirror of the metav1.LabelSelector K8s API object. Properties `match_labels` and + // `match_expressions` are ANDed. + LabelSelector CastaiEvictorV1LabelSelector `json:"labelSelector"` + Namespace *string `json:"namespace,omitempty"` +} + // CastaiInventoryV1beta1AddReservationResponse defines model for castai.inventory.v1beta1.AddReservationResponse. type CastaiInventoryV1beta1AddReservationResponse struct { Reservation *CastaiInventoryV1beta1ReservationDetails `json:"reservation,omitempty"` @@ -1564,7 +1645,8 @@ type NodetemplatesV1TemplateConstraints struct { MinMemory *int32 `json:"minMemory"` // Should include on-demand instances in the considered pool. - OnDemand *bool `json:"onDemand"` + OnDemand *bool `json:"onDemand"` + Os *[]string `json:"os,omitempty"` // Should include spot instances in the considered pool. // Note 1: if both spot and on-demand are false, then on-demand is assumed. @@ -2036,6 +2118,9 @@ type CreateInvitationJSONBody = NewInvitations // ClaimInvitationJSONBody defines parameters for ClaimInvitation. type ClaimInvitationJSONBody = map[string]interface{} +// EvictorAPIUpsertAdvancedConfigJSONBody defines parameters for EvictorAPIUpsertAdvancedConfig. +type EvictorAPIUpsertAdvancedConfigJSONBody = CastaiEvictorV1AdvancedConfig + // NodeTemplatesAPIFilterInstanceTypesJSONBody defines parameters for NodeTemplatesAPIFilterInstanceTypes. type NodeTemplatesAPIFilterInstanceTypesJSONBody = NodetemplatesV1NodeTemplate @@ -2172,6 +2257,9 @@ type CreateInvitationJSONRequestBody = CreateInvitationJSONBody // ClaimInvitationJSONRequestBody defines body for ClaimInvitation for application/json ContentType. type ClaimInvitationJSONRequestBody = ClaimInvitationJSONBody +// EvictorAPIUpsertAdvancedConfigJSONRequestBody defines body for EvictorAPIUpsertAdvancedConfig for application/json ContentType. +type EvictorAPIUpsertAdvancedConfigJSONRequestBody = EvictorAPIUpsertAdvancedConfigJSONBody + // NodeTemplatesAPIFilterInstanceTypesJSONRequestBody defines body for NodeTemplatesAPIFilterInstanceTypes for application/json ContentType. type NodeTemplatesAPIFilterInstanceTypesJSONRequestBody = NodeTemplatesAPIFilterInstanceTypesJSONBody @@ -2244,6 +2332,59 @@ type ScheduledRebalancingAPICreateRebalancingScheduleJSONRequestBody = Scheduled // ScheduledRebalancingAPIUpdateRebalancingScheduleJSONRequestBody defines body for ScheduledRebalancingAPIUpdateRebalancingSchedule for application/json ContentType. type ScheduledRebalancingAPIUpdateRebalancingScheduleJSONRequestBody = ScheduledRebalancingAPIUpdateRebalancingScheduleJSONBody +// Getter for additional properties for CastaiEvictorV1LabelSelector_MatchLabels. Returns the specified +// element and whether it was found +func (a CastaiEvictorV1LabelSelector_MatchLabels) Get(fieldName string) (value string, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for CastaiEvictorV1LabelSelector_MatchLabels +func (a *CastaiEvictorV1LabelSelector_MatchLabels) Set(fieldName string, value string) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]string) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for CastaiEvictorV1LabelSelector_MatchLabels to handle AdditionalProperties +func (a *CastaiEvictorV1LabelSelector_MatchLabels) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]string) + for fieldName, fieldBuf := range object { + var fieldVal string + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for CastaiEvictorV1LabelSelector_MatchLabels to handle AdditionalProperties +func (a CastaiEvictorV1LabelSelector_MatchLabels) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + // Getter for additional properties for ExternalclusterV1EKSClusterParams_Tags. Returns the specified // element and whether it was found func (a ExternalclusterV1EKSClusterParams_Tags) Get(fieldName string) (value string, found bool) { diff --git a/castai/sdk/client.gen.go b/castai/sdk/client.gen.go index 8cec0722..d936d843 100644 --- a/castai/sdk/client.gen.go +++ b/castai/sdk/client.gen.go @@ -125,6 +125,14 @@ type ClientInterface interface { ClaimInvitation(ctx context.Context, id string, body ClaimInvitationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // EvictorAPIGetAdvancedConfig request + EvictorAPIGetAdvancedConfig(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // EvictorAPIUpsertAdvancedConfig request with any body + EvictorAPIUpsertAdvancedConfigWithBody(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + EvictorAPIUpsertAdvancedConfig(ctx context.Context, clusterId string, body EvictorAPIUpsertAdvancedConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // NodeTemplatesAPIFilterInstanceTypes request with any body NodeTemplatesAPIFilterInstanceTypesWithBody(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -534,6 +542,42 @@ func (c *Client) ClaimInvitation(ctx context.Context, id string, body ClaimInvit return c.Client.Do(req) } +func (c *Client) EvictorAPIGetAdvancedConfig(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEvictorAPIGetAdvancedConfigRequest(c.Server, clusterId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) EvictorAPIUpsertAdvancedConfigWithBody(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEvictorAPIUpsertAdvancedConfigRequestWithBody(c.Server, clusterId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) EvictorAPIUpsertAdvancedConfig(ctx context.Context, clusterId string, body EvictorAPIUpsertAdvancedConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEvictorAPIUpsertAdvancedConfigRequest(c.Server, clusterId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) NodeTemplatesAPIFilterInstanceTypesWithBody(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewNodeTemplatesAPIFilterInstanceTypesRequestWithBody(c.Server, clusterId, contentType, body) if err != nil { @@ -2024,6 +2068,87 @@ func NewClaimInvitationRequestWithBody(server string, id string, contentType str return req, nil } +// NewEvictorAPIGetAdvancedConfigRequest generates requests for EvictorAPIGetAdvancedConfig +func NewEvictorAPIGetAdvancedConfigRequest(server string, clusterId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "clusterId", runtime.ParamLocationPath, clusterId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/kubernetes/clusters/%s/evictor-advanced-config", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewEvictorAPIUpsertAdvancedConfigRequest calls the generic EvictorAPIUpsertAdvancedConfig builder with application/json body +func NewEvictorAPIUpsertAdvancedConfigRequest(server string, clusterId string, body EvictorAPIUpsertAdvancedConfigJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewEvictorAPIUpsertAdvancedConfigRequestWithBody(server, clusterId, "application/json", bodyReader) +} + +// NewEvictorAPIUpsertAdvancedConfigRequestWithBody generates requests for EvictorAPIUpsertAdvancedConfig with any type of body +func NewEvictorAPIUpsertAdvancedConfigRequestWithBody(server string, clusterId string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "clusterId", runtime.ParamLocationPath, clusterId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/kubernetes/clusters/%s/evictor-advanced-config", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewNodeTemplatesAPIFilterInstanceTypesRequest calls the generic NodeTemplatesAPIFilterInstanceTypes builder with application/json body func NewNodeTemplatesAPIFilterInstanceTypesRequest(server string, clusterId string, body NodeTemplatesAPIFilterInstanceTypesJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -4972,6 +5097,14 @@ type ClientWithResponsesInterface interface { ClaimInvitationWithResponse(ctx context.Context, id string, body ClaimInvitationJSONRequestBody) (*ClaimInvitationResponse, error) + // EvictorAPIGetAdvancedConfig request + EvictorAPIGetAdvancedConfigWithResponse(ctx context.Context, clusterId string) (*EvictorAPIGetAdvancedConfigResponse, error) + + // EvictorAPIUpsertAdvancedConfig request with any body + EvictorAPIUpsertAdvancedConfigWithBodyWithResponse(ctx context.Context, clusterId string, contentType string, body io.Reader) (*EvictorAPIUpsertAdvancedConfigResponse, error) + + EvictorAPIUpsertAdvancedConfigWithResponse(ctx context.Context, clusterId string, body EvictorAPIUpsertAdvancedConfigJSONRequestBody) (*EvictorAPIUpsertAdvancedConfigResponse, error) + // NodeTemplatesAPIFilterInstanceTypes request with any body NodeTemplatesAPIFilterInstanceTypesWithBodyWithResponse(ctx context.Context, clusterId string, contentType string, body io.Reader) (*NodeTemplatesAPIFilterInstanceTypesResponse, error) @@ -5504,6 +5637,66 @@ func (r ClaimInvitationResponse) GetBody() []byte { // TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 +type EvictorAPIGetAdvancedConfigResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CastaiEvictorV1AdvancedConfig +} + +// Status returns HTTPResponse.Status +func (r EvictorAPIGetAdvancedConfigResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r EvictorAPIGetAdvancedConfigResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 +// Body returns body of byte array +func (r EvictorAPIGetAdvancedConfigResponse) GetBody() []byte { + return r.Body +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 + +type EvictorAPIUpsertAdvancedConfigResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CastaiEvictorV1AdvancedConfig +} + +// Status returns HTTPResponse.Status +func (r EvictorAPIUpsertAdvancedConfigResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r EvictorAPIUpsertAdvancedConfigResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 +// Body returns body of byte array +func (r EvictorAPIUpsertAdvancedConfigResponse) GetBody() []byte { + return r.Body +} + +// TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 + type NodeTemplatesAPIFilterInstanceTypesResponse struct { Body []byte HTTPResponse *http.Response @@ -7654,6 +7847,32 @@ func (c *ClientWithResponses) ClaimInvitationWithResponse(ctx context.Context, i return ParseClaimInvitationResponse(rsp) } +// EvictorAPIGetAdvancedConfigWithResponse request returning *EvictorAPIGetAdvancedConfigResponse +func (c *ClientWithResponses) EvictorAPIGetAdvancedConfigWithResponse(ctx context.Context, clusterId string) (*EvictorAPIGetAdvancedConfigResponse, error) { + rsp, err := c.EvictorAPIGetAdvancedConfig(ctx, clusterId) + if err != nil { + return nil, err + } + return ParseEvictorAPIGetAdvancedConfigResponse(rsp) +} + +// EvictorAPIUpsertAdvancedConfigWithBodyWithResponse request with arbitrary body returning *EvictorAPIUpsertAdvancedConfigResponse +func (c *ClientWithResponses) EvictorAPIUpsertAdvancedConfigWithBodyWithResponse(ctx context.Context, clusterId string, contentType string, body io.Reader) (*EvictorAPIUpsertAdvancedConfigResponse, error) { + rsp, err := c.EvictorAPIUpsertAdvancedConfigWithBody(ctx, clusterId, contentType, body) + if err != nil { + return nil, err + } + return ParseEvictorAPIUpsertAdvancedConfigResponse(rsp) +} + +func (c *ClientWithResponses) EvictorAPIUpsertAdvancedConfigWithResponse(ctx context.Context, clusterId string, body EvictorAPIUpsertAdvancedConfigJSONRequestBody) (*EvictorAPIUpsertAdvancedConfigResponse, error) { + rsp, err := c.EvictorAPIUpsertAdvancedConfig(ctx, clusterId, body) + if err != nil { + return nil, err + } + return ParseEvictorAPIUpsertAdvancedConfigResponse(rsp) +} + // NodeTemplatesAPIFilterInstanceTypesWithBodyWithResponse request with arbitrary body returning *NodeTemplatesAPIFilterInstanceTypesResponse func (c *ClientWithResponses) NodeTemplatesAPIFilterInstanceTypesWithBodyWithResponse(ctx context.Context, clusterId string, contentType string, body io.Reader) (*NodeTemplatesAPIFilterInstanceTypesResponse, error) { rsp, err := c.NodeTemplatesAPIFilterInstanceTypesWithBody(ctx, clusterId, contentType, body) @@ -8692,6 +8911,58 @@ func ParseClaimInvitationResponse(rsp *http.Response) (*ClaimInvitationResponse, return response, nil } +// ParseEvictorAPIGetAdvancedConfigResponse parses an HTTP response from a EvictorAPIGetAdvancedConfigWithResponse call +func ParseEvictorAPIGetAdvancedConfigResponse(rsp *http.Response) (*EvictorAPIGetAdvancedConfigResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &EvictorAPIGetAdvancedConfigResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CastaiEvictorV1AdvancedConfig + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseEvictorAPIUpsertAdvancedConfigResponse parses an HTTP response from a EvictorAPIUpsertAdvancedConfigWithResponse call +func ParseEvictorAPIUpsertAdvancedConfigResponse(rsp *http.Response) (*EvictorAPIUpsertAdvancedConfigResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &EvictorAPIUpsertAdvancedConfigResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CastaiEvictorV1AdvancedConfig + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseNodeTemplatesAPIFilterInstanceTypesResponse parses an HTTP response from a NodeTemplatesAPIFilterInstanceTypesWithResponse call func ParseNodeTemplatesAPIFilterInstanceTypesResponse(rsp *http.Response) (*NodeTemplatesAPIFilterInstanceTypesResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) diff --git a/castai/sdk/mock/client.go b/castai/sdk/mock/client.go index 028c4af7..73d2782a 100644 --- a/castai/sdk/mock/client.go +++ b/castai/sdk/mock/client.go @@ -455,6 +455,66 @@ func (mr *MockClientInterfaceMockRecorder) DeleteOrganizationUser(ctx, id, userI return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOrganizationUser", reflect.TypeOf((*MockClientInterface)(nil).DeleteOrganizationUser), varargs...) } +// EvictorAPIGetAdvancedConfig mocks base method. +func (m *MockClientInterface) EvictorAPIGetAdvancedConfig(ctx context.Context, clusterId string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, clusterId} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "EvictorAPIGetAdvancedConfig", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EvictorAPIGetAdvancedConfig indicates an expected call of EvictorAPIGetAdvancedConfig. +func (mr *MockClientInterfaceMockRecorder) EvictorAPIGetAdvancedConfig(ctx, clusterId interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, clusterId}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvictorAPIGetAdvancedConfig", reflect.TypeOf((*MockClientInterface)(nil).EvictorAPIGetAdvancedConfig), varargs...) +} + +// EvictorAPIUpsertAdvancedConfig mocks base method. +func (m *MockClientInterface) EvictorAPIUpsertAdvancedConfig(ctx context.Context, clusterId string, body sdk.EvictorAPIUpsertAdvancedConfigJSONRequestBody, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, clusterId, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "EvictorAPIUpsertAdvancedConfig", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EvictorAPIUpsertAdvancedConfig indicates an expected call of EvictorAPIUpsertAdvancedConfig. +func (mr *MockClientInterfaceMockRecorder) EvictorAPIUpsertAdvancedConfig(ctx, clusterId, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, clusterId, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvictorAPIUpsertAdvancedConfig", reflect.TypeOf((*MockClientInterface)(nil).EvictorAPIUpsertAdvancedConfig), varargs...) +} + +// EvictorAPIUpsertAdvancedConfigWithBody mocks base method. +func (m *MockClientInterface) EvictorAPIUpsertAdvancedConfigWithBody(ctx context.Context, clusterId, contentType string, body io.Reader, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, clusterId, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "EvictorAPIUpsertAdvancedConfigWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EvictorAPIUpsertAdvancedConfigWithBody indicates an expected call of EvictorAPIUpsertAdvancedConfigWithBody. +func (mr *MockClientInterfaceMockRecorder) EvictorAPIUpsertAdvancedConfigWithBody(ctx, clusterId, contentType, body interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, clusterId, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvictorAPIUpsertAdvancedConfigWithBody", reflect.TypeOf((*MockClientInterface)(nil).EvictorAPIUpsertAdvancedConfigWithBody), varargs...) +} + // ExternalClusterAPIAddNode mocks base method. func (m *MockClientInterface) ExternalClusterAPIAddNode(ctx context.Context, clusterId string, body sdk.ExternalClusterAPIAddNodeJSONRequestBody, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -2483,6 +2543,51 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) DeleteOrganizationWithRe return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOrganizationWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).DeleteOrganizationWithResponse), ctx, id) } +// EvictorAPIGetAdvancedConfigWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) EvictorAPIGetAdvancedConfigWithResponse(ctx context.Context, clusterId string) (*sdk.EvictorAPIGetAdvancedConfigResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EvictorAPIGetAdvancedConfigWithResponse", ctx, clusterId) + ret0, _ := ret[0].(*sdk.EvictorAPIGetAdvancedConfigResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EvictorAPIGetAdvancedConfigWithResponse indicates an expected call of EvictorAPIGetAdvancedConfigWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) EvictorAPIGetAdvancedConfigWithResponse(ctx, clusterId interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvictorAPIGetAdvancedConfigWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).EvictorAPIGetAdvancedConfigWithResponse), ctx, clusterId) +} + +// EvictorAPIUpsertAdvancedConfigWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) EvictorAPIUpsertAdvancedConfigWithBodyWithResponse(ctx context.Context, clusterId, contentType string, body io.Reader) (*sdk.EvictorAPIUpsertAdvancedConfigResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EvictorAPIUpsertAdvancedConfigWithBodyWithResponse", ctx, clusterId, contentType, body) + ret0, _ := ret[0].(*sdk.EvictorAPIUpsertAdvancedConfigResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EvictorAPIUpsertAdvancedConfigWithBodyWithResponse indicates an expected call of EvictorAPIUpsertAdvancedConfigWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) EvictorAPIUpsertAdvancedConfigWithBodyWithResponse(ctx, clusterId, contentType, body interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvictorAPIUpsertAdvancedConfigWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).EvictorAPIUpsertAdvancedConfigWithBodyWithResponse), ctx, clusterId, contentType, body) +} + +// EvictorAPIUpsertAdvancedConfigWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) EvictorAPIUpsertAdvancedConfigWithResponse(ctx context.Context, clusterId string, body sdk.EvictorAPIUpsertAdvancedConfigJSONRequestBody) (*sdk.EvictorAPIUpsertAdvancedConfigResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EvictorAPIUpsertAdvancedConfigWithResponse", ctx, clusterId, body) + ret0, _ := ret[0].(*sdk.EvictorAPIUpsertAdvancedConfigResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EvictorAPIUpsertAdvancedConfigWithResponse indicates an expected call of EvictorAPIUpsertAdvancedConfigWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) EvictorAPIUpsertAdvancedConfigWithResponse(ctx, clusterId, body interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EvictorAPIUpsertAdvancedConfigWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).EvictorAPIUpsertAdvancedConfigWithResponse), ctx, clusterId, body) +} + // ExternalClusterAPIAddNodeWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) ExternalClusterAPIAddNodeWithBodyWithResponse(ctx context.Context, clusterId, contentType string, body io.Reader) (*sdk.ExternalClusterAPIAddNodeResponse, error) { m.ctrl.T.Helper() diff --git a/docs/resources/evictor_advanced_config.md b/docs/resources/evictor_advanced_config.md new file mode 100644 index 00000000..1a0fed02 --- /dev/null +++ b/docs/resources/evictor_advanced_config.md @@ -0,0 +1,97 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "castai_evictor_advanced_config Resource - terraform-provider-castai" +subcategory: "" +description: |- + CAST AI eviction config resource to manage evictor properties +--- + +# castai_evictor_advanced_config (Resource) + +CAST AI eviction config resource to manage evictor properties + + + + +## Schema + +### Required + +- `evictor_advanced_config` (Block List, Min: 1) evictor advanced configuration to target specific node/pod (see [below for nested schema](#nestedblock--evictor_advanced_config)) + +### Optional + +- `cluster_id` (String) CAST AI cluster id. +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `evictor_advanced_config` + +Optional: + +- `aggressive` (Boolean) +- `disposable` (Boolean) +- `node_selector` (Block List) node selector (see [below for nested schema](#nestedblock--evictor_advanced_config--node_selector)) +- `pod_selector` (Block List) pod selector (see [below for nested schema](#nestedblock--evictor_advanced_config--pod_selector)) +- `removal_disabled` (Boolean) + + +### Nested Schema for `evictor_advanced_config.node_selector` + +Optional: + +- `match_expressions` (Block List) (see [below for nested schema](#nestedblock--evictor_advanced_config--node_selector--match_expressions)) +- `match_labels` (Map of String) + + +### Nested Schema for `evictor_advanced_config.node_selector.match_expressions` + +Required: + +- `key` (String) +- `operator` (String) + +Optional: + +- `values` (List of String) + + + + +### Nested Schema for `evictor_advanced_config.pod_selector` + +Optional: + +- `kind` (String) +- `match_expressions` (Block List) (see [below for nested schema](#nestedblock--evictor_advanced_config--pod_selector--match_expressions)) +- `match_labels` (Map of String) +- `namespace` (String) + + +### Nested Schema for `evictor_advanced_config.pod_selector.match_expressions` + +Required: + +- `key` (String) +- `operator` (String) + +Optional: + +- `values` (List of String) + + + + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) +- `update` (String) + + diff --git a/examples/gke/evictor_advanced_config/README.MD b/examples/gke/evictor_advanced_config/README.MD new file mode 100644 index 00000000..c94f6dcc --- /dev/null +++ b/examples/gke/evictor_advanced_config/README.MD @@ -0,0 +1,28 @@ +## GKE and CAST AI example with CAST AI Autoscaler evictor advanced config + +Following example shows how to onboard GKE cluster to CAST AI, configure [Autoscaler evictor advanced config](https://docs.cast.ai/docs/evictor-advanced-configuration) + +IAM policies required to connect the cluster to CAST AI in the example are created by [castai/gke-role-iam/castai module](https://github.com/castai/terraform-castai-gke-iam). + +This example builds on top of gke_cluster_autoscaler_policies example. Please refer to it for more details. + +Example configuration should be analysed in the following order: +1. Create VPC - `vpc.tf` +2. Create GKE cluster - `gke.tf` +3. Create IAM and other CAST AI related resources to connect GKE cluster to CAST AI, configure Autoscaler and Node Configurations - `castai.tf` + +# Usage +1. Rename `tf.vars.example` to `tf.vars` +2. Update `tf.vars` file with your project name, cluster name, cluster region and CAST AI API token. +3. Initialize Terraform. Under example root folder run: +``` +terraform init +``` +4. Run Terraform apply: +``` +terraform apply -var-file=tf.vars +``` +5. To destroy resources created by this example: +``` +terraform destroy -var-file=tf.vars +``` diff --git a/examples/gke/evictor_advanced_config/castai.tf b/examples/gke/evictor_advanced_config/castai.tf new file mode 100644 index 00000000..bb924bfb --- /dev/null +++ b/examples/gke/evictor_advanced_config/castai.tf @@ -0,0 +1,39 @@ +provider "castai" { + api_url = var.castai_api_url + api_token = var.castai_api_token +} + +module "gke_autoscaler_evictor_advanced_config" { + source = "../gke_cluster_autoscaler_policies" + + castai_api_token = var.castai_api_token + castai_api_url = var.castai_api_url + cluster_region = var.cluster_region + cluster_zones = var.cluster_zones + cluster_name = var.cluster_name + project_id = var.project_id + delete_nodes_on_disconnect = var.delete_nodes_on_disconnect + evictor_advanced_config = [ + { + pod_selector = { + kind = "Job" + namespace = "castai" + match_labels = { + "app.kubernetes.io/name" = "castai-node" + } + }, + aggressive = true + }, + { + node_selector = { + match_expressions = [ + { + key = "pod.cast.ai/flag" + operator = "Exists" + } + ] + }, + disposable = true + } + ] +} \ No newline at end of file diff --git a/examples/gke/evictor_advanced_config/variables.tf b/examples/gke/evictor_advanced_config/variables.tf new file mode 100644 index 00000000..53a46b10 --- /dev/null +++ b/examples/gke/evictor_advanced_config/variables.tf @@ -0,0 +1,44 @@ +# GKE module variables. +variable "cluster_name" { + type = string + description = "GKE cluster name in GCP project." +} + +variable "cluster_region" { + type = string + description = "The region to create the cluster." +} + +variable "cluster_zones" { + type = list(string) + description = "The zones to create the cluster." +} + +variable "project_id" { + type = string + description = "GCP project ID in which GKE cluster would be created." +} + +variable "castai_api_url" { + type = string + description = "URL of alternative CAST AI API to be used during development or testing" + default = "https://api-tiberiugal2.localenv.cast.ai" +} + +# Variables required for connecting EKS cluster to CAST AI +variable "castai_api_token" { + type = string + description = "CAST AI API token created in console.cast.ai API Access keys section." +} + +variable "delete_nodes_on_disconnect" { + type = bool + description = "Optional parameter, if set to true - CAST AI provisioned nodes will be deleted from cloud on cluster disconnection. For production use it is recommended to set it to false." + default = true +} + +variable "tags" { + type = map(any) + description = "Optional tags for new cluster nodes. This parameter applies only to new nodes - tags for old nodes are not reconciled." + default = {} +} diff --git a/examples/gke/gke_cluster_zonal_autoscaler/version.tf b/examples/gke/gke_cluster_zonal_autoscaler/version.tf index 67109f91..d19e42bd 100644 --- a/examples/gke/gke_cluster_zonal_autoscaler/version.tf +++ b/examples/gke/gke_cluster_zonal_autoscaler/version.tf @@ -14,4 +14,4 @@ terraform { } } required_version = ">= 0.13" -} +} \ No newline at end of file From 2705b7ab06d278668d2243a593c0a4c0ceea88b1 Mon Sep 17 00:00:00 2001 From: Jan Sykora Date: Tue, 17 Oct 2023 16:38:01 +0200 Subject: [PATCH 2/2] fix: Fix ACC tests --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 55518194..7780869e 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ default: build init-examples: @echo "==> Creating symlinks for example/ projects to terraform-provider-castai binary"; \ TF_PROVIDER_FILENAME=terraform-provider-castai; \ - GOOS=`go tool dist env | awk -F'=' '/^GOOS/ { print $$2}' | tr -d '";'`; \ - GOARCH=`go tool dist env | awk -F'=' '/^GOARCH/ { print $$2}' | tr -d '";'`; \ + GOOS=`go tool dist env | awk -F'=' '/^GOOS/ { print $$2}' | tr -d '"'`; \ + GOARCH=`go tool dist env | awk -F'=' '/^GOARCH/ { print $$2}' | tr -d '"'`; \ for examples in examples/eks examples/gke examples/aks ; do \ for tfproject in $$examples/* ; do \ TF_PROJECT_PLUGIN_PATH="$${tfproject}/terraform.d/plugins/registry.terraform.io/castai/castai/0.0.0-local/$${GOOS}_$${GOARCH}"; \