Skip to content

Commit

Permalink
Merge pull request #63 from cloudsmith-io/NO-TICKET/optional_api_key
Browse files Browse the repository at this point in the history
Add optional flag to disable api key return
  • Loading branch information
BartoszBlizniak authored Oct 24, 2023
2 parents 649c54b + 8a192dc commit c2c2930
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 24 deletions.
55 changes: 34 additions & 21 deletions cloudsmith/resource_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ func resourceServiceCreate(ctx context.Context, d *schema.ResourceData, m interf
// Unfortunately this means that if the user needs to rotate the service's
// API key then the only way to do it and have it reflected properly in
// Terraform is to taint the whole resource and let Terraform recreate it.
d.Set("key", service.GetKey())

if requiredBool(d, "store_api_key") {
d.Set("key", service.GetKey())
}
checkerFunc := func() error {
req := pc.APIClient.OrgsApi.OrgsServicesRead(pc.Auth, org, d.Id())
if _, resp, err := pc.APIClient.OrgsApi.OrgsServicesReadExecute(req); err != nil {
Expand All @@ -131,6 +132,8 @@ func resourceServiceRead(ctx context.Context, d *schema.ResourceData, m interfac

req := pc.APIClient.OrgsApi.OrgsServicesRead(pc.Auth, org, d.Id())

const lastFourChars = 4

service, resp, err := pc.APIClient.OrgsApi.OrgsServicesReadExecute(req)
if err != nil {
if is404(resp) {
Expand All @@ -152,30 +155,34 @@ func resourceServiceRead(ctx context.Context, d *schema.ResourceData, m interfac
// they'll need to recreate the resource if they want to pull the new key
// into Terraform. This can be accomplished by tainting.
var diags diag.Diagnostics
existingKey := requiredString(d, "key")
if existingKey == importSentinel {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "API key unavailable for imported services",
Detail: "API keys are only available via the Cloudsmith API at the time a service " +
"is created, and therefore it is not possible to retrieve the current API key for " +
"a service which has been imported. If the API key value is needed within Terraform" +
"then the resource can be tainted post-import to recreate it and store the key.",
AttributePath: cty.Path{cty.GetAttrStep{Name: "key"}},
})
} else {
existingLastFour := existingKey[len(existingKey)-4:]
newLastFour := service.GetKey()[len(service.GetKey())-4:]
if existingLastFour != newLastFour {
if requiredBool(d, "store_api_key") {
existingKey := requiredString(d, "key")
if existingKey == importSentinel {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "API key has changed",
Detail: "API key for this service has changed outside of Terraform. If this " +
"key is used within Terraform the resource must be tainted or otherwise " +
"recreated to retrieve the new value.",
Summary: "API key unavailable for imported services",
Detail: "API keys are only available via the Cloudsmith API at the time a service " +
"is created, and therefore it is not possible to retrieve the current API key for " +
"a service which has been imported. If the API key value is needed within Terraform" +
"then the resource can be tainted post-import to recreate it and store the key.",
AttributePath: cty.Path{cty.GetAttrStep{Name: "key"}},
})
} else {
existingLastFour := existingKey[len(existingKey)-lastFourChars:]
newLastFour := service.GetKey()[len(service.GetKey())-lastFourChars:]
if existingLastFour != newLastFour {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "API key has changed",
Detail: "API key for this service has changed outside of Terraform. If this " +
"key is used within Terraform, the resource must be tainted or otherwise " +
"recreated to retrieve the new value.",
AttributePath: cty.Path{cty.GetAttrStep{Name: "key"}},
})
}
}
} else {
d.Set("key", "**redacted**")
}

// organization is not returned from the service read endpoint, so we can
Expand Down Expand Up @@ -318,6 +325,12 @@ func resourceService() *schema.Resource {
},
Optional: true,
},
"store_api_key": {
Type: schema.TypeBool,
Description: "Whether to include the service's API key in Terraform state.",
Optional: true,
Default: true,
},
},
}
}
19 changes: 18 additions & 1 deletion cloudsmith/resource_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
// NOTE: It is not necessary to check properties that have been explicitly set
// as Terraform performs a drift/plan check after every step anyway. Only
// computed properties need explicitly checked.
// TestAccService_basic runs a series of tests for the cloudsmith_service resource.
func TestAccService_basic(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -69,6 +70,14 @@ func TestAccService_basic(t *testing.T) {
}),
),
},
{
Config: testAccServiceConfigNoAPIKey,
Check: resource.ComposeTestCheckFunc(
testAccServiceCheckExists("cloudsmith_service.test"),
// check that the key attribute is explicitly an empty string
resource.TestCheckResourceAttr("cloudsmith_service.test", "key", "**redacted**"),
),
},
{
ResourceName: "cloudsmith_service.test",
ImportState: true,
Expand All @@ -81,7 +90,7 @@ func TestAccService_basic(t *testing.T) {
), nil
},
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"key"},
ImportStateVerifyIgnore: []string{"key", "store_api_key"},
},
},
})
Expand Down Expand Up @@ -139,6 +148,14 @@ func testAccServiceCheckExists(resourceName string) resource.TestCheckFunc {
}
}

var testAccServiceConfigNoAPIKey = fmt.Sprintf(`
resource "cloudsmith_service" "test" {
name = "TF Test Service No API Key"
organization = "%s"
store_api_key = false
}
`, os.Getenv("CLOUDSMITH_NAMESPACE"))

var testAccServiceConfigBasic = fmt.Sprintf(`
resource "cloudsmith_service" "test" {
name = "TF Test Service"
Expand Down
4 changes: 2 additions & 2 deletions docs/resources/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ The following arguments are supported:
* `team` - (Optional) Variable number of blocks containing team assignments for this service.
* `role` - (Optional) The service's role in the team. If defined, must be one of `Member` or `Manager`.
* `slug` - (Required) The team the service should be added to.

* `store_api_key` - (Optional) The service's API key to be returned in state. Defaults to `true`. If set to `false`, the "key" value is replaced with `disabled`
## Attribute Reference

In addition to all arguments above, the following attributes are exported:

* `key` - The service's API key.
* `key` - The service's API key. If `store_api_key` is set to false, the value returned will equal to `disabled`
* `slug` - The slug identifies the service in URIs or where a username is required.

## Import
Expand Down

0 comments on commit c2c2930

Please sign in to comment.