Thanks to visit codestin.com
Credit goes to github.com

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

Add new image tags commands #181

Merged
merged 6 commits into from
Nov 8, 2020
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
36 changes: 36 additions & 0 deletions ci/integration/tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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()
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)

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
})
}
68 changes: 68 additions & 0 deletions coder-sdk/tags.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func Make() *cobra.Command {
logoutCmd(),
shCmd(),
usersCmd(),
tagsCmd(),
configSSHCmd(),
secretsCmd(),
envsCmd(),
Expand Down
172 changes: 172 additions & 0 deletions internal/cmd/tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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",
Hidden: true,
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]",
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 {
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().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")
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]",
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 {
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
}