Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export AWS_PROFILE=my-profile AWS_REGION=us-east-1
ldcli aws-devops-agent setup
```

The AWS DevOps Agent is available in `us-east-1`, `us-west-2`, `ca-central-1`, `sa-east-1`, `ap-south-1`, `ap-southeast-1`, `ap-southeast-2`, `ap-northeast-1`, `eu-central-1`, `eu-west-1` and `eu-west-2`, and requires AWS CLI 2.36 or later if you also use the AWS CLI directly. `setup` warns when the `aws` binary on your PATH is missing or older than that; the command itself uses the AWS SDK, so it still runs.
The AWS DevOps Agent is available in `us-east-1`, `us-east-2`, `us-west-2`, `ca-central-1`, `sa-east-1`, `ap-south-1`, `ap-southeast-1`, `ap-southeast-2`, `ap-northeast-1`, `ap-northeast-2`, `eu-central-1`, `eu-west-1`, `eu-west-2`, `eu-west-3` and `eu-north-1`, and requires AWS CLI 2.36 or later if you also use the AWS CLI directly. `setup` warns when the `aws` binary on your PATH is missing or older than that; the command itself uses the AWS SDK, so it still runs.

`setup` is safe to re-run: it reuses the IAM roles, the agent space matching `--agent-space-name`, the account and MCP associations on it, and any LaunchDarkly MCP server already registered on the account. Pass `--new-agent-space` to create an additional agent space instead. The IAM roles are account-wide and trust the agent spaces in every region, so running `setup` in a second region leaves the first one working; a role named `DevOpsAgentRole-AgentSpace` or `DevOpsAgentRole-WebappAdmin` that `setup` did not create is reused untouched, and it has to trust `aidevops.amazonaws.com` itself.

Expand Down
4 changes: 4 additions & 0 deletions internal/awsdevops/awsdevops.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@ import (
// SupportedRegions are the regions where the AWS DevOps Agent control plane is available.
var SupportedRegions = []string{
"us-east-1",
"us-east-2",
"us-west-2",
"ca-central-1",
"sa-east-1",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-northeast-1",
"ap-northeast-2",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"eu-north-1",
}

var (
Expand Down
4 changes: 2 additions & 2 deletions internal/awsdevops/awsdevops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,9 @@ func TestManualStepURLsPointAtRegistrationPages(t *testing.T) {
}

func TestNewClientsRejectsUnsupportedRegion(t *testing.T) {
_, err := awsdevops.NewClients(context.Background(), "us-east-2", "")
_, err := awsdevops.NewClients(context.Background(), "us-east-3", "")

assert.ErrorContains(t, err, "AWS DevOps Agent is not available in us-east-2")
assert.ErrorContains(t, err, "AWS DevOps Agent is not available in us-east-3")
}

func TestNewClientsRequiresCredentials(t *testing.T) {
Expand Down
73 changes: 73 additions & 0 deletions internal/awsdevops/regions_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package awsdevops_test

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"

"github.com/launchdarkly/ldcli/internal/awsdevops"
)

func disableAWSCredentials(t *testing.T) {
t.Helper()
t.Setenv("AWS_ACCESS_KEY_ID", "")
t.Setenv("AWS_SECRET_ACCESS_KEY", "")
t.Setenv("AWS_SESSION_TOKEN", "")
t.Setenv("AWS_PROFILE", "")
t.Setenv("AWS_CONFIG_FILE", "/dev/null")
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "/dev/null")
t.Setenv("AWS_EC2_METADATA_DISABLED", "true")
}

func TestNewClientsAcceptsTheNewRegions(t *testing.T) {
disableAWSCredentials(t)
for _, region := range []string{"us-east-2", "ap-northeast-2", "eu-west-3", "eu-north-1"} {
t.Run(region, func(t *testing.T) {
_, err := awsdevops.NewClients(context.Background(), region, "")

// Past the region check, so credentials fail.
assert.ErrorIs(t, err, awsdevops.ErrNoCredentials)
assert.NotContains(t, err.Error(), "not available")
})
}
}

func TestNewClientsRejectsNearMissRegions(t *testing.T) {
disableAWSCredentials(t)
for _, region := range []string{"us-east-3", "eu-north-2", "ap-northeast-4", "eu-west-4"} {
t.Run(region, func(t *testing.T) {
_, err := awsdevops.NewClients(context.Background(), region, "")

assert.ErrorContains(t, err, "AWS DevOps Agent is not available in "+region)
})
}
}

func TestSupportedRegionsMatchTheAWSList(t *testing.T) {
assert.Equal(t, []string{
"us-east-1", "us-east-2", "us-west-2", "ca-central-1", "sa-east-1",
"ap-south-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ap-northeast-2",
"eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1",
}, awsdevops.SupportedRegions)
}

func TestValidateAccessTokenSendsAPIVersion(t *testing.T) {
var got []string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
got = r.Header.Values("LD-API-Version")
if len(got) != 1 || got[0] != "20240415" {
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)

err := awsdevops.ValidateAccessToken(context.Background(), server.URL, "api-token")

assert.NoError(t, err)
assert.Equal(t, []string{"20240415"}, got)
}
3 changes: 3 additions & 0 deletions internal/awsdevops/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"net/http"
"net/url"
"time"

"github.com/launchdarkly/ldcli/internal/resources"
)

// ErrInvalidAccessToken is the sentinel for a token LaunchDarkly rejects.
Expand All @@ -32,6 +34,7 @@ func ValidateAccessToken(ctx context.Context, baseURI, token string) error {
return err
}
req.Header.Set("Authorization", token)
req.Header.Set("LD-API-Version", resources.APIVersion)

res, err := ldHTTPClient.Do(req)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions internal/resources/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
"github.com/launchdarkly/ldcli/internal/errors"
)

// apiVersion is the LaunchDarkly REST API version sent with non-beta requests, so
// APIVersion is the LaunchDarkly REST API version sent with non-beta requests, so
// responses don't depend on the default version stored on the caller's access token.
const apiVersion = "20240415"
const APIVersion = "20240415"

type UnauthenticatedClient interface {
MakeUnauthenticatedRequest(
Expand Down Expand Up @@ -61,7 +61,7 @@ func (c ResourcesClient) MakeRequest(
if isBeta {
req.Header.Set("LD-API-Version", "beta")
} else {
req.Header.Set("LD-API-Version", apiVersion)
req.Header.Set("LD-API-Version", APIVersion)
}
req.URL.RawQuery = query.Encode()

Expand Down
Loading