Skip to content

Commit

Permalink
ENG-8182, ENG-8215: Add Access Gateway support for repo binding resou…
Browse files Browse the repository at this point in the history
…rce (#180)

* Add SelectSidecarAsIdpAccessGateway to repo binding resource

* Update repo binding tests

* Rename select_sidecar_as_idp_access_gateway argument
  • Loading branch information
VictorGFM authored Mar 31, 2022
1 parent 3a502bf commit dacaaeb
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 68 deletions.
74 changes: 41 additions & 33 deletions cyral/resource_cyral_repository_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import (
)

type RepoBindingData struct {
SidecarID string
RepositoryID string
Enabled bool
Listener Listener `json:"listener"`
SidecarID string
RepositoryID string
Enabled bool
SidecarAsIdPAccessGateway bool `json:"isSelectedIdentityProviderSidecar,omitempty"`
Listener Listener `json:"listener"`
}

type Listener struct {
Expand Down Expand Up @@ -56,6 +57,11 @@ func resourceRepositoryBinding() *schema.Resource {
Optional: true,
Default: "0.0.0.0",
},
"sidecar_as_idp_access_gateway": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
Expand All @@ -67,10 +73,7 @@ func resourceRepositoryBindingCreate(ctx context.Context, d *schema.ResourceData
log.Printf("[DEBUG] Init resourceRepositoryBindingCreate")
c := m.(*client.Client)

resourceData, err := getRepoBindingDataFromResource(c, d)
if err != nil {
return createError("Unable to bind repository to sidecar", fmt.Sprintf("%v", err))
}
resourceData := getRepoBindingDataFromResource(d)

url := fmt.Sprintf("https://%s/v1/sidecars/%s/repos/%s", c.ControlPlane,
resourceData.SidecarID, resourceData.RepositoryID)
Expand All @@ -80,8 +83,6 @@ func resourceRepositoryBindingCreate(ctx context.Context, d *schema.ResourceData
}

d.SetId(fmt.Sprintf("%s-%s", resourceData.SidecarID, resourceData.RepositoryID))
d.Set("sidecar_id", resourceData.SidecarID)
d.Set("repository_id", resourceData.RepositoryID)

return resourceRepositoryBindingRead(ctx, d, m)
}
Expand All @@ -101,19 +102,15 @@ func resourceRepositoryBindingRead(ctx context.Context, d *schema.ResourceData,
sidecarID, repositoryID), fmt.Sprintf("%v", err))
}

response := RepoBindingData{
SidecarID: sidecarID,
RepositoryID: repositoryID,
}
response := RepoBindingData{}
if err := json.Unmarshal(body, &response); err != nil {
return createError(fmt.Sprintf("Unable to unmarshall JSON. SidecarID: %s, RepositoryID: %s",
sidecarID, repositoryID), fmt.Sprintf("%v", err))
}
log.Printf("[DEBUG] Response body (unmarshalled): %#v", response)

d.Set("enabled", response.Enabled)
d.Set("sidecar_id", response.SidecarID)
d.Set("repository_id", response.RepositoryID)
d.Set("sidecar_as_idp_access_gateway", response.SidecarAsIdPAccessGateway)
d.Set("listener_port", response.Listener.Port)
if host := response.Listener.Host; host != "" {
d.Set("listener_host", response.Listener.Host)
Expand All @@ -127,16 +124,10 @@ func resourceRepositoryBindingUpdate(ctx context.Context, d *schema.ResourceData
log.Printf("[DEBUG] Init resourceRepositoryBindingUpdate")
c := m.(*client.Client)

resourceData, err := getRepoBindingDataFromResource(c, d)
if err != nil {
return createError("Unable to update repository", fmt.Sprintf("%v", err))
}

url := fmt.Sprintf("https://%s/v1/sidecars/%s/repos/%s", c.ControlPlane,
resourceData.SidecarID, resourceData.RepositoryID)
resourceData := getRepoBindingDataFromResource(d)

if _, err = c.DoRequest(url, http.MethodPut, resourceData); err != nil {
return createError("Unable to update repository", fmt.Sprintf("%v", err))
if err := updateRepositoryBinding(c, resourceData); err != nil {
return createError("Unable to update repository binding", fmt.Sprintf("%v", err))
}

log.Printf("[DEBUG] End resourceRepositoryBindingUpdate")
Expand All @@ -148,10 +139,19 @@ func resourceRepositoryBindingDelete(ctx context.Context, d *schema.ResourceData
log.Printf("[DEBUG] Init resourceRepositoryBindingDelete")
c := m.(*client.Client)

sidecarID := d.Get("sidecar_id").(string)
repositoryID := d.Get("repository_id").(string)
// SidecarAsIdPAccessGateway is set to false to stop
// using the bound sidecar as the Access Gateway for Identity
// Provider users. This is needed so that the binding can
// be deleted, otherwise it will throw a validation error.
resourceData := getRepoBindingDataFromResource(d)
resourceData.SidecarAsIdPAccessGateway = false
if err := updateRepositoryBinding(c, resourceData); err != nil {
return createError("Unable to delete repository binding",
fmt.Sprintf("%v", err))
}

url := fmt.Sprintf("https://%s/v1/sidecars/%s/repos/%s", c.ControlPlane, sidecarID, repositoryID)
url := fmt.Sprintf("https://%s/v1/sidecars/%s/repos/%s", c.ControlPlane,
resourceData.SidecarID, resourceData.RepositoryID)

if _, err := c.DoRequest(url, http.MethodDelete, nil); err != nil {
return createError("Unable to delete repository binding", fmt.Sprintf("%v", err))
Expand All @@ -162,14 +162,22 @@ func resourceRepositoryBindingDelete(ctx context.Context, d *schema.ResourceData
return diag.Diagnostics{}
}

func getRepoBindingDataFromResource(c *client.Client, d *schema.ResourceData) (RepoBindingData, error) {
func getRepoBindingDataFromResource(d *schema.ResourceData) RepoBindingData {
return RepoBindingData{
Enabled: d.Get("enabled").(bool),
SidecarID: d.Get("sidecar_id").(string),
RepositoryID: d.Get("repository_id").(string),
Enabled: d.Get("enabled").(bool),
SidecarID: d.Get("sidecar_id").(string),
RepositoryID: d.Get("repository_id").(string),
SidecarAsIdPAccessGateway: d.Get("sidecar_as_idp_access_gateway").(bool),
Listener: Listener{
Host: d.Get("listener_host").(string),
Port: d.Get("listener_port").(int),
},
}, nil
}
}

func updateRepositoryBinding(c *client.Client, resourceData RepoBindingData) error {
url := fmt.Sprintf("https://%s/v1/sidecars/%s/repos/%s", c.ControlPlane,
resourceData.SidecarID, resourceData.RepositoryID)
_, err := c.DoRequest(url, http.MethodPut, resourceData)
return err
}
147 changes: 114 additions & 33 deletions cyral/resource_cyral_repository_binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,83 +7,139 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

var initialRepositoryBindingConfig RepoBindingData = RepoBindingData{
var initialConfig RepoBindingData = RepoBindingData{
SidecarID: "1",
RepositoryID: "1",
Enabled: false,
Listener: Listener{
Host: "host.com",
Port: 3333,
Port: 1234,
},
}

var updatedRepositoryBindingConfig RepoBindingData = RepoBindingData{
var updatedConfig RepoBindingData = RepoBindingData{
SidecarID: "2",
RepositoryID: "2",
Enabled: true,
Listener: Listener{
Host: "host-updated.com",
Port: 3334,
Port: 4321,
},
Enabled: true,
SidecarAsIdPAccessGateway: false,
}

func TestAccRepositoryBindingResource(t *testing.T) {
testConfig, testFunc := setupRepositoryBindingTest(initialRepositoryBindingConfig)
testUpdateConfig, testUpdateFunc := setupRepositoryBindingTest(updatedRepositoryBindingConfig)

resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testConfig,
Check: testFunc,
Config: testAccRepositoryBindingConfig_DefaultValues(),
Check: testAccRepositoryBindingCheck_DefaultValues(),
},
{
Config: testUpdateConfig,
Check: testUpdateFunc,
Config: testAccRepositoryBindingConfig_UpdatedIDs(),
Check: testAccRepositoryBindingCheck_UpdatedIDs(),
},
{
Config: testAccRepositoryBindingConfig_AccessGatewayEnabled(),
Check: testAccRepositoryBindingCheck_AccessGatewayEnabled(),
},
},
})
}

func setupRepositoryBindingTest(integrationData RepoBindingData) (string, resource.TestCheckFunc) {
configuration := formatRepoBindingDataIntoConfig(integrationData)
func testAccRepositoryBindingConfig_DefaultValues() string {
return fmt.Sprintf(`
resource "cyral_sidecar" "test_repo_binding_sidecar_1" {
name = "tf-provider-repo-binding-sidecar-1"
deployment_method = "cloudFormation"
}
resource "cyral_repository" "test_repo_binding_repository_1" {
name = "tf-provider-repo-binding-repo-1"
type = "mongodb"
host = "mongodb.cyral.com"
port = 27017
}
resource "cyral_repository_binding" "repo_binding" {
sidecar_id = cyral_sidecar.test_repo_binding_sidecar_%s.id
repository_id = cyral_repository.test_repo_binding_repository_%s.id
listener_port = %d
}`, initialConfig.SidecarID, initialConfig.RepositoryID, initialConfig.Listener.Port)
}

func testAccRepositoryBindingCheck_DefaultValues() resource.TestCheckFunc {
sidecarResource := fmt.Sprintf("cyral_sidecar.test_repo_binding_sidecar_%s",
integrationData.SidecarID)
initialConfig.SidecarID)
repositoryResource := fmt.Sprintf("cyral_repository.test_repo_binding_repository_%s",
integrationData.RepositoryID)
initialConfig.RepositoryID)

testFunction := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding", "enabled",
fmt.Sprintf("%t", integrationData.Enabled)),
return resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair("cyral_repository_binding.repo_binding", "repository_id",
repositoryResource, "id"),
resource.TestCheckResourceAttrPair("cyral_repository_binding.repo_binding", "sidecar_id",
sidecarResource, "id"),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding", "listener_host",
integrationData.Listener.Host),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding", "listener_port",
fmt.Sprintf("%d", integrationData.Listener.Port)),
fmt.Sprintf("%d", initialConfig.Listener.Port)),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"listener_host", "0.0.0.0"),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"enabled", "true"),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"sidecar_as_idp_access_gateway", "false"),
)

return configuration, testFunction
}

func formatRepoBindingDataIntoConfig(data RepoBindingData) string {
func testAccRepositoryBindingConfig_UpdatedIDs() string {
return fmt.Sprintf(`
resource "cyral_sidecar" "test_repo_binding_sidecar_1" {
name = "tf-provider-repo-binding-sidecar-1"
resource "cyral_sidecar" "test_repo_binding_sidecar_2" {
name = "tf-provider-repo-binding-sidecar-2"
deployment_method = "cloudFormation"
}
resource "cyral_repository" "test_repo_binding_repository_1" {
name = "tf-provider-repo-binding-repo-1"
resource "cyral_repository" "test_repo_binding_repository_2" {
name = "tf-provider-repo-binding-repo-2"
type = "mongodb"
host = "mongodb.cyral.com"
port = 27017
}
resource "cyral_repository_binding" "repo_binding" {
sidecar_id = cyral_sidecar.test_repo_binding_sidecar_%s.id
repository_id = cyral_repository.test_repo_binding_repository_%s.id
listener_port = %d
listener_host = "%s"
enabled = %t
sidecar_as_idp_access_gateway = %t
}`, updatedConfig.SidecarID, updatedConfig.RepositoryID,
updatedConfig.Listener.Port, updatedConfig.Listener.Host,
updatedConfig.Enabled, updatedConfig.SidecarAsIdPAccessGateway)
}

func testAccRepositoryBindingCheck_UpdatedIDs() resource.TestCheckFunc {
sidecarResource := fmt.Sprintf("cyral_sidecar.test_repo_binding_sidecar_%s",
updatedConfig.SidecarID)
repositoryResource := fmt.Sprintf("cyral_repository.test_repo_binding_repository_%s",
updatedConfig.RepositoryID)

return resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair("cyral_repository_binding.repo_binding", "repository_id",
repositoryResource, "id"),
resource.TestCheckResourceAttrPair("cyral_repository_binding.repo_binding", "sidecar_id",
sidecarResource, "id"),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding", "listener_port",
fmt.Sprintf("%d", updatedConfig.Listener.Port)),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"listener_host", updatedConfig.Listener.Host),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"enabled", "true"),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"sidecar_as_idp_access_gateway", "false"),
)
}

func testAccRepositoryBindingConfig_AccessGatewayEnabled() string {
return fmt.Sprintf(`
resource "cyral_sidecar" "test_repo_binding_sidecar_2" {
name = "tf-provider-repo-binding-sidecar-2"
deployment_method = "cloudFormation"
Expand All @@ -97,10 +153,35 @@ func formatRepoBindingDataIntoConfig(data RepoBindingData) string {
}
resource "cyral_repository_binding" "repo_binding" {
enabled = %t
sidecar_id = cyral_sidecar.test_repo_binding_sidecar_%s.id
repository_id = cyral_repository.test_repo_binding_repository_%s.id
listener_host = "%s"
listener_port = %d
}`, data.Enabled, data.SidecarID, data.RepositoryID, data.Listener.Host, data.Listener.Port)
listener_host = "%s"
enabled = %t
sidecar_as_idp_access_gateway = true
}`, updatedConfig.SidecarID, updatedConfig.RepositoryID,
updatedConfig.Listener.Port, updatedConfig.Listener.Host,
updatedConfig.Enabled)
}

func testAccRepositoryBindingCheck_AccessGatewayEnabled() resource.TestCheckFunc {
sidecarResource := fmt.Sprintf("cyral_sidecar.test_repo_binding_sidecar_%s",
updatedConfig.SidecarID)
repositoryResource := fmt.Sprintf("cyral_repository.test_repo_binding_repository_%s",
updatedConfig.RepositoryID)

return resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair("cyral_repository_binding.repo_binding", "repository_id",
repositoryResource, "id"),
resource.TestCheckResourceAttrPair("cyral_repository_binding.repo_binding", "sidecar_id",
sidecarResource, "id"),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding", "listener_port",
fmt.Sprintf("%d", updatedConfig.Listener.Port)),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"listener_host", updatedConfig.Listener.Host),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"enabled", "true"),
resource.TestCheckResourceAttr("cyral_repository_binding.repo_binding",
"sidecar_as_idp_access_gateway", "true"),
)
}
4 changes: 3 additions & 1 deletion docs/resources/repository_binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ Allows [binding repositories to sidecars](https://cyral.com/docs/sidecars/sideca

```hcl
resource "cyral_repository_binding" "some_resource_name" {
enabled = true|false
enabled = true
repository_id = cyral_repository.SOME_REPOSITORY_RESOURCE_NAME.id
sidecar_id = cyral_sidecar.SOME_SIDECAR_RESOURCE_NAME.id
listener_port = 0
listener_host = "0.0.0.0"
sidecar_as_idp_access_gateway = false
}
```

Expand Down Expand Up @@ -75,6 +76,7 @@ resource "cyral_repository_binding" "repo_binding" {
- `sidecar_id` - (Required) ID of the sidecar that the repository(ies) will be bound to.
- `listener_port` - (Required) Port in which the sidecar will listen for the given repository.
- `listener_host` - (Optional) Address in which the sidecar will listen for the given repository. By default, the sidecar will listen in all interfaces.
- `sidecar_as_idp_access_gateway` - (Optional) Indicates whether or not the sidecar in the binding configuration is selected as the Access Gateway for Identity Provider users connecting to the underlying data repository. Defaults to `false`.

## Attribute Reference

Expand Down
2 changes: 1 addition & 1 deletion docs/resources/repository_conf_auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ resource "cyral_repository_conf_auth" "some_resource_name" {
- `repository_id` - (Required) The ID of the repository to be configured.
- `allow_native_auth` - (Optional) Should the communication allow native authentication?
- `client_tls` - (Optional) Is the repo Client using TLS?
- `identity_provider` - (Optional) The name of the identity provider.
- `identity_provider` - (Optional) The ID (Alias) of the identity provider integration.
- `repo_tls` - (Optional) Is TLS enabled for the repository?

## Attribute Reference
Expand Down

0 comments on commit dacaaeb

Please sign in to comment.