Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: add support for environment tags #345

Merged
merged 19 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

## 0.9.2

- Add commands to manage environment tags.
[#345](https://github.com/pulumi/esc/pull/345)

## 0.9.1

### Improvements
Expand Down
153 changes: 130 additions & 23 deletions cmd/esc/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,9 @@ type testEnvironmentRevision struct {
}

type testEnvironment struct {
revisions []*testEnvironmentRevision
tags map[string]int
revisions []*testEnvironmentRevision
revisionTags map[string]int
tags map[string]string
}

func (env *testEnvironment) latest() *testEnvironmentRevision {
Expand Down Expand Up @@ -365,7 +366,7 @@ func (c *testPulumiClient) getEnvironment(orgName, envName, version string) (*te
}
revision = int(rev)
} else {
rev, ok := env.tags[version]
rev, ok := env.revisionTags[version]
if !ok {
return nil, nil, errors.New("not found")
}
Expand Down Expand Up @@ -496,8 +497,9 @@ func (c *testPulumiClient) CreateEnvironment(ctx context.Context, orgName, envNa
return errors.New("already exists")
}
c.environments[name] = &testEnvironment{
revisions: []*testEnvironmentRevision{{}},
tags: map[string]int{"latest": 0},
revisions: []*testEnvironmentRevision{{}},
revisionTags: map[string]int{"latest": 0},
tags: map[string]string{},
}
return nil
}
Expand Down Expand Up @@ -568,10 +570,10 @@ func (c *testPulumiClient) UpdateEnvironmentWithRevision(
yaml: yaml,
tag: base64.StdEncoding.EncodeToString(h.Sum(nil)),
})
env.tags["latest"] = revisionNumber
env.revisionTags["latest"] = revisionNumber
}

return diags, env.tags["latest"], err
return diags, env.revisionTags["latest"], err
}

func (c *testPulumiClient) DeleteEnvironment(ctx context.Context, orgName, envName string) error {
Expand Down Expand Up @@ -627,6 +629,86 @@ func (c *testPulumiClient) GetOpenProperty(ctx context.Context, orgName, envName
return nil, errors.New("NYI")
}

func (c *testPulumiClient) GetEnvironmentTag(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some tests?

If you add a new file in cli/testdata, it will be automatically picked up by the test harness:

  • The run section contains the command(s) to run
  • The environments section contains the environments to define for testing

Once you've filled in those two sections, you can generate the stdout/stderr sections by running PULUMI_ACCEPT=1 go test ./cmd/esc/cli from the repository root.

You'll likely need to revert changes to cmd/esc/cli/testdata/env-login-gh100.yaml and cmd/esc/cli/testdata/env-set.yaml as the regeneration process makes undesirable formatting changes to those files.

ctx context.Context,
orgName, envName, key string,
) (*client.EnvironmentTag, error) {
ts := time.Now()
return &client.EnvironmentTag{
ID: "1234",
Name: "team",
Value: "pulumi",
Created: ts,
Modified: ts,
EditorLogin: "pulumipus",
EditorName: "pulumipus",
}, nil
}

func (c *testPulumiClient) ListEnvironmentTags(
ctx context.Context,
orgName string,
envName string,
options client.ListEnvironmentTagsOptions,
) ([]*client.EnvironmentTag, string, error) {
ts := time.Now()
return []*client.EnvironmentTag{
{
ID: "1234",
Name: "team",
Value: "pulumi",
Created: ts,
Modified: ts,
EditorLogin: "pulumipus",
EditorName: "pulumipus",
},
}, "0", nil
}

func (c *testPulumiClient) CreateEnvironmentTag(
ctx context.Context,
orgName, envName, key, value string,
) (*client.EnvironmentTag, error) {
ts := time.Now()
return &client.EnvironmentTag{
ID: "1234",
Name: key,
Value: value,
Created: ts,
Modified: ts,
EditorLogin: "pulumipus",
EditorName: "pulumipus",
}, nil
}

func (c *testPulumiClient) UpdateEnvironmentTag(
ctx context.Context,
orgName, envName, currentKey, currentValue, newKey, newValue string,
) (*client.EnvironmentTag, error) {
name := newKey
if name == "" {
name = currentKey
}
value := newValue
if value == "" {
value = currentValue
}
ts := time.Now()
return &client.EnvironmentTag{
ID: "1234",
Name: name,
Value: value,
Created: ts,
Modified: ts,
EditorLogin: "pulumipus",
EditorName: "pulumipus",
}, nil
}

func (c *testPulumiClient) DeleteEnvironmentTag(ctx context.Context, orgName, envName, tagName string) error {
return nil
}

func (c *testPulumiClient) GetEnvironmentRevision(
ctx context.Context,
orgName string,
Expand Down Expand Up @@ -733,11 +815,11 @@ func (c *testPulumiClient) CreateEnvironmentRevisionTag(
return errors.New("not found")
}

if _, ok := env.tags[tagName]; ok {
if _, ok := env.revisionTags[tagName]; ok {
return errors.New("already exists")
}

env.tags[tagName] = rev
env.revisionTags[tagName] = rev
return nil
}

Expand All @@ -753,7 +835,7 @@ func (c *testPulumiClient) GetEnvironmentRevisionTag(
return nil, err
}

rev, ok := env.tags[tagName]
rev, ok := env.revisionTags[tagName]
if !ok {
return nil, &apitype.ErrorResponse{Code: http.StatusNotFound}
}
Expand Down Expand Up @@ -781,11 +863,11 @@ func (c *testPulumiClient) UpdateEnvironmentRevisionTag(
return errors.New("not found")
}

if _, ok := env.tags[tagName]; !ok {
if _, ok := env.revisionTags[tagName]; !ok {
return &apitype.ErrorResponse{Code: http.StatusNotFound}
}

env.tags[tagName] = rev
env.revisionTags[tagName] = rev
return nil
}

Expand All @@ -801,11 +883,11 @@ func (c *testPulumiClient) DeleteEnvironmentRevisionTag(
return err
}

if _, ok := env.tags[tagName]; !ok {
if _, ok := env.revisionTags[tagName]; !ok {
return errors.New("not found")
}

delete(env.tags, tagName)
delete(env.revisionTags, tagName)
return nil
}

Expand All @@ -821,10 +903,10 @@ func (c *testPulumiClient) ListEnvironmentRevisionTags(
return nil, err
}

names := maps.Keys(env.tags)
names := maps.Keys(env.revisionTags)
slices.Sort(names)
return fx.ToSlice(fx.FMap(fx.IterSlice(names), func(name string) (client.EnvironmentRevisionTag, bool) {
return client.EnvironmentRevisionTag{Name: name, Revision: env.tags[name]}, name > options.After
return client.EnvironmentRevisionTag{Name: name, Revision: env.revisionTags[name]}, name > options.After
})), nil
}

Expand Down Expand Up @@ -910,8 +992,22 @@ func (c *testExec) runScript(script string, cmd *exec.Cmd) error {
esc.SetIn(hc.Stdin)
esc.SetOut(hc.Stdout)
esc.SetErr(hc.Stderr)
if err := esc.Execute(); err != nil {

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
esc.SetContext(ctx)
defer cancel()

ch := make(chan error, 1)
go func() {
ch <- esc.Execute()
}()
select {
case <-ctx.Done():
return interp.NewExitStatus(1)
case result := <-ch:
if result != nil {
return interp.NewExitStatus(1)
}
}
return nil
}
Expand Down Expand Up @@ -974,6 +1070,10 @@ type cliTestcaseRevisions struct {
Revisions []cliTestcaseRevision `yaml:"revisions,omitempty"`
}

type cliTestcaseEnvironmentTags struct {
Tags map[string]string `yaml:"tags,omitempty"`
}

type cliTestcaseYAML struct {
Parent string `yaml:"parent,omitempty"`

Expand Down Expand Up @@ -1030,8 +1130,14 @@ func loadTestcase(path string) (*cliTestcaseYAML, *cliTestcase, error) {
revisions = cliTestcaseRevisions{Revisions: []cliTestcaseRevision{{YAML: env}}}
}

var tags cliTestcaseEnvironmentTags
envTags := map[string]string{}
if err := env.Decode(&tags); err == nil {
envTags = tags.Tags
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this is what we want. I think that we should add a new field to cliTestcaseRevisions that contains the environment tags. That will give any test the ability to use environment tags as well as other features.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry do you mean moving the existing tags to cliTestcaseRevisions instead of renaming them to revisionTags on the test environment?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah--it looks like as written an environment can either contain a set of tags or a set of revisions (unless I'm reading it wrong?). Feels like instead we want cliTestcaseRevisions to contain a field of type cliTestcaseEnvironmentTags.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah I follow you - will update

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually thinking more about this I feel like it makes sense to keep these at the top level on the env since revision tags must be unique across all revisions. I did adjust the interface of revision tags though to allow for passing a list instead of a single value since that mirrors what the API supports


envRevisions := []*testEnvironmentRevision{{number: 1}}
tags := map[string]int{}
revisionTags := map[string]int{}
for _, rev := range revisions.Revisions {
bytes, err := yaml.Marshal(rev.YAML)
if err != nil {
Expand All @@ -1054,18 +1160,19 @@ func loadTestcase(path string) (*cliTestcaseYAML, *cliTestcase, error) {
})

if rev.Tag != "" {
if _, ok := tags[rev.Tag]; ok || rev.Tag == "latest" {
if _, ok := revisionTags[rev.Tag]; ok || rev.Tag == "latest" {
return nil, nil, fmt.Errorf("duplicate tag %q", rev.Tag)
}
tags[rev.Tag] = revisionNumber
revisionTags[rev.Tag] = revisionNumber
envRevisions[revisionNumber-1].tag = rev.Tag
}
}
tags["latest"] = len(envRevisions)
revisionTags["latest"] = len(envRevisions)

environments[k] = &testEnvironment{
revisions: envRevisions,
tags: tags,
revisions: envRevisions,
revisionTags: revisionTags,
tags: envTags,
}
}

Expand Down
27 changes: 27 additions & 0 deletions cmd/esc/cli/client/apitype.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,33 @@ type UpdateEnvironmentRevisionTagRequest struct {
Revision *int `json:"revision,omitempty"`
}

type EnvironmentTag struct {
ID string `json:"id"`
Name string `json:"name"`
Value string `json:"value"`
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`
EditorLogin string `json:"editorLogin"`
EditorName string `json:"editorName"`
}

type ListEnvironmentTagsResponse struct {
Tags map[string]*EnvironmentTag `json:"tags"`
NextToken string `json:"nextToken"`
}

type TagRequest struct {
Name string `json:"name"`
Value string `json:"value"`
}

type CreateEnvironmentTagRequest = TagRequest

type UpdateEnvironmentTagRequest struct {
CurrentTag TagRequest `json:"currentTag"`
NewTag TagRequest `json:"newTag"`
}

type EnvironmentRevisionTag struct {
Name string `json:"name"`
Revision int `json:"revision"`
Expand Down
Loading
Loading