-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: environment management support (#501)
Adds the ability to manage a team's Environments ✨ by introducing a pair of data sources and a shiny new resource. Co-authored-by: Brooke Sargent <brookesargent@honeycomb.io>
- Loading branch information
1 parent
3ecd18f
commit 1018f58
Showing
27 changed files
with
1,360 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package v2 | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/hashicorp/jsonapi" | ||
|
||
hnyclient "github.com/honeycombio/terraform-provider-honeycombio/client" | ||
) | ||
|
||
// Compile-time proof of interface implementation. | ||
var _ Environments = (*environments)(nil) | ||
|
||
type Environments interface { | ||
Create(ctx context.Context, env *Environment) (*Environment, error) | ||
Get(ctx context.Context, id string) (*Environment, error) | ||
Update(ctx context.Context, env *Environment) (*Environment, error) | ||
Delete(ctx context.Context, id string) error | ||
List(ctx context.Context, opts ...ListOption) (*Pager[Environment], error) | ||
} | ||
|
||
type environments struct { | ||
client *Client | ||
authinfo *AuthMetadata | ||
} | ||
|
||
const ( | ||
environmentsPath = "/2/teams/%s/environments" | ||
environmentsByIDPath = "/2/teams/%s/environments/%s" | ||
) | ||
|
||
const ( | ||
EnvironmentColorBlue = "blue" | ||
EnvironmentColorGreen = "green" | ||
EnvironmentColorGold = "gold" | ||
EnvironmentColorRed = "red" | ||
EnvironmentColorPurple = "purple" | ||
EnvironmentColorLightBlue = "lightBlue" | ||
EnvironmentColorLightGreen = "lightGreen" | ||
EnvironmentColorLightGold = "lightGold" | ||
EnvironmentColorLightRed = "lightRed" | ||
EnvironmentColorLightPurple = "lightPurple" | ||
) | ||
|
||
func EnvironmentColorTypes() []string { | ||
return []string{ | ||
EnvironmentColorBlue, | ||
EnvironmentColorGreen, | ||
EnvironmentColorGold, | ||
EnvironmentColorRed, | ||
EnvironmentColorPurple, | ||
EnvironmentColorLightBlue, | ||
EnvironmentColorLightGreen, | ||
EnvironmentColorLightGold, | ||
EnvironmentColorLightRed, | ||
EnvironmentColorLightPurple, | ||
} | ||
} | ||
|
||
func (e *environments) Create(ctx context.Context, env *Environment) (*Environment, error) { | ||
r, err := e.client.Do(ctx, | ||
http.MethodPost, | ||
fmt.Sprintf(environmentsPath, e.authinfo.Team.Slug), | ||
env, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if r.StatusCode != http.StatusCreated { | ||
return nil, hnyclient.ErrorFromResponse(r) | ||
} | ||
|
||
envrion := new(Environment) | ||
if err := jsonapi.UnmarshalPayload(r.Body, envrion); err != nil { | ||
return nil, err | ||
} | ||
return envrion, nil | ||
} | ||
|
||
func (e *environments) Get(ctx context.Context, id string) (*Environment, error) { | ||
r, err := e.client.Do(ctx, | ||
http.MethodGet, | ||
fmt.Sprintf(environmentsByIDPath, e.authinfo.Team.Slug, id), | ||
nil, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if r.StatusCode != http.StatusOK { | ||
return nil, hnyclient.ErrorFromResponse(r) | ||
} | ||
|
||
envrion := new(Environment) | ||
if err := jsonapi.UnmarshalPayload(r.Body, envrion); err != nil { | ||
return nil, err | ||
} | ||
return envrion, nil | ||
} | ||
|
||
func (e *environments) Update(ctx context.Context, env *Environment) (*Environment, error) { | ||
r, err := e.client.Do(ctx, | ||
http.MethodPatch, | ||
fmt.Sprintf(environmentsByIDPath, e.authinfo.Team.Slug, env.ID), | ||
env, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if r.StatusCode != http.StatusOK { | ||
return nil, hnyclient.ErrorFromResponse(r) | ||
} | ||
|
||
envrion := new(Environment) | ||
if err := jsonapi.UnmarshalPayload(r.Body, envrion); err != nil { | ||
return nil, err | ||
} | ||
return envrion, nil | ||
} | ||
|
||
func (e *environments) Delete(ctx context.Context, id string) error { | ||
r, err := e.client.Do(ctx, | ||
http.MethodDelete, | ||
fmt.Sprintf(environmentsByIDPath, e.authinfo.Team.Slug, id), | ||
nil, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
if r.StatusCode != http.StatusNoContent { | ||
return hnyclient.ErrorFromResponse(r) | ||
} | ||
return nil | ||
} | ||
|
||
func (e *environments) List(ctx context.Context, os ...ListOption) (*Pager[Environment], error) { | ||
return NewPager[Environment]( | ||
e.client, | ||
fmt.Sprintf(environmentsPath, e.authinfo.Team.Slug), | ||
os..., | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package v2 | ||
|
||
import ( | ||
"context" | ||
"math" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
hnyclient "github.com/honeycombio/terraform-provider-honeycombio/client" | ||
"github.com/honeycombio/terraform-provider-honeycombio/internal/helper" | ||
"github.com/honeycombio/terraform-provider-honeycombio/internal/helper/test" | ||
) | ||
|
||
func TestClient_Environments(t *testing.T) { | ||
ctx := context.Background() | ||
c := newTestClient(t) | ||
|
||
t.Run("happy path", func(t *testing.T) { | ||
// create a new Environment | ||
newEnv := &Environment{ | ||
Name: test.RandomStringWithPrefix("test.", 20), | ||
Description: helper.ToPtr(test.RandomString(50)), | ||
Color: helper.ToPtr(EnvironmentColorBlue), | ||
} | ||
e, err := c.Environments.Create(ctx, newEnv) | ||
require.NoError(t, err) | ||
assert.NotEmpty(t, e.ID) | ||
assert.Equal(t, newEnv.Name, e.Name) | ||
assert.NotEmpty(t, e.Slug) | ||
assert.Equal(t, newEnv.Description, e.Description) | ||
assert.Equal(t, newEnv.Color, e.Color) | ||
if assert.NotNil(t, e.Settings) { | ||
assert.True(t, *e.Settings.DeleteProtected) | ||
} | ||
|
||
// read the Environment back and compare | ||
env, err := c.Environments.Get(ctx, e.ID) | ||
require.NoError(t, err) | ||
assert.Equal(t, e.ID, env.ID) | ||
assert.Equal(t, e.Name, env.Name) | ||
assert.Equal(t, e.Slug, env.Slug) | ||
assert.Equal(t, e.Description, env.Description) | ||
assert.Equal(t, e.Color, env.Color) | ||
if assert.NotNil(t, env.Settings) { | ||
assert.True(t, *env.Settings.DeleteProtected) | ||
} | ||
|
||
// update the Environment's description and color | ||
newDescription := helper.ToPtr(test.RandomString(50)) | ||
env.Description = newDescription | ||
env.Color = helper.ToPtr(EnvironmentColorGreen) | ||
env, err = c.Environments.Update(ctx, env) | ||
require.NoError(t, err) | ||
assert.Equal(t, e.ID, env.ID) | ||
assert.Equal(t, newDescription, env.Description) | ||
assert.Equal(t, EnvironmentColorGreen, *env.Color) | ||
|
||
// try to delete the environment with delete protection enabled | ||
var de hnyclient.DetailedError | ||
err = c.Environments.Delete(ctx, env.ID) | ||
require.ErrorAs(t, err, &de) | ||
assert.Equal(t, http.StatusConflict, de.Status) | ||
|
||
// disable deletion protection and delete the Environment | ||
_, err = c.Environments.Update(ctx, &Environment{ | ||
ID: env.ID, | ||
Settings: &EnvironmentSettings{ | ||
DeleteProtected: helper.ToPtr(false), | ||
}, | ||
}) | ||
require.NoError(t, err) | ||
err = c.Environments.Delete(ctx, env.ID) | ||
require.NoError(t, err) | ||
|
||
// verify the Environment was deleted | ||
_, err = c.Environments.Get(ctx, env.ID) | ||
require.ErrorAs(t, err, &de) | ||
assert.True(t, de.IsNotFound()) | ||
}) | ||
} | ||
|
||
func TestClient_Environments_Pagination(t *testing.T) { | ||
ctx := context.Background() | ||
c := newTestClient(t) | ||
|
||
// create a bunch of environments | ||
numEnvs := int(math.Floor(1.5 * float64(defaultPageSize))) | ||
testEnvs := make([]*Environment, numEnvs) | ||
for i := 0; i < numEnvs; i++ { | ||
e, err := c.Environments.Create(ctx, &Environment{ | ||
Name: test.RandomStringWithPrefix("test.", 20), | ||
}) | ||
require.NoError(t, err) | ||
testEnvs[i] = e | ||
} | ||
t.Cleanup(func() { | ||
for _, e := range testEnvs { | ||
c.Environments.Update(ctx, &Environment{ | ||
ID: e.ID, | ||
Settings: &EnvironmentSettings{ | ||
DeleteProtected: helper.ToPtr(false), | ||
}, | ||
}) | ||
c.Environments.Delete(ctx, e.ID) | ||
} | ||
}) | ||
|
||
t.Run("happy path", func(t *testing.T) { | ||
envs := make([]*Environment, 0) | ||
pager, err := c.Environments.List(ctx) | ||
require.NoError(t, err) | ||
|
||
items, err := pager.Next(ctx) | ||
require.NoError(t, err) | ||
assert.Len(t, items, defaultPageSize, "incorrect number of items") | ||
assert.True(t, pager.HasNext(), "should have more pages") | ||
envs = append(envs, items...) | ||
|
||
for pager.HasNext() { | ||
items, err = pager.Next(ctx) | ||
require.NoError(t, err) | ||
envs = append(envs, items...) | ||
} | ||
// we can't guarantee that there are exactly numEnvs environments | ||
assert.GreaterOrEqual(t, len(envs), numEnvs, "should have at least %d environments", numEnvs) | ||
}) | ||
|
||
t.Run("works with custom page size", func(t *testing.T) { | ||
pageSize := 5 | ||
pager, err := c.Environments.List(ctx, PageSize(pageSize)) | ||
require.NoError(t, err) | ||
|
||
items, err := pager.Next(ctx) | ||
require.NoError(t, err) | ||
assert.Len(t, items, pageSize, "incorrect number of items") | ||
assert.True(t, pager.HasNext(), "should have more pages") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Data Source: honeycombio_environment | ||
|
||
The `honeycombio_environment` data source retrieves the details of a single Environment. | ||
If you want to retrieve multiple Environments, use the `honeycombio_environments` data source instead. | ||
|
||
-> **NOTE** This data source requires the provider be configured with a Management Key with `environments:read` in the configured scopes. | ||
|
||
|
||
## Example Usage | ||
|
||
```hcl | ||
# Retrieve the details of an Environment | ||
data "honeycombio_environment" "prod" { | ||
id = "hcaen_01j1d7t02zf7wgw7q89z3t60vf" | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
* `id` - (Required) The ID of the Environment | ||
|
||
## Attribute Reference | ||
|
||
In addition to all arguments above, the following attributes are exported: | ||
|
||
* `name` - the Environment's name. | ||
* `slug` - the Environment's slug. | ||
* `description` - the Environment's description. | ||
* `color` - the Environment's color. | ||
* `delete_protected` - the current state of the Environment's deletion protection status. | ||
|
Oops, something went wrong.