Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 85 additions & 6 deletions castai/data_source_gke.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,39 @@ import (

"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"
)

const (
// fieldGKEPoliciesPolicy is the name of the resource
fieldGKEPoliciesPolicy = "policy"
// fieldGKEPoliciesFeatures is the name of the policies per feature
fieldGKEPoliciesFeatures = "features"
loadBalancersNetworkEndpointGroupFeature = "load_balancers_network_endpoint_group"
loadBalancersTargetBackendPoolsFeature = "load_balancers_target_backend_pools"
loadBalancersUnmanagedInstanceGroupsFeature = "load_balancers_unmanaged_instance_groups"
)

func dataSourceGKEPolicies() *schema.Resource {
return &schema.Resource{
ReadContext: dataSourceGKEPoliciesRead,
Description: "Returns list of GCP policies needed for onboarding a cluster into CAST AI",
Schema: map[string]*schema.Schema{
"policy": {
fieldGKEPoliciesFeatures: {
Description: "Provide a list of GCP feature names to include the necessary policies for them to work.",
Type: schema.TypeList,
ForceNew: true,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
loadBalancersNetworkEndpointGroupFeature,
loadBalancersTargetBackendPoolsFeature,
loadBalancersUnmanagedInstanceGroupsFeature,
}, false),
},
},
fieldGKEPoliciesPolicy: {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Expand All @@ -23,12 +49,65 @@ func dataSourceGKEPolicies() *schema.Resource {
}
}

func dataSourceGKEPoliciesRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
policies, _ := gke.GetUserPolicy()
data.SetId("gke")
if err := data.Set("policy", policies); err != nil {
return diag.FromErr(fmt.Errorf("setting gke policy: %w", err))
func dataSourceGKEPoliciesRead(_ context.Context, data *schema.ResourceData, _ interface{}) diag.Diagnostics {
// add policies per specified features
features, ok := data.Get(fieldGKEPoliciesFeatures).([]interface{})
if !ok {
return diag.FromErr(fmt.Errorf("failed to retrieve features"))
}

// Initialize policy set
policySet := make(map[string]struct{})

// Process each feature
for _, feature := range features {
var err error
var policies []string

switch feature {
case loadBalancersNetworkEndpointGroupFeature:
policies, err = gke.GetLoadBalancersNetworkEndpointGroupPolicy()
case loadBalancersTargetBackendPoolsFeature:
policies, err = gke.GetLoadBalancersTargetBackendPoolsPolicy()
case loadBalancersUnmanagedInstanceGroupsFeature:
policies, err = gke.GetLoadBalancersUnmanagedInstanceGroupsPolicy()
default:
return diag.FromErr(fmt.Errorf("unknown feature: %s", feature))
}

if err != nil {
return diag.FromErr(fmt.Errorf("getting %s policy: %w", feature, err))
}

policySet = appendArrayToMap(policies, policySet)
}

// Add base user policies
userPolicy, err := gke.GetUserPolicy()
if err != nil {
return diag.FromErr(fmt.Errorf("getting user policy: %w", err))
}
policySet = appendArrayToMap(userPolicy, policySet)

var allPolicies []string
for policy := range policySet {
allPolicies = append(allPolicies, policy)
}

if err := data.Set(fieldGKEPoliciesPolicy, allPolicies); err != nil {
return diag.FromErr(fmt.Errorf("setting %s policy: %w", fieldGKEPoliciesPolicy, err))
}
data.SetId("gke")

return nil
}

func appendArrayToMap(arr []string, m map[string]struct{}) map[string]struct{} {
if m == nil {
m = make(map[string]struct{})
}
for _, v := range arr {
m[v] = struct{}{}
}
return m
}
113 changes: 113 additions & 0 deletions castai/data_source_gke_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package castai

import (
"context"
"github.com/castai/terraform-provider-castai/castai/policies/gke"
"github.com/castai/terraform-provider-castai/castai/sdk"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/stretchr/testify/require"
"testing"
)

func Test_dataSourceGKEPoliciesRead(t *testing.T) {
up, _ := gke.GetUserPolicy()
lbNeg, _ := gke.GetLoadBalancersNetworkEndpointGroupPolicy()
lbTbp, _ := gke.GetLoadBalancersTargetBackendPoolsPolicy()
lbUig, _ := gke.GetLoadBalancersUnmanagedInstanceGroupsPolicy()
tests := []struct {
name string
features []interface{}
expected int
hasError bool
}{
{
name: "all features",
features: []interface{}{
loadBalancersNetworkEndpointGroupFeature,
loadBalancersTargetBackendPoolsFeature,
loadBalancersUnmanagedInstanceGroupsFeature,
},
expected: len(up) + len(lbNeg) + len(lbTbp) + len(lbUig) - 1, // -1 for the duplicate policy
hasError: false,
},
{
name: "empty features",
expected: len(up),
hasError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := require.New(t)
//mockClient := mock_sdk.NewMockClientInterface(gomock.NewController(t))

ctx := context.Background()
provider := &ProviderConfig{
api: &sdk.ClientWithResponses{
ClientInterface: nil,
},
}

state := terraform.NewInstanceStateShimmedFromValue(cty.ObjectVal(map[string]cty.Value{}), 0)

resource := dataSourceGKEPolicies()
data := resource.Data(state)
r.NoError(data.Set(fieldGKEPoliciesFeatures, tt.features))

result := resource.ReadContext(ctx, data, provider)
if tt.hasError {
r.True(result.HasError())
} else {
r.Nil(result)
r.False(result.HasError())
actualPolicies := data.Get(fieldGKEPoliciesPolicy).([]interface{})
r.Len(actualPolicies, tt.expected)
}
})
}
}

func TestAccDataSourceGKEPolicies_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: providerFactories,
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: testAccDataSourceGKEPoliciesConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.castai_gke_user_policies.gke", "features.#", "3"),
resource.TestCheckResourceAttr("data.castai_gke_user_policies.gke", "policy.#", "46"),
),
},
{
Config: testAccDataSourceGKEPoliciesConfigUpdated,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.castai_gke_user_policies.gke", "features.#", "2"),
resource.TestCheckResourceAttr("data.castai_gke_user_policies.gke", "policy.#", "43ß"),
),
},
},
})
}

const testAccDataSourceGKEPoliciesConfig = `
data "castai_gke_user_policies" "gke" {
features = [
"load_balancers_network_endpoint_group",
"load_balancers_target_backend_pools",
"load_balancers_unmanaged_instance_groups"
]
}
`
const testAccDataSourceGKEPoliciesConfigUpdated = `
data "castai_gke_user_policies" "gke" {
features = [
"load_balancers_network_endpoint_group",
"load_balancers_target_backend_pools"
]
}
`
8 changes: 8 additions & 0 deletions castai/policies/gke/loadBalancers-networkEndpointGroup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Policies": [
"compute.networkEndpointGroups.get",
"compute.networkEndpointGroups.list",
"compute.networkEndpointGroups.attachNetworkEndpoints",
"compute.networkEndpointGroups.detachNetworkEndpoints"
]
}
8 changes: 8 additions & 0 deletions castai/policies/gke/loadBalancers-targetBackendPools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Policies": [
"compute.targetPools.get",
"compute.targetPools.addInstance",
"compute.targetPools.removeInstance",
"compute.instances.use"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"Policies": [
"compute.instanceGroups.update",
"compute.instances.use"
]
}
39 changes: 39 additions & 0 deletions castai/policies/gke/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import (
var (
//go:embed iam-policy.json
Policy []byte

//go:embed loadBalancers-networkEndpointGroup.json
LoadBalancersNetworkEndpointGroup []byte

//go:embed loadBalancers-targetBackendPools.json
LoadBalancersTargetBackendPools []byte

//go:embed loadBalancers-unmanagedInstanceGroups.json
LoadBalancersUnmanagedInstanceGroups []byte
)

type pols struct {
Expand All @@ -23,3 +32,33 @@ func GetUserPolicy() ([]string, error) {

return p.Policies, nil
}

func GetLoadBalancersNetworkEndpointGroupPolicy() ([]string, error) {
var p pols
err := json.Unmarshal(LoadBalancersNetworkEndpointGroup, &p)
if err != nil {
return nil, err
}

return p.Policies, nil
}

func GetLoadBalancersTargetBackendPoolsPolicy() ([]string, error) {
var p pols
err := json.Unmarshal(LoadBalancersTargetBackendPools, &p)
if err != nil {
return nil, err
}

return p.Policies, nil
}

func GetLoadBalancersUnmanagedInstanceGroupsPolicy() ([]string, error) {
var p pols
err := json.Unmarshal(LoadBalancersUnmanagedInstanceGroups, &p)
if err != nil {
return nil, err
}

return p.Policies, nil
}
56 changes: 53 additions & 3 deletions castai/policies/gke/policy_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gke

import (
"github.com/stretchr/testify/require"
"testing"
)

Expand All @@ -14,12 +15,61 @@ func TestPolicies(t *testing.T) {
t.Fatalf("couldn't generate user policy")
}

clustersGet := "container.clusters.get"
zonesGet := "serviceusage.services.list"
wantClustersGet := "container.clusters.get"
wantZonesGet := "serviceusage.services.list"

if !contains(userpolicy, clustersGet) || !contains(userpolicy, zonesGet) {
if !contains(userpolicy, wantClustersGet) || !contains(userpolicy, wantZonesGet) {
t.Fatalf("generated User policy document does not contain required policies")
}
require.Equal(t, 37, len(userpolicy))
})
t.Run("LoadBalancersNetworkEndpointGroup policy", func(t *testing.T) {
lbNegPolicy, err := GetLoadBalancersNetworkEndpointGroupPolicy()
if err != nil {
t.Error(err)
}
if lbNegPolicy == nil {
t.Fatalf("couldn't generate LoadBalancersNetworkEndpointGroup policy")
}

want := "compute.networkEndpointGroups.get"

if !contains(lbNegPolicy, want) {
t.Fatalf("generated LoadBalancersNetworkEndpointGroup policy document does not contain required policies")
}
require.Equal(t, 4, len(lbNegPolicy))
})
t.Run("LoadBalancersTargetBackendPools policy", func(t *testing.T) {
lbTbpPolicy, err := GetLoadBalancersTargetBackendPoolsPolicy()
if err != nil {
t.Error(err)
}
if lbTbpPolicy == nil {
t.Fatalf("couldn't generate LoadBalancersTargetBackendPools policy")
}

want := "compute.targetPools.get"

if !contains(lbTbpPolicy, want) {
t.Fatalf("generated LoadBalancersTargetBackendPools policy document does not contain required policies")
}
require.Equal(t, 4, len(lbTbpPolicy))
})
t.Run("LoadBalancersUnmanagedInstanceGroups policy", func(t *testing.T) {
lbUigPolicy, err := GetLoadBalancersUnmanagedInstanceGroupsPolicy()
if err != nil {
t.Error(err)
}
if lbUigPolicy == nil {
t.Fatalf("couldn't generate LoadBalancersUnmanagedInstanceGroups policy")
}

want := "compute.instanceGroups.update"

if !contains(lbUigPolicy, want) {
t.Fatalf("generated LoadBalancersUnmanagedInstanceGroups policy document does not contain required policies")
}
require.Equal(t, 2, len(lbUigPolicy))
})
}

Expand Down
Loading
Loading