Skip to content

Commit

Permalink
Feat: add new OIDC credentials creation and assigment (AWS data sourc…
Browse files Browse the repository at this point in the history
…e) (#759)

* Feat: add new OIDC credentials creation and assigment (AWS data source)

* fix integration test by adding oidc to policy

* revert integration test
  • Loading branch information
TomerHeber authored Dec 4, 2023
1 parent 5a55606 commit 008ccd8
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 0 deletions.
1 change: 1 addition & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type ApiClientInterface interface {
OrganizationId() (string, error)
OrganizationPolicyUpdate(OrganizationPolicyUpdatePayload) (*Organization, error)
OrganizationUserUpdateRole(userId string, roleId string) error
OidcSub() (string, error)
Policy(projectId string) (Policy, error)
PolicyUpdate(payload PolicyUpdatePayload) (Policy, error)
Projects() ([]Project, error)
Expand Down
15 changes: 15 additions & 0 deletions client/api_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions client/api_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,17 @@ func (client *ApiClient) ApiKeys() ([]ApiKey, error) {

return result, err
}

func (client *ApiClient) OidcSub() (string, error) {
organizationId, err := client.OrganizationId()
if err != nil {
return "", err
}

var result string
if err := client.http.Get("/api-keys/oidc-sub", map[string]string{"organizationId": organizationId}, &result); err != nil {
return "", err
}

return result, nil
}
32 changes: 32 additions & 0 deletions client/api_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,36 @@ var _ = Describe("ApiKey Client", func() {
httpCall.Times(1)
})
})

Describe("Get Oidc Sub", func() {
var returnedOidcSub string
var err error
mockedOidcSub := "oidc sub 1234"

BeforeEach(func() {
mockOrganizationIdCall(organizationId)
httpCall = mockHttpClient.EXPECT().
Get("/api-keys/oidc-sub", map[string]string{"organizationId": organizationId}, gomock.Any()).
Do(func(path string, request interface{}, response *string) {
*response = mockedOidcSub
})
returnedOidcSub, err = apiClient.OidcSub()
})

It("Should get organization id", func() {
organizationIdCall.Times(1)
})

It("Should send GET request", func() {
httpCall.Times(1)
})

It("Should return Oidc sub", func() {
Expect(returnedOidcSub).To(Equal(mockedOidcSub))
})

It("Should not return error", func() {
Expect(err).To(BeNil())
})
})
})
66 changes: 66 additions & 0 deletions env0/data_aws_oidc_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package env0

import (
"context"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataAwsOidcCredentials() *schema.Resource {
return &schema.Resource{
ReadContext: dataAwsOidcCredentialRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "the name of the aws oidc credentials",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
"id": {
Type: schema.TypeString,
Description: "the id of the aws oidc credentials",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
"oidc_sub": {
Type: schema.TypeString,
Computed: true,
Description: "the jwt oidc sub",
},
},
}
}

func dataAwsOidcCredentialRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var credentials client.Credentials
var err error

id, ok := d.GetOk("id")
if ok {
credentials, err = getCredentialsById(id.(string), credentialsTypeToPrefixList[AWS_OIDC_TYPE], meta)
} else {
credentials, err = getCredentialsByName(d.Get("name").(string), credentialsTypeToPrefixList[AWS_OIDC_TYPE], meta)
}

if err != nil {
return DataGetFailure("aws oidc credentials", id, err)
}

if err := writeResourceData(&credentials, d); err != nil {
return diag.Errorf("schema resource data serialization failed: %v", err)
}

apiClient := meta.(client.ApiClientInterface)

oidcSub, err := apiClient.OidcSub()
if err != nil {
return diag.Errorf("failed to get oidc sub: %v", err)
}

d.Set("oidc_sub", oidcSub)

return nil
}
124 changes: 124 additions & 0 deletions env0/data_aws_oidc_credentials_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package env0

import (
"fmt"
"regexp"
"testing"

"github.com/env0/terraform-provider-env0/client"
"github.com/env0/terraform-provider-env0/client/http"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAwsOidcCredentialDataSource(t *testing.T) {
credentials := client.Credentials{
Id: "id0",
Name: "name0",
Type: string(client.AwsOidcCredentialsType),
}

credentialsOther1 := client.Credentials{
Id: "id1",
Name: "name1",
Type: string(client.AwsOidcCredentialsType),
}

credentialsOther2 := client.Credentials{
Id: "id2",
Name: "name2",
Type: string(client.AwsAssumedRoleCredentialsType),
}

oidcSub := "oidc sub 123345 !!!"

byName := map[string]interface{}{"name": credentials.Name}
byId := map[string]interface{}{"id": credentials.Id}

resourceType := "env0_aws_oidc_credentials"
resourceName := "test_aws_oidc_credentials"
accessor := dataSourceAccessor(resourceType, resourceName)

getValidTestCase := func(input map[string]interface{}) resource.TestCase {
return resource.TestCase{
Steps: []resource.TestStep{
{
Config: dataSourceConfigCreate(resourceType, resourceName, input),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", credentials.Id),
resource.TestCheckResourceAttr(accessor, "name", credentials.Name),
resource.TestCheckResourceAttr(accessor, "oidc_sub", oidcSub),
),
},
},
}
}

getErrorTestCase := func(input map[string]interface{}, expectedError string) resource.TestCase {
return resource.TestCase{
Steps: []resource.TestStep{
{
Config: dataSourceConfigCreate(resourceType, resourceName, input),
ExpectError: regexp.MustCompile(expectedError),
},
},
}
}

mockGetCredentials := func(returnValue client.Credentials) func(mockFunc *client.MockApiClientInterface) {
return func(mock *client.MockApiClientInterface) {
mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(returnValue, nil)
mock.EXPECT().OidcSub().AnyTimes().Return(oidcSub, nil)
}
}

mockListCredentials := func(returnValue []client.Credentials) func(mockFunc *client.MockApiClientInterface) {
return func(mock *client.MockApiClientInterface) {
mock.EXPECT().CloudCredentialsList().AnyTimes().Return(returnValue, nil)
mock.EXPECT().OidcSub().AnyTimes().Return(oidcSub, nil)
}
}

t.Run("by id", func(t *testing.T) {
runUnitTest(t,
getValidTestCase(byId),
mockGetCredentials(credentials),
)
})

t.Run("by name", func(t *testing.T) {
runUnitTest(t,
getValidTestCase(byName),
mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2}),
)
})

t.Run("throw error when no name or id is supplied", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase(map[string]interface{}{}, "one of `id,name` must be specified"),
func(mock *client.MockApiClientInterface) {},
)
})

t.Run("throw error when by name and more than one is returned", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase(byName, "found multiple credentials"),
mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2, credentials}),
)
})

t.Run("Throw error when by name and not found", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase(byName, "not found"),
mockListCredentials([]client.Credentials{credentialsOther1, credentialsOther2}),
)
})

t.Run("Throw error when by id and not found", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase(byId, fmt.Sprintf("id %s not found", credentials.Id)),
func(mock *client.MockApiClientInterface) {
mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(client.Credentials{}, http.NewMockFailedResponseError(404))
},
)
})
}
1 change: 1 addition & 0 deletions env0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func Provider(version string) plugin.ProviderFunc {
"env0_azure_cost_credentials": dataCredentials(AZURE_COST_TYPE),
"env0_google_cost_credentials": dataCredentials(GCP_COST_TYPE),
"env0_aws_credentials": dataCredentials(AWS_TYPE),
"env0_aws_oidc_credentials": dataAwsOidcCredentials(),
"env0_gcp_credentials": dataCredentials(GCP_TYPE),
"env0_azure_credentials": dataCredentials(AZURE_TYPE),
"env0_team": dataTeam(),
Expand Down
16 changes: 16 additions & 0 deletions examples/data-sources/env0_aws_oidc_credentials/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
resource "env0_aws_oidc_credentials" "example" {
name = "name"
role_arn = "role_arn"
}

data "env0_aws_oidc_credentials" "by_id" {
id = env0_aws_oidc_credentials.example.id
}

data "env0_aws_oidc_credentials" "by_name" {
name = env0_aws_oidc_credentials.example.name
}

output "oidc_sub" {
value = data.env0_aws_oidc_credentials.by_name.oidc_sub
}

0 comments on commit 008ccd8

Please sign in to comment.