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

Commit 6e745bf

Browse files
author
Faris Huskovic
committed
Change coder create envs image flag to take image name and use defaults from image
1 parent 6f8b9b8 commit 6e745bf

File tree

5 files changed

+134
-26
lines changed

5 files changed

+134
-26
lines changed

ci/integration/envs_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestEnvsCLI(t *testing.T) {
4949
// Invalid environment name should fail.
5050
c.Run(ctx, "coder envs create --image "+ubuntuImgID+" this-IS-an-invalid-EnvironmentName").Assert(t,
5151
tcli.Error(),
52-
tcli.StderrMatches(regexp.QuoteMeta("environment name must conform to regex ^[a-z0-9]([a-z0-9-]+)?")),
52+
tcli.StderrMatches(regexp.QuoteMeta("fatal: image not found - did you forget to import this image?\n\n")),
5353
)
5454

5555
// TODO(Faris) : uncomment this when we can safely purge the environments
@@ -63,7 +63,7 @@ func TestEnvsCLI(t *testing.T) {
6363
// Image does not exist should fail.
6464
c.Run(ctx, "coder envs create --image does-not-exist env-will-not-be-created").Assert(t,
6565
tcli.Error(),
66-
tcli.StderrMatches(regexp.QuoteMeta("does not exist")),
66+
tcli.StderrMatches(regexp.QuoteMeta("fatal: image not found - did you forget to import this image?\n\n")),
6767
)
6868
})
6969
}

coder-sdk/image.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type Image struct {
1313
Description string `json:"description"`
1414
URL string `json:"url"` // User-supplied URL for image.
1515
DefaultCPUCores float32 `json:"default_cpu_cores"`
16-
DefaultMemoryGB int `json:"default_memory_gb"`
16+
DefaultMemoryGB float32 `json:"default_memory_gb"`
1717
DefaultDiskGB int `json:"default_disk_gb"`
1818
Deprecated bool `json:"deprecated"`
1919
}
@@ -47,3 +47,13 @@ func (c Client) ImportImage(ctx context.Context, orgID string, req ImportImageRe
4747
}
4848
return &img, nil
4949
}
50+
51+
// GetOrganizationImages returns all of the images imported for org.
52+
func (c Client) GetOrganizationImages(ctx context.Context, org string) ([]*Image, error) {
53+
var imgs []*Image
54+
55+
if err := c.requestBody(ctx, http.MethodGet, "/api/orgs/"+org+"/images", nil, &imgs); err != nil {
56+
return nil, err
57+
}
58+
return imgs, nil
59+
}

internal/cmd/ceapi.go

+77
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"cdr.dev/coder-cli/coder-sdk"
89
"cdr.dev/coder-cli/internal/clog"
@@ -81,3 +82,79 @@ func findEnv(ctx context.Context, client *coder.Client, envName, userEmail strin
8182
clog.Tipf("run \"coder envs ls\" to view your environments"),
8283
)
8384
}
85+
86+
func findImg(ctx context.Context, client *coder.Client, email, imgName string) (*coder.Image, error) {
87+
switch {
88+
case client == nil:
89+
return nil, xerrors.New("nil client")
90+
case email == "":
91+
return nil, xerrors.New("user email unset")
92+
case imgName == "":
93+
return nil, xerrors.New("img name unset")
94+
}
95+
96+
// Get all imported images for each org the user belongs to.
97+
imgs, err := getImportedImgs(ctx, client, email)
98+
if err != nil {
99+
return nil, err
100+
}
101+
102+
var matchingImgs []*coder.Image
103+
104+
// The user may provide an image thats not an exact match
105+
// to one of their imported images but they may be close.
106+
// We can assist the user by collecting images that contain
107+
// the user provided image flag value as a substring.
108+
for _, img := range imgs {
109+
if strings.Contains(img.Repository, imgName) {
110+
matchingImgs = append(matchingImgs, img)
111+
}
112+
}
113+
114+
numImgs := len(matchingImgs)
115+
116+
if numImgs == 0 {
117+
return nil, xerrors.New("image not found - did you forget to import this image?")
118+
}
119+
120+
if numImgs > 1 {
121+
var lines []string
122+
123+
for _, img := range matchingImgs {
124+
lines = append(lines, img.Repository)
125+
}
126+
127+
clog.LogInfo(
128+
fmt.Sprintf("Found %d possible matches for %q.", numImgs, imgName),
129+
clog.Tipf("Did you mean? %s", strings.Join(lines, "\n")),
130+
)
131+
132+
return nil, xerrors.New("please refine your search")
133+
}
134+
return matchingImgs[0], nil
135+
}
136+
137+
func getImportedImgs(ctx context.Context, client *coder.Client, userEmail string) ([]*coder.Image, error) {
138+
u, err := client.UserByEmail(ctx, userEmail)
139+
if err != nil {
140+
return nil, err
141+
}
142+
143+
orgs, err := client.Organizations(ctx)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
orgs = lookupUserOrgs(u, orgs)
149+
var importedImgs []*coder.Image
150+
151+
// Get all of the imported images for all of the orgs the user belongs to.
152+
for _, org := range orgs {
153+
imgs, err := client.GetOrganizationImages(ctx, org.ID)
154+
if err != nil {
155+
return nil, err
156+
}
157+
importedImgs = append(importedImgs, imgs...)
158+
}
159+
return importedImgs, nil
160+
}

internal/cmd/envs.go

+42-23
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,16 @@ import (
99
"cdr.dev/coder-cli/coder-sdk"
1010
"cdr.dev/coder-cli/internal/clog"
1111
"cdr.dev/coder-cli/internal/x/xtabwriter"
12+
1213
"github.com/manifoldco/promptui"
1314
"github.com/spf13/cobra"
1415
"golang.org/x/sync/errgroup"
1516
"golang.org/x/xerrors"
1617
)
1718

1819
const (
19-
defaultOrg = "default"
20-
defaultImgTag = "latest"
21-
defaultCPUCores float32 = 1
22-
defaultMemGB float32 = 1
23-
defaultDiskGB = 10
24-
defaultGPUs = 0
20+
defaultOrg = "default"
21+
defaultImgTag = "latest"
2522
)
2623

2724
func envsCommand() *cobra.Command {
@@ -39,7 +36,7 @@ func envsCommand() *cobra.Command {
3936
rmEnvsCommand(&user),
4037
watchBuildLogCommand(),
4138
rebuildEnvCommand(),
42-
createEnvCommand(),
39+
createEnvCommand(&user),
4340
editEnvCommand(&user),
4441
)
4542
return cmd
@@ -147,7 +144,7 @@ coder envs --user charlie@coder.com ls -o json \
147144
}
148145
}
149146

150-
func createEnvCommand() *cobra.Command {
147+
func createEnvCommand(user *string) *cobra.Command {
151148
var (
152149
org string
153150
img string
@@ -171,22 +168,40 @@ coder envs create --cpu 4 --disk 100 --memory 8 --image 5f443b16-30652892427b955
171168
if img == "" {
172169
return xerrors.New("image id unset")
173170
}
171+
172+
client, err := newClient()
173+
if err != nil {
174+
return err
175+
}
176+
177+
importedImg, err := findImg(cmd.Context(), client, *user, img)
178+
if err != nil {
179+
return err
180+
}
181+
174182
// ExactArgs(1) ensures our name value can't panic on an out of bounds.
175183
createReq := &coder.CreateEnvironmentRequest{
176184
Name: args[0],
177-
ImageID: img,
185+
ImageID: importedImg.ID,
178186
ImageTag: tag,
179187
}
180-
// We're explicitly ignoring errors for these because all of these flags
181-
// have a non-zero-value default value set already.
188+
// We're explicitly ignoring errors for these because all we
189+
// need to now is if the numeric type is 0 or not.
182190
createReq.CPUCores, _ = cmd.Flags().GetFloat32("cpu")
183191
createReq.MemoryGB, _ = cmd.Flags().GetFloat32("memory")
184192
createReq.DiskGB, _ = cmd.Flags().GetInt("disk")
185193
createReq.GPUs, _ = cmd.Flags().GetInt("gpus")
186194

187-
client, err := newClient()
188-
if err != nil {
189-
return err
195+
// if any of these defaulted to their zero value we provision
196+
// the create request with the imported image defaults instead.
197+
if createReq.CPUCores == 0 {
198+
createReq.CPUCores = importedImg.DefaultCPUCores
199+
}
200+
if createReq.MemoryGB == 0 {
201+
createReq.MemoryGB = importedImg.DefaultMemoryGB
202+
}
203+
if createReq.DiskGB == 0 {
204+
createReq.DiskGB = importedImg.DefaultDiskGB
190205
}
191206

192207
env, err := client.CreateEnvironment(cmd.Context(), org, *createReq)
@@ -211,11 +226,11 @@ coder envs create --cpu 4 --disk 100 --memory 8 --image 5f443b16-30652892427b955
211226
}
212227
cmd.Flags().StringVarP(&org, "org", "o", defaultOrg, "ID of the organization the environment should be created under.")
213228
cmd.Flags().StringVarP(&tag, "tag", "t", defaultImgTag, "tag of the image the environment will be based off of.")
214-
cmd.Flags().Float32P("cpu", "c", defaultCPUCores, "number of cpu cores the environment should be provisioned with.")
215-
cmd.Flags().Float32P("memory", "m", defaultMemGB, "GB of RAM an environment should be provisioned with.")
216-
cmd.Flags().IntP("disk", "d", defaultDiskGB, "GB of disk storage an environment should be provisioned with.")
217-
cmd.Flags().IntP("gpus", "g", defaultGPUs, "number GPUs an environment should be provisioned with.")
218-
cmd.Flags().StringVarP(&img, "image", "i", "", "ID of the image to base the environment off of.")
229+
cmd.Flags().Float32P("cpu", "c", 0, "number of cpu cores the environment should be provisioned with.")
230+
cmd.Flags().Float32P("memory", "m", 0, "GB of RAM an environment should be provisioned with.")
231+
cmd.Flags().IntP("disk", "d", 0, "GB of disk storage an environment should be provisioned with.")
232+
cmd.Flags().IntP("gpus", "g", 0, "number GPUs an environment should be provisioned with.")
233+
cmd.Flags().StringVarP(&img, "image", "i", "", "name of the image to base the environment off of.")
219234
cmd.Flags().BoolVar(&follow, "follow", false, "follow buildlog after initiating rebuild")
220235
_ = cmd.MarkFlagRequired("image")
221236
return cmd
@@ -289,10 +304,14 @@ coder envs edit back-end-env --disk 20`,
289304
updateReq.GPUs = &gpus
290305
}
291306

292-
if img == "" {
293-
updateReq.ImageID = &env.ImageID
307+
if img != "" {
308+
importedImg, err := findImg(cmd.Context(), client, *user, img)
309+
if err != nil {
310+
return err
311+
}
312+
updateReq.ImageID = &importedImg.ID
294313
} else {
295-
updateReq.ImageID = &img
314+
updateReq.ImageID = &env.ImageID
296315
}
297316

298317
if tag == "" {
@@ -320,7 +339,7 @@ coder envs edit back-end-env --disk 20`,
320339
return nil
321340
},
322341
}
323-
cmd.Flags().StringVarP(&img, "image", "i", "", "image ID of the image you wan't the environment to be based off of.")
342+
cmd.Flags().StringVarP(&img, "image", "i", "", "name of the image you wan't the environment to be based off of.")
324343
cmd.Flags().StringVarP(&tag, "tag", "t", "latest", "image tag of the image you wan't to base the environment off of.")
325344
cmd.Flags().Float32P("cpu", "c", cpuCores, "The number of cpu cores the environment should be provisioned with.")
326345
cmd.Flags().Float32P("memory", "m", memGB, "The amount of RAM an environment should be provisioned with.")

swap.sh

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/bash
2+
rm $(which coder) && go install ./cmd/coder/*.go && echo "swapped binary"

0 commit comments

Comments
 (0)