Skip to content

Commit

Permalink
Feat: add support for remote state access configuration (#634)
Browse files Browse the repository at this point in the history
* Feat: add support for remote state access configuration

* modified post to put

* update staticcheck

* update to golang 1.19

* added an example

* removed integration test

* id is now environment id
  • Loading branch information
TomerHeber committed Apr 17, 2023
1 parent 7192f61 commit 5bca0d1
Show file tree
Hide file tree
Showing 14 changed files with 411 additions and 8 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ env:
ENV0_API_ENDPOINT: ${{ secrets.ENV0_API_ENDPOINT }}
ENV0_API_KEY: ${{ secrets.TF_PROVIDER_INTEGRATION_TEST_API_KEY }} # API Key for organization 'TF-provider-integration-tests' @ dev
ENV0_API_SECRET: ${{ secrets.TF_PROVIDER_INTEGRATION_TEST_API_SECRET }}
GO_VERSION: 1.18
GO_VERSION: 1.19
TERRAFORM_VERSION: 1.1.7

jobs:
Expand All @@ -34,9 +34,9 @@ jobs:
run: |
! go vet ./... | read
- name: Go staticcheck
uses: dominikh/staticcheck-action@v1.2.0
uses: dominikh/staticcheck-action@v1.3.0
with:
version: "2022.1"
version: "2023.1.3"
install-go: false
- name: Go Test
run: go test -v ./...
Expand All @@ -45,7 +45,7 @@ jobs:
integration-tests:
name: Integration Tests
runs-on: ubuntu-20.04
container: golang:1.18-alpine3.16
container: golang:1.19-alpine3.17
timeout-minutes: 20
steps:
- name: Install Terraform
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- main

env:
GO_VERSION: 1.18
GO_VERSION: 1.19

jobs:
generate-docs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
- 'v*.*.*'

env:
GO_VERSION: 1.18
GO_VERSION: 1.19

jobs:
goreleaser:
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ resource "env0_configuration_variable" "in_a_template" {
template_id = env0_template.tested1.id
}
```

## Authentication

1. Generate an `api_key` and `api_secret` from the Organization Settings page.
Expand All @@ -65,7 +66,9 @@ resource "env0_configuration_variable" "in_a_template" {
```

### How to get VCS credentials for Creating a template or a VCS environment

To create an `env0_template` or a VCS `env0_environment` resources a user must provision the corresponding credentials:

1. `github_installation_id` for Github
2. `bitbucket_client_key` for Bitbucket
3. `gitlab_project_id` + `token_id` for Gitlab
Expand All @@ -89,7 +92,7 @@ resource "env0_template" "example" {

## Development Setup

> **Supported Go Version: 1.18**
> **Supported Go Version: 1.19**
### Build

Expand Down
3 changes: 3 additions & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ type ApiClientInterface interface {
CustomFlowGetAssignments(assignments []CustomFlowAssignment) ([]CustomFlowAssignment, error)
SubscribeWorkflowTrigger(environmentId string, payload WorkflowTriggerEnvironments) error
UnsubscribeWorkflowTrigger(environmentId string, payload WorkflowTriggerEnvironments) error
RemoteStateAccessConfiguration(environmentId string) (*RemoteStateAccessConfiguration, error)
RemoteStateAccessConfigurationCreate(environmentId string, payload RemoteStateAccessConfigurationCreate) (*RemoteStateAccessConfiguration, error)
RemoteStateAccessConfigurationDelete(environmentId string) error
}

func NewApiClient(client http.HttpClientInterface, defaultOrganizationId string) ApiClientInterface {
Expand Down
44 changes: 44 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.

35 changes: 35 additions & 0 deletions client/remote_state_access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package client

type RemoteStateAccessConfiguration struct {
EnvironmentId string `json:"environmentId"`
AccessibleFromEntireOrganization bool `json:"accessibleFromEntireOrganization"`
AllowedProjectIds []string `json:"allowedProjectIds" tfschema:",omitempty"`
}

type RemoteStateAccessConfigurationCreate struct {
AccessibleFromEntireOrganization bool `json:"accessibleFromEntireOrganization"`
AllowedProjectIds []string `json:"allowedProjectIds,omitempty"`
}

func (client *ApiClient) RemoteStateAccessConfiguration(environmentId string) (*RemoteStateAccessConfiguration, error) {
var result RemoteStateAccessConfiguration

if err := client.http.Get("/remote-backend/states/"+environmentId+"/access-control", nil, &result); err != nil {
return nil, err
}

return &result, nil
}

func (client *ApiClient) RemoteStateAccessConfigurationCreate(environmentId string, payload RemoteStateAccessConfigurationCreate) (*RemoteStateAccessConfiguration, error) {
var result RemoteStateAccessConfiguration
if err := client.http.Put("/remote-backend/states/"+environmentId+"/access-control", payload, &result); err != nil {
return nil, err
}

return &result, nil
}

func (client *ApiClient) RemoteStateAccessConfigurationDelete(environmentId string) error {
return client.http.Delete("/remote-backend/states/" + environmentId + "/access-control")
}
84 changes: 84 additions & 0 deletions client/remote_state_access_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package client_test

import (
. "github.com/env0/terraform-provider-env0/client"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("RemoteStateAccess", func() {
environmentId := "environmnet_id"

remoteStateAccess := RemoteStateAccessConfiguration{
EnvironmentId: environmentId,
AccessibleFromEntireOrganization: false,
AllowedProjectIds: []string{
"pid1",
},
}

Describe("Create", func() {
var err error
var remoteStateAccessResponse *RemoteStateAccessConfiguration

BeforeEach(func() {
createRequest := RemoteStateAccessConfigurationCreate{
AllowedProjectIds: remoteStateAccess.AllowedProjectIds,
}

httpCall = mockHttpClient.EXPECT().
Put("/remote-backend/states/"+environmentId+"/access-control", createRequest, gomock.Any()).
Do(func(path string, request interface{}, response *RemoteStateAccessConfiguration) {
*response = remoteStateAccess
})
httpCall.Times(1)
remoteStateAccessResponse, err = apiClient.RemoteStateAccessConfigurationCreate(environmentId, createRequest)
})

It("Should not return error", func() {
Expect(err).To(BeNil())
})

It("Should return created remote state access configuration", func() {
Expect(*remoteStateAccessResponse).To(Equal(remoteStateAccess))
})
})

Describe("Get", func() {
var err error
var remoteStateAccessResponse *RemoteStateAccessConfiguration

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().
Get("/remote-backend/states/"+environmentId+"/access-control", gomock.Nil(), gomock.Any()).
Do(func(path string, request interface{}, response *RemoteStateAccessConfiguration) {
*response = remoteStateAccess
})
httpCall.Times(1)
remoteStateAccessResponse, err = apiClient.RemoteStateAccessConfiguration(environmentId)
})

It("Should return remote state access configuration", func() {
Expect(*remoteStateAccessResponse).To(Equal(remoteStateAccess))
})

It("Should not return error", func() {
Expect(err).To(BeNil())
})
})

Describe("Delete", func() {
var err error

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Delete("/remote-backend/states/" + environmentId + "/access-control")
httpCall.Times(1)
err = apiClient.RemoteStateAccessConfigurationDelete(environmentId)
})

It("Should not return error", func() {
Expect(err).To(BeNil())
})
})
})
1 change: 1 addition & 0 deletions env0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func Provider(version string) plugin.ProviderFunc {
"env0_user_organization_assignment": resourceUserOrganizationAssignment(),
"env0_custom_flow": resourceCustomFlow(),
"env0_custom_flow_assignment": resourceCustomFlowAssignment(),
"env0_environment_state_access": resourceEnvironmentStateAccess(),
},
}

Expand Down
95 changes: 95 additions & 0 deletions env0/resource_environment_state_access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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 resourceEnvironmentStateAccess() *schema.Resource {
return &schema.Resource{
CreateContext: resourceEnvironmentStateAccessCreate,
ReadContext: resourceEnvironmentStateAccessRead,
DeleteContext: resourceEnvironmentStateAccessDelete,

Schema: map[string]*schema.Schema{
"environment_id": {
Type: schema.TypeString,
Description: "id of the environment",
Required: true,
ForceNew: true,
},
"accessible_from_entire_organization": {
Type: schema.TypeBool,
Description: "when this parameter is 'false', allowed_project_ids should be provided. Defaults to 'false'",
Optional: true,
Default: false,
ForceNew: true,
},
"allowed_project_ids": {
Type: schema.TypeList,
Description: "list of allowed project_ids. Used when 'accessible_from_entire_organization' is 'false'",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func resourceEnvironmentStateAccessCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
environmentId := d.Get("environment_id").(string)

var payload client.RemoteStateAccessConfigurationCreate
if err := readResourceData(&payload, d); err != nil {
return diag.Errorf("schema resource data deserialization failed: %v", err)
}

if payload.AccessibleFromEntireOrganization && payload.AllowedProjectIds != nil {
return diag.Errorf("'allowed_project_ids' should not be set when 'accessible_from_entire_organization' is set to 'true'")
}

apiClient := meta.(client.ApiClientInterface)

remoteStateAccess, err := apiClient.RemoteStateAccessConfigurationCreate(environmentId, payload)
if err != nil {
return diag.Errorf("could not create a remote state access configation: %v", err)
}

d.SetId(remoteStateAccess.EnvironmentId)

return nil
}

func resourceEnvironmentStateAccessRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
environmentId := d.Get("environment_id").(string)

apiClient := meta.(client.ApiClientInterface)

remoteStateAccess, err := apiClient.RemoteStateAccessConfiguration(environmentId)
if err != nil {
return ResourceGetFailure("remote state access configation", d, err)
}

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

return nil
}

func resourceEnvironmentStateAccessDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
environmentId := d.Get("environment_id").(string)

apiClient := meta.(client.ApiClientInterface)

if err := apiClient.RemoteStateAccessConfigurationDelete(environmentId); err != nil {
return diag.Errorf("could not delete remote state access configation: %v", err)
}

return nil
}
Loading

0 comments on commit 5bca0d1

Please sign in to comment.