Skip to content

Commit

Permalink
Feat: Add azure credentials (#258)
Browse files Browse the repository at this point in the history
* Feat: Add azure credentials

* Update docs

* added resource and tests ( removed import from gcp )

* Update docs

* missing argument test

* removed aws credentials import

* added example and IT

* Update docs

* added & fix client tests

* pr-comments removed cloud credentials data

* pr-comments sensitive

* pr-comments data

* Update docs

* Revert "pr-comments data"

This reverts commit d46d1de.

* Revert "pr-comments removed cloud credentials data"

This reverts commit 0f20b41.

* Update docs

Co-authored-by: update generated docs action <[email protected]>
  • Loading branch information
liranfarage89 and update generated docs action authored Mar 15, 2022
1 parent 223efb3 commit dd8a54d
Show file tree
Hide file tree
Showing 21 changed files with 639 additions and 65 deletions.
1 change: 1 addition & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type ApiClientInterface interface {
AwsCredentialsCreate(request AwsCredentialsCreatePayload) (ApiKey, error)
CloudCredentialsDelete(id string) error
GcpCredentialsCreate(request GcpCredentialsCreatePayload) (ApiKey, error)
AzureCredentialsCreate(request AzureCredentialsCreatePayload) (ApiKey, error)
AssignCloudCredentialsToProject(projectId string, credentialId string) (CloudCredentialsProjectAssignment, error)
RemoveCloudCredentialsFromProject(projectId string, credentialId string) error
CloudCredentialIdsInProject(projectId string) ([]string, 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.

4 changes: 3 additions & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ var _ = AfterSuite(func() {
ctrl.Finish()
})

func mockOrganizationIdCall(organizationId string) {
func mockOrganizationIdCall(organizationId string) *gomock.Call {
organizations := []Organization{{
Id: organizationId,
}}

organizationIdCall = mockHttpClient.EXPECT().Get("/organizations", nil, gomock.Any()).Do(func(path string, params interface{}, response *[]Organization) {
*response = organizations
})

return organizationIdCall
}

func TestApiClient(t *testing.T) {
Expand Down
16 changes: 16 additions & 0 deletions client/cloud_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ func (self *ApiClient) GcpCredentialsCreate(request GcpCredentialsCreatePayload)
return result, nil
}

func (self *ApiClient) AzureCredentialsCreate(request AzureCredentialsCreatePayload) (ApiKey, error) {
organizationId, err := self.organizationId()
if err != nil {
return ApiKey{}, err
}

request.OrganizationId = organizationId

var result ApiKey
err = self.http.Post("/credentials", request, &result)
if err != nil {
return ApiKey{}, err
}
return result, nil
}

func (self *ApiClient) CloudCredentialsDelete(id string) error {
return self.http.Delete("/credentials/" + id)
}
39 changes: 39 additions & 0 deletions client/cloud_credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,45 @@ var _ = Describe("CloudCredentials", func() {
})
})

Describe("AzureCredentialsCreate", func() {
const azureRequestType = "AZURE_SERVICE_PRINCIPAL_FOR_DEPLOYMENT"
mockAzureApiKey := mockApiKey
mockAzureApiKey.Type = azureRequestType
BeforeEach(func() {

mockOrganizationIdCall(organizationId).Times(1)

payloadValue := AzureCredentialsValuePayload{
ClientId: "fakeClientId",
ClientSecret: "fakeClientSecret",
SubscriptionId: "fakeSubscriptionId",
TenantId: "fakeTenantId",
}

httpCall = mockHttpClient.EXPECT().
Post("/credentials", AzureCredentialsCreatePayload{
Name: credentialsName,
OrganizationId: organizationId,
Type: azureRequestType,
Value: payloadValue,
},
gomock.Any()).
Do(func(path string, request interface{}, response *ApiKey) {
*response = mockAzureApiKey
}).Times(1)

apiKey, _ = apiClient.AzureCredentialsCreate(AzureCredentialsCreatePayload{
Name: credentialsName,
Value: payloadValue,
Type: azureRequestType,
})
})

It("Should return key", func() {
Expect(apiKey).To(Equal(mockAzureApiKey))
})
})

Describe("CloudCredentialsDelete", func() {
BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Delete("/credentials/" + mockApiKey.Id)
Expand Down
22 changes: 19 additions & 3 deletions client/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,13 @@ type SshKeyCreatePayload struct {

type AwsCredentialsType string
type GcpCredentialsType string
type AzureCredentialsType string

const (
AwsAssumedRoleCredentialsType AwsCredentialsType = "AWS_ASSUMED_ROLE_FOR_DEPLOYMENT"
AwsAccessKeysCredentialsType AwsCredentialsType = "AWS_ACCESS_KEYS_FOR_DEPLOYMENT"
GcpServiceAccountCredentialsType GcpCredentialsType = "GCP_SERVICE_ACCOUNT_FOR_DEPLOYMENT"
AwsAssumedRoleCredentialsType AwsCredentialsType = "AWS_ASSUMED_ROLE_FOR_DEPLOYMENT"
AwsAccessKeysCredentialsType AwsCredentialsType = "AWS_ACCESS_KEYS_FOR_DEPLOYMENT"
GcpServiceAccountCredentialsType GcpCredentialsType = "GCP_SERVICE_ACCOUNT_FOR_DEPLOYMENT"
AzureServicePrincipalCredentialsType AzureCredentialsType = "AZURE_SERVICE_PRINCIPAL_FOR_DEPLOYMENT"
)

type AwsCredentialsCreatePayload struct {
Expand All @@ -338,6 +340,13 @@ type GcpCredentialsCreatePayload struct {
Value GcpCredentialsValuePayload `json:"value"`
}

type AzureCredentialsCreatePayload struct {
Name string `json:"name"`
OrganizationId string `json:"organizationId"`
Type AzureCredentialsType `json:"type"`
Value AzureCredentialsValuePayload `json:"value"`
}

type AwsCredentialsValuePayload struct {
RoleArn string `json:"roleArn"`
ExternalId string `json:"externalId"`
Expand All @@ -350,6 +359,13 @@ type GcpCredentialsValuePayload struct {
ServiceAccountKey string `json:"serviceAccountKey"`
}

type AzureCredentialsValuePayload struct {
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
SubscriptionId string `json:"subscriptionId"`
TenantId string `json:"tenantId"`
}

type ApiKey struct {
Id string `json:"id"`
Name string `json:"name"`
Expand Down
23 changes: 23 additions & 0 deletions docs/data-sources/azure_credentials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "env0_azure_credentials Data Source - terraform-provider-env0"
subcategory: ""
description: |-
---

# env0_azure_credentials (Data Source)





<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- **id** (String) the id of the credential
- **name** (String) the name of the credential


7 changes: 0 additions & 7 deletions docs/resources/aws_credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,4 @@ resource "env0_aws_credentials" "credentials" {
- **id** (String) The ID of this resource.
- **secret_access_key** (String, Sensitive) the aws access key secret

## Import

Import is supported using the following syntax:

```shell
terraform import env0_aws_credentials.by_id d31a6b30-5f69-4d24-937c-22322754934e
terraform import env0_aws_credentials.by_name ProductionCredentials
```
40 changes: 40 additions & 0 deletions docs/resources/azure_credentials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "env0_azure_credentials Resource - terraform-provider-env0"
subcategory: ""
description: |-
---

# env0_azure_credentials (Resource)



## Example Usage

```terraform
resource "env0_azure_credentials" "credentials" {
name = "name"
client_id = "client_id"
client_secret = "client_secret"
subscription_id = "subscription_id"
tenant_id = "tenant_id"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- **client_id** (String) the azure client id
- **client_secret** (String, Sensitive) the azure client secret
- **name** (String) name for the credentials
- **subscription_id** (String) the azure subscription id
- **tenant_id** (String) the azure tenant id

### Optional

- **id** (String) The ID of this resource.


93 changes: 93 additions & 0 deletions env0/data_azure_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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 dataAzureCredentials() *schema.Resource {
return &schema.Resource{
ReadContext: dataAzureCredentialsRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "the name of the credential",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
"id": {
Type: schema.TypeString,
Description: "the id of the credential",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
},
}
}

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

id, ok := d.GetOk("id")
if ok {
credentials, err = getAzureCredentialsById(id.(string), meta)
if err != nil {
return err
}
} else {
name, _ := d.Get("name").(string) // name must be specified here
credentials, err = getAzureCredentialsByName(name, meta)
if err != nil {
return err
}
}

d.SetId(credentials.Id)
d.Set("name", credentials.Name)

return nil
}

func getAzureCredentialsByName(name interface{}, meta interface{}) (client.ApiKey, diag.Diagnostics) {
apiClient := meta.(client.ApiClientInterface)
credentialsList, err := apiClient.CloudCredentialsList()
if err != nil {
return client.ApiKey{}, diag.Errorf("Could not query Azure Credentials by name: %v", err)
}

credentialsByNameAndType := make([]client.ApiKey, 0)
for _, candidate := range credentialsList {
if candidate.Name == name.(string) && isValidAzureCredentialsType(candidate.Type) {
credentialsByNameAndType = append(credentialsByNameAndType, candidate)
}
}

if len(credentialsByNameAndType) > 1 {
return client.ApiKey{}, diag.Errorf("Found multiple Azure Credentials for name: %s", name)
}
if len(credentialsByNameAndType) == 0 {
return client.ApiKey{}, diag.Errorf("Could not find Azure Credentials with name: %s", name)
}
return credentialsByNameAndType[0], nil
}

func getAzureCredentialsById(id string, meta interface{}) (client.ApiKey, diag.Diagnostics) {
apiClient := meta.(client.ApiClientInterface)
credentials, err := apiClient.CloudCredentials(id)
if !isValidAzureCredentialsType(credentials.Type) {
return client.ApiKey{}, diag.Errorf("Found credentials which are not Azure Credentials: %v", credentials)
}
if err != nil {
return client.ApiKey{}, diag.Errorf("Could not query Azure Credentials: %v", err)
}
return credentials, nil
}

func isValidAzureCredentialsType(credentialsType string) bool {
return client.AzureCredentialsType(credentialsType) == client.AzureServicePrincipalCredentialsType
}
Loading

0 comments on commit dd8a54d

Please sign in to comment.