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 commands for managing version tags #283

Merged
merged 1 commit into from
Apr 19, 2024
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
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
- Add support for listing the revisions to an environment.
[#277](https://github.com/pulumi/esc/pull/277)

- Add support for managing version tags.
[#283](https://github.com/pulumi/esc/pull/283)

### Bug Fixes

- Ensure that redacted output is flushed in `esc run`
Expand Down
120 changes: 120 additions & 0 deletions cmd/esc/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"hash/fnv"
"io"
"io/fs"
"net/http"
"os"
"os/exec"
"path"
Expand All @@ -23,12 +24,14 @@ import (
"time"

"github.com/gofrs/uuid"
"github.com/pgavlin/fx"
"github.com/pulumi/esc"
"github.com/pulumi/esc/cmd/esc/cli/client"
"github.com/pulumi/esc/eval"
"github.com/pulumi/esc/schema"
"github.com/pulumi/esc/syntax"
"github.com/pulumi/pulumi/pkg/v3/backend/display"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
Expand All @@ -37,6 +40,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
Expand Down Expand Up @@ -641,6 +645,122 @@ func (c *testPulumiClient) ListEnvironmentRevisions(
return resp, nil
}

// CreateEnvironmentRevisionTag creates a new revision tag with the given name.
func (c *testPulumiClient) CreateEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
revision *int,
) error {
env, _, err := c.getEnvironment(orgName, envName, "")
if err != nil {
return err
}

rev := len(env.revisions)
if revision != nil {
rev = *revision
}
if rev < 1 || rev > len(env.revisions) {
return errors.New("not found")
}

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

env.tags[tagName] = rev
return nil
}

// GetEnvironmentRevisionTag returns a description of the given revision tag.
func (c *testPulumiClient) GetEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
) (*client.EnvironmentRevisionTag, error) {
env, _, err := c.getEnvironment(orgName, envName, "")
if err != nil {
return nil, err
}

rev, ok := env.tags[tagName]
if !ok {
return nil, &apitype.ErrorResponse{Code: http.StatusNotFound}
}
return &client.EnvironmentRevisionTag{Name: tagName, Revision: rev}, nil
}

// UpdateEnvironmentRevisionTag updates the revision tag with the given name.
func (c *testPulumiClient) UpdateEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
revision *int,
) error {
env, _, err := c.getEnvironment(orgName, envName, "")
if err != nil {
return err
}

rev := len(env.revisions)
if revision != nil {
rev = *revision
}
if rev < 1 || rev > len(env.revisions) {
return errors.New("not found")
}

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

env.tags[tagName] = rev
return nil
}

// DeleteEnvironmentRevisionTag deletes the revision tag with the given name.
func (c *testPulumiClient) DeleteEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
) error {
env, _, err := c.getEnvironment(orgName, envName, "")
if err != nil {
return err
}

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

delete(env.tags, tagName)
return nil
}

// ListEnvironmentRevisionTags lists the revision tags for the given environment.
func (c *testPulumiClient) ListEnvironmentRevisionTags(
ctx context.Context,
orgName string,
envName string,
options client.ListEnvironmentRevisionTagsOptions,
) ([]client.EnvironmentRevisionTag, error) {
env, _, err := c.getEnvironment(orgName, envName, "")
if err != nil {
return nil, err
}

names := maps.Keys(env.tags)
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
})), nil
}

type testExec struct {
fs testFS
environ map[string]string
Expand Down
100 changes: 100 additions & 0 deletions cmd/esc/cli/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,26 @@ type Client interface {
envName string,
options ListEnvironmentRevisionsOptions,
) ([]EnvironmentRevision, error)

// CreateEnvironmentRevisionTag creates a new revision tag with the given name.
CreateEnvironmentRevisionTag(ctx context.Context, orgName, envName, tagName string, revision *int) error

// GetEnvironmentRevisionTag returns a description of the given revision tag.
GetEnvironmentRevisionTag(ctx context.Context, orgName, envName, tagName string) (*EnvironmentRevisionTag, error)

// UpdateEnvironmentRevisionTag updates the revision tag with the given name.
UpdateEnvironmentRevisionTag(ctx context.Context, orgName, envName, tagName string, revision *int) error

// DeleteEnvironmentRevisionTag deletes the revision tag with the given name.
DeleteEnvironmentRevisionTag(ctx context.Context, orgName, envName, tagName string) error

// ListEnvironmentRevisionTags lists the revision tags for the given environment.
ListEnvironmentRevisionTags(
ctx context.Context,
orgName string,
envName string,
options ListEnvironmentRevisionTagsOptions,
) ([]EnvironmentRevisionTag, error)
}

type client struct {
Expand Down Expand Up @@ -537,6 +557,80 @@ func (pc *client) ListEnvironmentRevisions(
return resp, nil
}

// CreateEnvironmentRevisionTag creates a new revision tag with the given name.
func (pc *client) CreateEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
revision *int,
) error {
req := CreateEnvironmentRevisionTagRequest{Revision: revision}
path := fmt.Sprintf("/api/preview/environments/%v/%v/tags/%v", orgName, envName, tagName)
return pc.restCall(ctx, http.MethodPost, path, nil, &req, nil)
}

// GetEnvironmentRevisionTag returns a description of the given revision tag.
func (pc *client) GetEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
) (*EnvironmentRevisionTag, error) {
var resp EnvironmentRevisionTag
path := fmt.Sprintf("/api/preview/environments/%v/%v/tags/%v", orgName, envName, tagName)
err := pc.restCall(ctx, http.MethodGet, path, nil, nil, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}

// UpdateEnvironmentRevisionTag updates the revision tag with the given name.
func (pc *client) UpdateEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
revision *int,
) error {
req := UpdateEnvironmentRevisionTagRequest{Revision: revision}
path := fmt.Sprintf("/api/preview/environments/%v/%v/tags/%v", orgName, envName, tagName)
return pc.restCall(ctx, http.MethodPatch, path, nil, &req, nil)
}

// DeleteEnvironmentRevisionTag deletes the revision tag with the given name.
func (pc *client) DeleteEnvironmentRevisionTag(
ctx context.Context,
orgName string,
envName string,
tagName string,
) error {
path := fmt.Sprintf("/api/preview/environments/%v/%v/tags/%v", orgName, envName, tagName)
return pc.restCall(ctx, http.MethodDelete, path, nil, nil, nil)
}

type ListEnvironmentRevisionTagsOptions struct {
After string `url:"after"`
Count *int `url:"count"`
}

// ListEnvironmentRevisionTags lists the revision tags for the given environment.
func (pc *client) ListEnvironmentRevisionTags(
ctx context.Context,
orgName string,
envName string,
options ListEnvironmentRevisionTagsOptions,
) ([]EnvironmentRevisionTag, error) {
var resp ListEnvironmentRevisionTagsResponse
path := fmt.Sprintf("/api/preview/environments/%v/%v/tags", orgName, envName)
err := pc.restCall(ctx, http.MethodGet, path, options, nil, &resp)
if err != nil {
return nil, err
}
return resp.Tags, nil
}

type httpCallOptions struct {
// RetryPolicy defines the policy for retrying requests by httpClient.Do.
//
Expand Down Expand Up @@ -762,3 +856,9 @@ func cleanPath(p string) string {

return np
}

// IsNotFound returns true if the indicated error is a "not found" error.
func IsNotFound(err error) bool {
resp, ok := err.(*apitype.ErrorResponse)
return ok && resp.Code == http.StatusNotFound
}
1 change: 1 addition & 0 deletions cmd/esc/cli/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func newEnvCmd(esc *escCommand) *cobra.Command {
cmd.AddCommand(newEnvGetCmd(env))
cmd.AddCommand(newEnvSetCmd(env))
cmd.AddCommand(newEnvLogCmd(env))
cmd.AddCommand(newEnvVersionCmd(env))
cmd.AddCommand(newEnvLsCmd(env))
cmd.AddCommand(newEnvRmCmd(env))
cmd.AddCommand(newEnvOpenCmd(env))
Expand Down
28 changes: 28 additions & 0 deletions cmd/esc/cli/env_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2023, Pulumi Corporation.

package cli

import (
"github.com/spf13/cobra"
)

func newEnvVersionCmd(env *envCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Manage version tags",
Long: "Manage version tags\n" +
"\n" +
"This command creates, inspects, updates, deletes, and lists version tags.\n" +
"A version tag is a name that refers to a specific revision of an environment.\n" +
"Once created, version tags can be updated to refer to new reversions\n" +
"of an environment. Version tags can be used to refer to a particular logical\n" +
"version of an environment rather than a specific revision.\n",
SilenceUsage: true,
}

cmd.AddCommand(newEnvVersionTagCmd(env))
cmd.AddCommand(newEnvVersionRmCmd(env))
cmd.AddCommand(newEnvVersionLsCmd(env))

return cmd
}
77 changes: 77 additions & 0 deletions cmd/esc/cli/env_version_ls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2023, Pulumi Corporation.

package cli

import (
"context"
"fmt"
"io"

"github.com/spf13/cobra"

"github.com/pulumi/esc/cmd/esc/cli/client"
"github.com/pulumi/esc/cmd/esc/cli/style"
)

func newEnvVersionLsCmd(env *envCommand) *cobra.Command {
var pagerFlag string
var utc bool

cmd := &cobra.Command{
Use: "ls [<org-name>/]<environment-name>",
Short: "List version tags.",
Long: "List version tags\n" +
"\n" +
"This command lists the version tags for an environment.\n",
SilenceUsage: true,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()

if err := env.esc.getCachedClient(ctx); err != nil {
return err
}

orgName, envName, revisionOrTag, args, err := env.getEnvName(args)
if err != nil {
return err
}
if revisionOrTag != "" {
return fmt.Errorf("the ls command does not accept revisions or tags")
}
_ = args

st := style.NewStylist(style.Profile(env.esc.stdout))

after := ""
return env.esc.pager.Run(pagerFlag, env.esc.stdout, env.esc.stderr, func(ctx context.Context, stdout io.Writer) error {
count := 500
for {
options := client.ListEnvironmentRevisionTagsOptions{
After: after,
Count: &count,
}
tags, err := env.esc.client.ListEnvironmentRevisionTags(ctx, orgName, envName, options)
if err != nil {
return err
}
if len(tags) == 0 {
break
}
after = tags[len(tags)-1].Name

for _, t := range tags {
printRevisionTag(stdout, st, t, utc)
fmt.Fprintf(stdout, "\n")
}
}
return nil
})
},
}

cmd.Flags().StringVar(&pagerFlag, "pager", "", "the command to use to page through the environment's version tags")
cmd.Flags().BoolVar(&utc, "utc", false, "display times in UTC")

return cmd
}
Loading
Loading