Skip to content

Commit

Permalink
prepare 1.4.0 release (#37)
Browse files Browse the repository at this point in the history
* Prep for migration release (#75)

* add goreleaser.yml

* Update changelog

* fix version number

* Add GH actions for release (#76)

* Add GH actions for release

* Bump travis version

* Update readme

* Automatically add changelog entry to release (#77)

* Update go.mod and automatically set version header (#78)

* Add access tokens (#79)

* prepare 1.2.2 release (#24)

* Fix non-empty plan when creating a team member with a custom role (#63)

* Fix handling of missing user target variation in API response (#64)

Co-authored-by: Isabelle Miller <[email protected]>

* v1.2.2

* Cleanup after v1.2.2 release

* [ch85469] first pass at tokens

* Update launchdarkly/resource_launchdarkly_access_token.go

Co-authored-by: Henry Barrow <[email protected]>

* add links to sensitive data callout

* expiry and permission fixes

* update markdown

* use latest openapi client

* keys

* tidy

* fixes

* docs update

* Fix go.mod and vendoring

* Remove henry's fmt.Printf

* Clean up go.mod

* clean up go.mod even further

* address some of review

* custom role keys instead of ids

* remove unused var

* reintroduce removed code

* remove references to deleted key

* Remove fmt.Println

* formatting fixes

* fix tests

Co-authored-by: Henry Barrow <[email protected]>
Co-authored-by: Isabelle Miller <[email protected]>
Co-authored-by: tf-release-bot <[email protected]>

* update changelog for 1.4.0

Co-authored-by: Henry Barrow <[email protected]>
Co-authored-by: Isabelle Miller <[email protected]>
Co-authored-by: tf-release-bot <[email protected]>
  • Loading branch information
4 people authored Aug 21, 2020
1 parent 9d7aac3 commit 4298e18
Show file tree
Hide file tree
Showing 914 changed files with 55,851 additions and 92,902 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## [Unreleased]

FEATURES:

- Added the `launchdarkly_access_token` resource.

## [1.3.4] (August 3, 2020)

NOTES:
Expand Down
15 changes: 4 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,8 @@ module github.com/launchdarkly/terraform-provider-launchdarkly
go 1.14

require (
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/terraform-plugin-sdk v1.0.0
github.com/launchdarkly/api-client-go v3.2.0+incompatible
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 // indirect
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
github.com/antihax/optional v1.0.0
github.com/hashicorp/terraform-plugin-sdk v1.15.0
github.com/launchdarkly/api-client-go v3.2.1-0.20200812163818-441cfcaaa92c+incompatible
github.com/stretchr/testify v1.6.1
)

replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
123 changes: 67 additions & 56 deletions go.sum

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion launchdarkly/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ type Client struct {
}

func newClient(token string, apiHost string, oauth bool) (*Client, error) {
fmt.Printf("version %s", version)
if token == "" {
return nil, errors.New("token cannot be empty")
}
Expand Down
4 changes: 4 additions & 0 deletions launchdarkly/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,8 @@ const (
CONFIG = "config"
DEFAULT_ON_VARIATION = "default_on_variation"
DEFAULT_OFF_VARIATION = "default_off_variation"
SERVICE_TOKEN = "service_token"
DEFAULT_API_VERSION = "default_api_version"
TOKEN = "token"
EXPIRE = "expire"
)
1 change: 1 addition & 0 deletions launchdarkly/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func Provider() terraform.ResourceProvider {
"launchdarkly_team_member": resourceTeamMember(),
"launchdarkly_feature_flag_environment": resourceFeatureFlagEnvironment(),
"launchdarkly_destination": resourceDestination(),
"launchdarkly_access_token": resourceAccessToken(),
},
DataSourcesMap: map[string]*schema.Resource{
"launchdarkly_team_member": dataSourceTeamMember(),
Expand Down
266 changes: 266 additions & 0 deletions launchdarkly/resource_launchdarkly_access_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package launchdarkly

import (
"fmt"
"log"
"net/http"

"github.com/antihax/optional"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"

ldapi "github.com/launchdarkly/api-client-go"
)

func resourceAccessToken() *schema.Resource {
tokenPolicySchema := policyStatementsSchema()
tokenPolicySchema.Description = "A list of policy statements defining the permissions for the token. May be used in place of a built-in or custom role."
return &schema.Resource{
Create: resourceAccessTokenCreate,
Read: resourceAccessTokenRead,
Update: resourceAccessTokenUpdate,
Delete: resourceAccessTokenDelete,
Exists: resourceAccessTokenExists,

Schema: map[string]*schema.Schema{
NAME: {
Type: schema.TypeString,
Description: "The human-readable name of the access token",
Required: true,
},
ROLE: {
Type: schema.TypeString,
Description: "The name of a built-in role for the token",
Optional: true,
ValidateFunc: validateTeamMemberRole,
},
CUSTOM_ROLES: {
Type: schema.TypeSet,
Description: "A set of custom role keys to use as access limits for the access token",
Set: schema.HashString,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
POLICY_STATEMENTS: tokenPolicySchema,
SERVICE_TOKEN: {
Type: schema.TypeBool,
Description: "Whether the token will be a service token https://docs.launchdarkly.com/home/account-security/api-access-tokens#service-tokens",
Optional: true,
ForceNew: true,
Default: false,
},
DEFAULT_API_VERSION: {
Type: schema.TypeInt,
Description: "The default API version for this token. Defaults to the latest API version.",
Optional: true,
ForceNew: true,
Computed: true,
},
TOKEN: {
Type: schema.TypeString,
Description: "The secret key used to authorize usage of the LaunchDarkly API",
Computed: true,
Sensitive: true,
},
EXPIRE: {
Type: schema.TypeInt,
Description: "Replace the computed token secret with a new value. The expired secret will no longer be able to authorize usage of the LaunchDarkly API. Should be an expiration time for the current token secret, expressed as a Unix epoch time in milliseconds. Setting this to a negative value will expire the existing token immediately. To reset the token value again, change 'expire' to a new value. Setting this field at resource creation time WILL NOT set an expiration time for the token.",
Optional: true,
},
},
}
}

func resourceAccessTokenCreate(d *schema.ResourceData, metaRaw interface{}) error {
client := metaRaw.(*Client)
accessTokenName := d.Get(NAME).(string)
accessTokenRole := d.Get(ROLE).(string)
serviceToken := d.Get(SERVICE_TOKEN).(bool)
defaultApiVersion := d.Get(DEFAULT_API_VERSION).(int)
customRolesRaw := d.Get(CUSTOM_ROLES).(*schema.Set).List()

customRoles := make([]string, len(customRolesRaw))
for i, cr := range customRolesRaw {
customRoles[i] = cr.(string)
}
policyStatements, err := policyStatementsFromResourceData(d)
if err != nil {
return err
}

accessTokenBody := ldapi.TokenBody{
Name: accessTokenName,
Role: accessTokenRole,
CustomRoleIds: customRoles,
InlineRole: policyStatements,
ServiceToken: serviceToken,
DefaultApiVersion: int32(defaultApiVersion),
}

tokenRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) {
return client.ld.AccessTokensApi.PostToken(client.ctx, accessTokenBody)
})
token := tokenRaw.(ldapi.Token)
if err != nil {
return fmt.Errorf("failed to create access token with name %q: %s", accessTokenName, handleLdapiErr(err))
}

_ = d.Set(TOKEN, token.Token)
d.SetId(token.Id)
return resourceAccessTokenRead(d, metaRaw)
}

func resourceAccessTokenRead(d *schema.ResourceData, metaRaw interface{}) error {
client := metaRaw.(*Client)
accessTokenID := d.Id()

accessTokenRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) {
return client.ld.AccessTokensApi.GetToken(client.ctx, accessTokenID)
})
accessToken := accessTokenRaw.(ldapi.Token)
if isStatusNotFound(res) {
log.Printf("[WARN] failed to find access token with id %q, removing from state", accessTokenID)
d.SetId("")
return nil
}
if err != nil {
return fmt.Errorf("failed to get access token with id %q: %s", accessTokenID, handleLdapiErr(err))
}

_ = d.Set(NAME, accessToken.Name)
if accessToken.Role != "" {
_ = d.Set(ROLE, accessToken.Role)
}
if len(accessToken.CustomRoleIds) > 0 {
customRoleKeys, err := customRoleIDsToKeys(client, accessToken.CustomRoleIds)
if err != nil {
return err
}
_ = d.Set(CUSTOM_ROLES, customRoleKeys)
}
_ = d.Set(SERVICE_TOKEN, accessToken.ServiceToken)
_ = d.Set(DEFAULT_API_VERSION, accessToken.DefaultApiVersion)

policies := accessToken.InlineRole
if len(policies) > 0 {
err = d.Set(POLICY_STATEMENTS, policyStatementsToResourceData(policies))
if err != nil {
return fmt.Errorf("could not set policy on access token with id %q: %v", accessTokenID, err)
}
}

return nil
}

func resourceAccessTokenUpdate(d *schema.ResourceData, metaRaw interface{}) error {
client := metaRaw.(*Client)
accessTokenID := d.Id()
accessTokenName := d.Get(NAME).(string)
accessTokenRole := d.Get(ROLE).(string)
customRolesRaw := d.Get(CUSTOM_ROLES).(*schema.Set).List()

customRoleKeys := make([]string, len(customRolesRaw))
for i, cr := range customRolesRaw {
customRoleKeys[i] = cr.(string)
}
customRoleIds, err := customRoleKeysToIDs(client, customRoleKeys)
if err != nil {
return err
}

policyStatements, err := policyStatementsFromResourceData(d)
if err != nil {
return err
}
p := statementsToPolicies(policyStatements)

patch := []ldapi.PatchOperation{
patchReplace("/name", &accessTokenName),
}

if d.HasChange(ROLE) {
var op ldapi.PatchOperation
if accessTokenRole == "" {
op = patchRemove("/role")
} else {
op = patchReplace("/role", &accessTokenRole)
}
patch = append(patch, op)
}
if d.HasChange(CUSTOM_ROLES) {
var op ldapi.PatchOperation
if len(customRoleIds) == 0 {
op = patchRemove("/customRoleIds")
} else {
op = patchReplace("/customRoleIds", &customRoleIds)
}
patch = append(patch, op)
}
if d.HasChange(POLICY_STATEMENTS) {
var op ldapi.PatchOperation
if len(p) == 0 {
op = patchRemove("/inlineRole")
} else {
op = patchReplace("/inlineRole", &p)
}
patch = append(patch, op)
}

_, _, err = handleRateLimit(func() (interface{}, *http.Response, error) {
return client.ld.AccessTokensApi.PatchToken(client.ctx, accessTokenID, patch)
})
if err != nil {
return fmt.Errorf("failed to update access token with id %q: %s", accessTokenID, handleLdapiErr(err))
}

// Reset the access token if the expire field has been updated
if d.HasChange(EXPIRE) {
oldExpireRaw, newExpireRaw := d.GetChange(EXPIRE)
oldExpire := oldExpireRaw.(int)
newExpire := newExpireRaw.(int)
opts := ldapi.ResetTokenOpts{}
if oldExpire != newExpire && newExpire != 0 {
if newExpire > 0 {
opts.Expiry = optional.NewInt64(int64(newExpire))
}
token, _, err := client.ld.AccessTokensApi.ResetToken(client.ctx, accessTokenID, &opts)
if err != nil {
return fmt.Errorf("failed to reset access token with id %q: %s", accessTokenID, handleLdapiErr(err))
}
d.Set(EXPIRE, newExpire)
d.Set(TOKEN, token.Token)
}
}

return resourceAccessTokenRead(d, metaRaw)
}

func resourceAccessTokenDelete(d *schema.ResourceData, metaRaw interface{}) error {
client := metaRaw.(*Client)
accessTokenID := d.Id()

_, _, err := handleRateLimit(func() (interface{}, *http.Response, error) {
res, err := client.ld.AccessTokensApi.DeleteToken(client.ctx, accessTokenID)
return nil, res, err
})
if err != nil {
return fmt.Errorf("failed to delete access token with id %q: %s", accessTokenID, handleLdapiErr(err))
}

return nil
}

func resourceAccessTokenExists(d *schema.ResourceData, metaRaw interface{}) (bool, error) {
return accessTokenExists(d.Id(), metaRaw.(*Client))
}

func accessTokenExists(accessTokenID string, meta *Client) (bool, error) {
_, res, err := meta.ld.AccessTokensApi.GetToken(meta.ctx, accessTokenID)
if isStatusNotFound(res) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("failed to get access token with id %q: %s", accessTokenID, handleLdapiErr(err))
}

return true, nil
}
Loading

0 comments on commit 4298e18

Please sign in to comment.