From 7fc2aaa0d950c48f32d180abc2b49ffc3f7d3bdd Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Sat, 7 Nov 2020 19:09:58 -0600 Subject: [PATCH 1/6] Add new image tags commands --- coder-sdk/tags.go | 68 +++++++++++++++ docs/coder.md | 1 + docs/coder_tags.md | 23 ++++++ docs/coder_tags_create.md | 33 ++++++++ docs/coder_tags_ls.md | 33 ++++++++ docs/coder_tags_rm.md | 32 ++++++++ internal/cmd/cmd.go | 1 + internal/cmd/tags.go | 168 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 359 insertions(+) create mode 100644 coder-sdk/tags.go create mode 100644 docs/coder_tags.md create mode 100644 docs/coder_tags_create.md create mode 100644 docs/coder_tags_ls.md create mode 100644 docs/coder_tags_rm.md create mode 100644 internal/cmd/tags.go diff --git a/coder-sdk/tags.go b/coder-sdk/tags.go new file mode 100644 index 00000000..8c7282a9 --- /dev/null +++ b/coder-sdk/tags.go @@ -0,0 +1,68 @@ +package coder + +import ( + "context" + "net/http" + "time" +) + +// ImageTag is a Docker image tag. +type ImageTag struct { + ImageID string `json:"image_id" table:"-"` + Tag string `json:"tag" table:"Tag"` + LatestHash string `json:"latest_hash" table:"-"` + HashLastUpdatedAt time.Time `json:"hash_last_updated_at" table:"-"` + OSRelease *OSRelease `json:"os_release" table:"OS"` + Environments []*Environment `json:"environments" table:"-"` + UpdatedAt time.Time `json:"updated_at" table:"UpdatedAt"` + CreatedAt time.Time `json:"created_at" table:"-"` +} + +// OSRelease is the marshalled /etc/os-release file. +type OSRelease struct { + ID string `json:"id"` + PrettyName string `json:"pretty_name"` + HomeURL string `json:"home_url"` +} + +func (o OSRelease) String() string { + return o.PrettyName +} + +// CreateImageTagReq defines the request parameters for creating a new image tag. +type CreateImageTagReq struct { + Tag string `json:"tag"` + Default bool `json:"default"` +} + +// CreateImageTag creates a new image tag resource. +func (c Client) CreateImageTag(ctx context.Context, imageID string, req CreateImageTagReq) (*ImageTag, error) { + var tag ImageTag + if err := c.requestBody(ctx, http.MethodPost, "/api/images/"+imageID+"/tags", req, tag); err != nil { + return nil, err + } + return &tag, nil +} + +// DeleteImageTag deletes an image tag resource. +func (c Client) DeleteImageTag(ctx context.Context, imageID, tag string) error { + return c.requestBody(ctx, http.MethodDelete, "/api/images/"+imageID+"/tags/"+tag, nil, nil) +} + +// ImageTags fetch all image tags. +func (c Client) ImageTags(ctx context.Context, imageID string) ([]ImageTag, error) { + var tags []ImageTag + if err := c.requestBody(ctx, http.MethodGet, "/api/images/"+imageID+"/tags", nil, &tags); err != nil { + return nil, err + } + return tags, nil +} + +// ImageTagByID fetch an image tag by ID. +func (c Client) ImageTagByID(ctx context.Context, imageID, tagID string) (*ImageTag, error) { + var tag ImageTag + if err := c.requestBody(ctx, http.MethodGet, "/api/images/"+imageID+"/tags/"+tagID, nil, &tag); err != nil { + return nil, err + } + return &tag, nil +} diff --git a/docs/coder.md b/docs/coder.md index e268d312..f9099719 100644 --- a/docs/coder.md +++ b/docs/coder.md @@ -18,6 +18,7 @@ coder provides a CLI for working with an existing Coder Enterprise installation * [coder logout](coder_logout.md) - Remove local authentication credentials if any exist * [coder sh](coder_sh.md) - Open a shell and execute commands in a Coder environment * [coder sync](coder_sync.md) - Establish a one way directory sync to a Coder environment +* [coder tags](coder_tags.md) - operate on Coder image tags * [coder urls](coder_urls.md) - Interact with environment DevURLs * [coder users](coder_users.md) - Interact with Coder user accounts diff --git a/docs/coder_tags.md b/docs/coder_tags.md new file mode 100644 index 00000000..5cf8ca00 --- /dev/null +++ b/docs/coder_tags.md @@ -0,0 +1,23 @@ +## coder tags + +operate on Coder image tags + +### Options + +``` + -h, --help help for tags +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation +* [coder tags create](coder_tags_create.md) - +* [coder tags ls](coder_tags_ls.md) - +* [coder tags rm](coder_tags_rm.md) - + diff --git a/docs/coder_tags_create.md b/docs/coder_tags_create.md new file mode 100644 index 00000000..cc8ce253 --- /dev/null +++ b/docs/coder_tags_create.md @@ -0,0 +1,33 @@ +## coder tags create + + + +``` +coder tags create [tag] [flags] +``` + +### Examples + +``` +coder tags create latest --image ubuntu --org default +``` + +### Options + +``` + --default make this tag the default for its image + -h, --help help for create + --image string image name + --org string organization name +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder tags](coder_tags.md) - operate on Coder image tags + diff --git a/docs/coder_tags_ls.md b/docs/coder_tags_ls.md new file mode 100644 index 00000000..423b136b --- /dev/null +++ b/docs/coder_tags_ls.md @@ -0,0 +1,33 @@ +## coder tags ls + + + +``` +coder tags ls [flags] +``` + +### Examples + +``` +coder tags ls --image ubuntu --org default --output json +``` + +### Options + +``` + -h, --help help for ls + -i, --image string image by name + --org string organization by name + --output string output format (human|json) (default "human") +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder tags](coder_tags.md) - operate on Coder image tags + diff --git a/docs/coder_tags_rm.md b/docs/coder_tags_rm.md new file mode 100644 index 00000000..9d0b6ac6 --- /dev/null +++ b/docs/coder_tags_rm.md @@ -0,0 +1,32 @@ +## coder tags rm + + + +``` +coder tags rm [tag] [flags] +``` + +### Examples + +``` +coder tags rm latest --image ubuntu --org default +``` + +### Options + +``` + -h, --help help for rm + -i, --image string image by name + -o, --org string organization by name +``` + +### Options inherited from parent commands + +``` + -v, --verbose show verbose output +``` + +### SEE ALSO + +* [coder tags](coder_tags.md) - operate on Coder image tags + diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 937c0558..e7b8aa63 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -26,6 +26,7 @@ func Make() *cobra.Command { logoutCmd(), shCmd(), usersCmd(), + tagsCmd(), configSSHCmd(), secretsCmd(), envsCmd(), diff --git a/internal/cmd/tags.go b/internal/cmd/tags.go new file mode 100644 index 00000000..0317ba46 --- /dev/null +++ b/internal/cmd/tags.go @@ -0,0 +1,168 @@ +package cmd + +import ( + "encoding/json" + "os" + + "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/pkg/clog" + "cdr.dev/coder-cli/pkg/tablewriter" + "github.com/spf13/cobra" + "golang.org/x/xerrors" +) + +func tagsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "tags", + Short: "operate on Coder image tags", + } + + cmd.AddCommand( + tagsLsCmd(), + tagsCreateCmd(), + tagsRmCmd(), + ) + return cmd +} + +func tagsCreateCmd() *cobra.Command { + var ( + orgName string + imageName string + defaultTag bool + ) + cmd := &cobra.Command{ + Use: "create [tag]", + Example: `coder tags create latest --image ubuntu --org default`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := newClient(ctx) + if err != nil { + return err + } + img, err := findImg(ctx, client, findImgConf{ + orgName: orgName, + imgName: imageName, + email: coder.Me, + }) + if err != nil { + return xerrors.Errorf("find image: %w", err) + } + + _, err = client.CreateImageTag(ctx, img.ID, coder.CreateImageTagReq{ + Tag: args[0], + Default: defaultTag, + }) + if err != nil { + return xerrors.Errorf("create image tag: %w", err) + } + clog.LogSuccess("created new tag") + + return nil + }, + } + + cmd.Flags().StringVar(&imageName, "image", "", "image name") + cmd.Flags().StringVar(&orgName, "org", "", "organization name") + cmd.Flags().BoolVar(&defaultTag, "default", false, "make this tag the default for its image") + _ = cmd.MarkFlagRequired("org") + _ = cmd.MarkFlagRequired("image") + return cmd +} + +func tagsLsCmd() *cobra.Command { + var ( + orgName string + imageName string + outputFmt string + ) + cmd := &cobra.Command{ + Use: "ls", + Example: `coder tags ls --image ubuntu --org default --output json`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := newClient(ctx) + if err != nil { + return err + } + + img, err := findImg(ctx, client, findImgConf{ + email: coder.Me, + orgName: orgName, + imgName: imageName, + }) + if err != nil { + return err + } + + tags, err := client.ImageTags(ctx, img.ID) + if err != nil { + return err + } + + switch outputFmt { + case humanOutput: + err = tablewriter.WriteTable(len(tags), func(i int) interface{} { return tags[i] }) + if err != nil { + return err + } + case jsonOutput: + err := json.NewEncoder(os.Stdout).Encode(tags) + if err != nil { + return err + } + default: + return clog.Error("unknown --output value") + } + + return nil + }, + } + cmd.Flags().StringVar(&orgName, "org", "", "organization by name") + cmd.Flags().StringVarP(&imageName, "image", "i", "", "image by name") + cmd.Flags().StringVar(&outputFmt, "output", humanOutput, "output format (human|json)") + _ = cmd.MarkFlagRequired("image") + _ = cmd.MarkFlagRequired("org") + return cmd +} + +func tagsRmCmd() *cobra.Command { + var ( + imageName string + orgName string + ) + cmd := &cobra.Command{ + Use: "rm [tag]", + Example: `coder tags rm latest --image ubuntu --org default`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := newClient(ctx) + if err != nil { + return err + } + + img, err := findImg(ctx, client, findImgConf{ + email: coder.Me, + imgName: imageName, + orgName: orgName, + }) + if err != nil { + return err + } + + if err = client.DeleteImageTag(ctx, img.ID, args[0]); err != nil { + return err + } + clog.LogSuccess("removed tag") + + return nil + }, + } + cmd.Flags().StringVarP(&orgName, "org", "o", "", "organization by name") + cmd.Flags().StringVarP(&imageName, "image", "i", "", "image by name") + _ = cmd.MarkFlagRequired("image") + _ = cmd.MarkFlagRequired("org") + return cmd +} From 10eb0cca0ea1046b488e9c0489e80f445435c165 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Sat, 7 Nov 2020 19:13:49 -0600 Subject: [PATCH 2/6] fixup! Add new image tags commands --- docs/coder_tags_create.md | 4 ++-- internal/cmd/tags.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/coder_tags_create.md b/docs/coder_tags_create.md index cc8ce253..e35ecf02 100644 --- a/docs/coder_tags_create.md +++ b/docs/coder_tags_create.md @@ -17,8 +17,8 @@ coder tags create latest --image ubuntu --org default ``` --default make this tag the default for its image -h, --help help for create - --image string image name - --org string organization name + -i, --image string image name + -o, --org string organization name ``` ### Options inherited from parent commands diff --git a/internal/cmd/tags.go b/internal/cmd/tags.go index 0317ba46..9b2325eb 100644 --- a/internal/cmd/tags.go +++ b/internal/cmd/tags.go @@ -63,8 +63,8 @@ func tagsCreateCmd() *cobra.Command { }, } - cmd.Flags().StringVar(&imageName, "image", "", "image name") - cmd.Flags().StringVar(&orgName, "org", "", "organization name") + cmd.Flags().StringVarP(&imageName, "image", "i", "", "image name") + cmd.Flags().StringVarP(&orgName, "org", "o", "", "organization name") cmd.Flags().BoolVar(&defaultTag, "default", false, "make this tag the default for its image") _ = cmd.MarkFlagRequired("org") _ = cmd.MarkFlagRequired("image") From 0caca7b698616c6f51ba6b54d09af2b94ae6d96d Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Sat, 7 Nov 2020 19:25:45 -0600 Subject: [PATCH 3/6] fixup! Add new image tags commands --- docs/coder_tags.md | 4 ++-- docs/coder_tags_create.md | 4 ++++ docs/coder_tags_rm.md | 2 +- internal/cmd/tags.go | 3 +++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/coder_tags.md b/docs/coder_tags.md index 5cf8ca00..663920e5 100644 --- a/docs/coder_tags.md +++ b/docs/coder_tags.md @@ -17,7 +17,7 @@ operate on Coder image tags ### SEE ALSO * [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation -* [coder tags create](coder_tags_create.md) - +* [coder tags create](coder_tags_create.md) - add an image tag * [coder tags ls](coder_tags_ls.md) - -* [coder tags rm](coder_tags_rm.md) - +* [coder tags rm](coder_tags_rm.md) - remove an image tag diff --git a/docs/coder_tags_create.md b/docs/coder_tags_create.md index e35ecf02..a4089d97 100644 --- a/docs/coder_tags_create.md +++ b/docs/coder_tags_create.md @@ -1,6 +1,10 @@ ## coder tags create +add an image tag +### Synopsis + +allow users to create environments with this image tag ``` coder tags create [tag] [flags] diff --git a/docs/coder_tags_rm.md b/docs/coder_tags_rm.md index 9d0b6ac6..c909f8df 100644 --- a/docs/coder_tags_rm.md +++ b/docs/coder_tags_rm.md @@ -1,6 +1,6 @@ ## coder tags rm - +remove an image tag ``` coder tags rm [tag] [flags] diff --git a/internal/cmd/tags.go b/internal/cmd/tags.go index 9b2325eb..7c9bbe52 100644 --- a/internal/cmd/tags.go +++ b/internal/cmd/tags.go @@ -33,6 +33,8 @@ func tagsCreateCmd() *cobra.Command { ) cmd := &cobra.Command{ Use: "create [tag]", + Short: "add an image tag", + Long: "allow users to create environments with this image tag", Example: `coder tags create latest --image ubuntu --org default`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -134,6 +136,7 @@ func tagsRmCmd() *cobra.Command { ) cmd := &cobra.Command{ Use: "rm [tag]", + Short: "remove an image tag", Example: `coder tags rm latest --image ubuntu --org default`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { From 1115494d774ffe387a7dd9decdbb15c47a4d5236 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Sat, 7 Nov 2020 19:47:03 -0600 Subject: [PATCH 4/6] Add ls integration test --- ci/integration/tags_test.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 ci/integration/tags_test.go diff --git a/ci/integration/tags_test.go b/ci/integration/tags_test.go new file mode 100644 index 00000000..5f7c9aed --- /dev/null +++ b/ci/integration/tags_test.go @@ -0,0 +1,35 @@ +package integration + +import ( + "context" + "testing" + + "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/pkg/tcli" + "cdr.dev/slog/sloggers/slogtest/assert" +) + +func TestTags(t *testing.T) { + t.Parallel() + run(t, "tags-cli-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { + headlessLogin(ctx, t, c) + client := cleanupClient(ctx, t) + + ensureImageImported(ctx, t, client, "ubuntu") + + c.Run(ctx, "coder tags ls").Assert(t, + tcli.Error(), + ) + c.Run(ctx, "coder tags ls --image ubuntu --org default").Assert(t, + tcli.Success(), + ) + var tags []coder.ImageTag + c.Run(ctx, "coder tags ls --image ubuntu --org default --output json").Assert(t, + tcli.Success(), + tcli.StdoutJSONUnmarshal(&tags), + ) + assert.True(t, "> 0 tags", len(tags) > 0) + + // TODO(@cmoog) add create and rm integration tests + }) +} From 0484bd3839716e6432daaa3b1de1cb4e8725a7eb Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Sat, 7 Nov 2020 20:02:12 -0600 Subject: [PATCH 5/6] fixup! Add ls integration test --- ci/integration/tags_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/integration/tags_test.go b/ci/integration/tags_test.go index 5f7c9aed..2fb7dfe0 100644 --- a/ci/integration/tags_test.go +++ b/ci/integration/tags_test.go @@ -11,6 +11,7 @@ import ( func TestTags(t *testing.T) { t.Parallel() + t.Skip("wait for dedicated test cluster so we can create an org") run(t, "tags-cli-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { headlessLogin(ctx, t, c) client := cleanupClient(ctx, t) From e0228262ebb8ca9a3f8ed2b1cfefd1397624ff20 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Sat, 7 Nov 2020 20:30:07 -0600 Subject: [PATCH 6/6] Hide tags cmd --- docs/coder.md | 1 - docs/coder_tags.md | 23 ----------------------- docs/coder_tags_create.md | 37 ------------------------------------- docs/coder_tags_ls.md | 33 --------------------------------- docs/coder_tags_rm.md | 32 -------------------------------- internal/cmd/tags.go | 5 +++-- 6 files changed, 3 insertions(+), 128 deletions(-) delete mode 100644 docs/coder_tags.md delete mode 100644 docs/coder_tags_create.md delete mode 100644 docs/coder_tags_ls.md delete mode 100644 docs/coder_tags_rm.md diff --git a/docs/coder.md b/docs/coder.md index f9099719..e268d312 100644 --- a/docs/coder.md +++ b/docs/coder.md @@ -18,7 +18,6 @@ coder provides a CLI for working with an existing Coder Enterprise installation * [coder logout](coder_logout.md) - Remove local authentication credentials if any exist * [coder sh](coder_sh.md) - Open a shell and execute commands in a Coder environment * [coder sync](coder_sync.md) - Establish a one way directory sync to a Coder environment -* [coder tags](coder_tags.md) - operate on Coder image tags * [coder urls](coder_urls.md) - Interact with environment DevURLs * [coder users](coder_users.md) - Interact with Coder user accounts diff --git a/docs/coder_tags.md b/docs/coder_tags.md deleted file mode 100644 index 663920e5..00000000 --- a/docs/coder_tags.md +++ /dev/null @@ -1,23 +0,0 @@ -## coder tags - -operate on Coder image tags - -### Options - -``` - -h, --help help for tags -``` - -### Options inherited from parent commands - -``` - -v, --verbose show verbose output -``` - -### SEE ALSO - -* [coder](coder.md) - coder provides a CLI for working with an existing Coder Enterprise installation -* [coder tags create](coder_tags_create.md) - add an image tag -* [coder tags ls](coder_tags_ls.md) - -* [coder tags rm](coder_tags_rm.md) - remove an image tag - diff --git a/docs/coder_tags_create.md b/docs/coder_tags_create.md deleted file mode 100644 index a4089d97..00000000 --- a/docs/coder_tags_create.md +++ /dev/null @@ -1,37 +0,0 @@ -## coder tags create - -add an image tag - -### Synopsis - -allow users to create environments with this image tag - -``` -coder tags create [tag] [flags] -``` - -### Examples - -``` -coder tags create latest --image ubuntu --org default -``` - -### Options - -``` - --default make this tag the default for its image - -h, --help help for create - -i, --image string image name - -o, --org string organization name -``` - -### Options inherited from parent commands - -``` - -v, --verbose show verbose output -``` - -### SEE ALSO - -* [coder tags](coder_tags.md) - operate on Coder image tags - diff --git a/docs/coder_tags_ls.md b/docs/coder_tags_ls.md deleted file mode 100644 index 423b136b..00000000 --- a/docs/coder_tags_ls.md +++ /dev/null @@ -1,33 +0,0 @@ -## coder tags ls - - - -``` -coder tags ls [flags] -``` - -### Examples - -``` -coder tags ls --image ubuntu --org default --output json -``` - -### Options - -``` - -h, --help help for ls - -i, --image string image by name - --org string organization by name - --output string output format (human|json) (default "human") -``` - -### Options inherited from parent commands - -``` - -v, --verbose show verbose output -``` - -### SEE ALSO - -* [coder tags](coder_tags.md) - operate on Coder image tags - diff --git a/docs/coder_tags_rm.md b/docs/coder_tags_rm.md deleted file mode 100644 index c909f8df..00000000 --- a/docs/coder_tags_rm.md +++ /dev/null @@ -1,32 +0,0 @@ -## coder tags rm - -remove an image tag - -``` -coder tags rm [tag] [flags] -``` - -### Examples - -``` -coder tags rm latest --image ubuntu --org default -``` - -### Options - -``` - -h, --help help for rm - -i, --image string image by name - -o, --org string organization by name -``` - -### Options inherited from parent commands - -``` - -v, --verbose show verbose output -``` - -### SEE ALSO - -* [coder tags](coder_tags.md) - operate on Coder image tags - diff --git a/internal/cmd/tags.go b/internal/cmd/tags.go index 7c9bbe52..ad77932d 100644 --- a/internal/cmd/tags.go +++ b/internal/cmd/tags.go @@ -13,8 +13,9 @@ import ( func tagsCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "tags", - Short: "operate on Coder image tags", + Use: "tags", + Hidden: true, + Short: "operate on Coder image tags", } cmd.AddCommand(