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

Lines changed: 36 additions & 0 deletions
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

Lines changed: 68 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 172 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy