Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 9d236af

Browse files
authored
Add new hidden image tags commands (#181)
1 parent 0156dd0 commit 9d236af

File tree

4 files changed

+277
-0
lines changed

4 files changed

+277
-0
lines changed

ci/integration/tags_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"cdr.dev/coder-cli/coder-sdk"
8+
"cdr.dev/coder-cli/pkg/tcli"
9+
"cdr.dev/slog/sloggers/slogtest/assert"
10+
)
11+
12+
func TestTags(t *testing.T) {
13+
t.Parallel()
14+
t.Skip("wait for dedicated test cluster so we can create an org")
15+
run(t, "tags-cli-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) {
16+
headlessLogin(ctx, t, c)
17+
client := cleanupClient(ctx, t)
18+
19+
ensureImageImported(ctx, t, client, "ubuntu")
20+
21+
c.Run(ctx, "coder tags ls").Assert(t,
22+
tcli.Error(),
23+
)
24+
c.Run(ctx, "coder tags ls --image ubuntu --org default").Assert(t,
25+
tcli.Success(),
26+
)
27+
var tags []coder.ImageTag
28+
c.Run(ctx, "coder tags ls --image ubuntu --org default --output json").Assert(t,
29+
tcli.Success(),
30+
tcli.StdoutJSONUnmarshal(&tags),
31+
)
32+
assert.True(t, "> 0 tags", len(tags) > 0)
33+
34+
// TODO(@cmoog) add create and rm integration tests
35+
})
36+
}

coder-sdk/tags.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package coder
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"time"
7+
)
8+
9+
// ImageTag is a Docker image tag.
10+
type ImageTag struct {
11+
ImageID string `json:"image_id" table:"-"`
12+
Tag string `json:"tag" table:"Tag"`
13+
LatestHash string `json:"latest_hash" table:"-"`
14+
HashLastUpdatedAt time.Time `json:"hash_last_updated_at" table:"-"`
15+
OSRelease *OSRelease `json:"os_release" table:"OS"`
16+
Environments []*Environment `json:"environments" table:"-"`
17+
UpdatedAt time.Time `json:"updated_at" table:"UpdatedAt"`
18+
CreatedAt time.Time `json:"created_at" table:"-"`
19+
}
20+
21+
// OSRelease is the marshalled /etc/os-release file.
22+
type OSRelease struct {
23+
ID string `json:"id"`
24+
PrettyName string `json:"pretty_name"`
25+
HomeURL string `json:"home_url"`
26+
}
27+
28+
func (o OSRelease) String() string {
29+
return o.PrettyName
30+
}
31+
32+
// CreateImageTagReq defines the request parameters for creating a new image tag.
33+
type CreateImageTagReq struct {
34+
Tag string `json:"tag"`
35+
Default bool `json:"default"`
36+
}
37+
38+
// CreateImageTag creates a new image tag resource.
39+
func (c Client) CreateImageTag(ctx context.Context, imageID string, req CreateImageTagReq) (*ImageTag, error) {
40+
var tag ImageTag
41+
if err := c.requestBody(ctx, http.MethodPost, "/api/images/"+imageID+"/tags", req, tag); err != nil {
42+
return nil, err
43+
}
44+
return &tag, nil
45+
}
46+
47+
// DeleteImageTag deletes an image tag resource.
48+
func (c Client) DeleteImageTag(ctx context.Context, imageID, tag string) error {
49+
return c.requestBody(ctx, http.MethodDelete, "/api/images/"+imageID+"/tags/"+tag, nil, nil)
50+
}
51+
52+
// ImageTags fetch all image tags.
53+
func (c Client) ImageTags(ctx context.Context, imageID string) ([]ImageTag, error) {
54+
var tags []ImageTag
55+
if err := c.requestBody(ctx, http.MethodGet, "/api/images/"+imageID+"/tags", nil, &tags); err != nil {
56+
return nil, err
57+
}
58+
return tags, nil
59+
}
60+
61+
// ImageTagByID fetch an image tag by ID.
62+
func (c Client) ImageTagByID(ctx context.Context, imageID, tagID string) (*ImageTag, error) {
63+
var tag ImageTag
64+
if err := c.requestBody(ctx, http.MethodGet, "/api/images/"+imageID+"/tags/"+tagID, nil, &tag); err != nil {
65+
return nil, err
66+
}
67+
return &tag, nil
68+
}

internal/cmd/cmd.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func Make() *cobra.Command {
2626
logoutCmd(),
2727
shCmd(),
2828
usersCmd(),
29+
tagsCmd(),
2930
configSSHCmd(),
3031
secretsCmd(),
3132
envsCmd(),

internal/cmd/tags.go

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
7+
"cdr.dev/coder-cli/coder-sdk"
8+
"cdr.dev/coder-cli/pkg/clog"
9+
"cdr.dev/coder-cli/pkg/tablewriter"
10+
"github.com/spf13/cobra"
11+
"golang.org/x/xerrors"
12+
)
13+
14+
func tagsCmd() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "tags",
17+
Hidden: true,
18+
Short: "operate on Coder image tags",
19+
}
20+
21+
cmd.AddCommand(
22+
tagsLsCmd(),
23+
tagsCreateCmd(),
24+
tagsRmCmd(),
25+
)
26+
return cmd
27+
}
28+
29+
func tagsCreateCmd() *cobra.Command {
30+
var (
31+
orgName string
32+
imageName string
33+
defaultTag bool
34+
)
35+
cmd := &cobra.Command{
36+
Use: "create [tag]",
37+
Short: "add an image tag",
38+
Long: "allow users to create environments with this image tag",
39+
Example: `coder tags create latest --image ubuntu --org default`,
40+
Args: cobra.ExactArgs(1),
41+
RunE: func(cmd *cobra.Command, args []string) error {
42+
ctx := cmd.Context()
43+
client, err := newClient(ctx)
44+
if err != nil {
45+
return err
46+
}
47+
img, err := findImg(ctx, client, findImgConf{
48+
orgName: orgName,
49+
imgName: imageName,
50+
email: coder.Me,
51+
})
52+
if err != nil {
53+
return xerrors.Errorf("find image: %w", err)
54+
}
55+
56+
_, err = client.CreateImageTag(ctx, img.ID, coder.CreateImageTagReq{
57+
Tag: args[0],
58+
Default: defaultTag,
59+
})
60+
if err != nil {
61+
return xerrors.Errorf("create image tag: %w", err)
62+
}
63+
clog.LogSuccess("created new tag")
64+
65+
return nil
66+
},
67+
}
68+
69+
cmd.Flags().StringVarP(&imageName, "image", "i", "", "image name")
70+
cmd.Flags().StringVarP(&orgName, "org", "o", "", "organization name")
71+
cmd.Flags().BoolVar(&defaultTag, "default", false, "make this tag the default for its image")
72+
_ = cmd.MarkFlagRequired("org")
73+
_ = cmd.MarkFlagRequired("image")
74+
return cmd
75+
}
76+
77+
func tagsLsCmd() *cobra.Command {
78+
var (
79+
orgName string
80+
imageName string
81+
outputFmt string
82+
)
83+
cmd := &cobra.Command{
84+
Use: "ls",
85+
Example: `coder tags ls --image ubuntu --org default --output json`,
86+
RunE: func(cmd *cobra.Command, args []string) error {
87+
ctx := cmd.Context()
88+
client, err := newClient(ctx)
89+
if err != nil {
90+
return err
91+
}
92+
93+
img, err := findImg(ctx, client, findImgConf{
94+
email: coder.Me,
95+
orgName: orgName,
96+
imgName: imageName,
97+
})
98+
if err != nil {
99+
return err
100+
}
101+
102+
tags, err := client.ImageTags(ctx, img.ID)
103+
if err != nil {
104+
return err
105+
}
106+
107+
switch outputFmt {
108+
case humanOutput:
109+
err = tablewriter.WriteTable(len(tags), func(i int) interface{} { return tags[i] })
110+
if err != nil {
111+
return err
112+
}
113+
case jsonOutput:
114+
err := json.NewEncoder(os.Stdout).Encode(tags)
115+
if err != nil {
116+
return err
117+
}
118+
default:
119+
return clog.Error("unknown --output value")
120+
}
121+
122+
return nil
123+
},
124+
}
125+
cmd.Flags().StringVar(&orgName, "org", "", "organization by name")
126+
cmd.Flags().StringVarP(&imageName, "image", "i", "", "image by name")
127+
cmd.Flags().StringVar(&outputFmt, "output", humanOutput, "output format (human|json)")
128+
_ = cmd.MarkFlagRequired("image")
129+
_ = cmd.MarkFlagRequired("org")
130+
return cmd
131+
}
132+
133+
func tagsRmCmd() *cobra.Command {
134+
var (
135+
imageName string
136+
orgName string
137+
)
138+
cmd := &cobra.Command{
139+
Use: "rm [tag]",
140+
Short: "remove an image tag",
141+
Example: `coder tags rm latest --image ubuntu --org default`,
142+
Args: cobra.ExactArgs(1),
143+
RunE: func(cmd *cobra.Command, args []string) error {
144+
ctx := cmd.Context()
145+
client, err := newClient(ctx)
146+
if err != nil {
147+
return err
148+
}
149+
150+
img, err := findImg(ctx, client, findImgConf{
151+
email: coder.Me,
152+
imgName: imageName,
153+
orgName: orgName,
154+
})
155+
if err != nil {
156+
return err
157+
}
158+
159+
if err = client.DeleteImageTag(ctx, img.ID, args[0]); err != nil {
160+
return err
161+
}
162+
clog.LogSuccess("removed tag")
163+
164+
return nil
165+
},
166+
}
167+
cmd.Flags().StringVarP(&orgName, "org", "o", "", "organization by name")
168+
cmd.Flags().StringVarP(&imageName, "image", "i", "", "image by name")
169+
_ = cmd.MarkFlagRequired("image")
170+
_ = cmd.MarkFlagRequired("org")
171+
return cmd
172+
}

0 commit comments

Comments
 (0)