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

Commit 58b4d42

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

File tree

5 files changed

+324
-81
lines changed

5 files changed

+324
-81
lines changed

ci/integration/envs_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
)
1010

1111
// From Coder organization images
12-
const ubuntuImgID = "5f443b16-30652892427b955601330fa5"
12+
// const ubuntuImgID = "5f443b16-30652892427b955601330fa5"
1313

1414
func TestEnvsCLI(t *testing.T) {
1515
t.Parallel()
@@ -37,6 +37,18 @@ func TestEnvsCLI(t *testing.T) {
3737
tcli.StderrEmpty(),
3838
)
3939

40+
// Image unset.
41+
c.Run(ctx, "coder envs create test-env").Assert(t,
42+
tcli.StderrMatches(regexp.QuoteMeta("fatal: required flag(s) \"image\" not set")),
43+
tcli.Error(),
44+
)
45+
46+
// Image not imported.
47+
c.Run(ctx, "coder envs create test-env --image doesntmatter").Assert(t,
48+
tcli.StderrMatches(regexp.QuoteMeta("fatal: image not found - did you forget to import this image?")),
49+
tcli.Error(),
50+
)
51+
4052
// TODO(Faris) : uncomment this when we can safely purge the environments
4153
// the integrations tests would create in the sidecar
4254
// Successfully create environment.
@@ -46,24 +58,12 @@ func TestEnvsCLI(t *testing.T) {
4658
// tcli.StderrMatches(regexp.QuoteMeta("Successfully created environment \"test-ubuntu\"")),
4759
// )
4860

49-
// Invalid environment name should fail.
50-
c.Run(ctx, "coder envs create --image "+ubuntuImgID+" this-IS-an-invalid-EnvironmentName").Assert(t,
51-
tcli.Error(),
52-
tcli.StderrMatches(regexp.QuoteMeta("environment name must conform to regex ^[a-z0-9]([a-z0-9-]+)?")),
53-
)
54-
5561
// TODO(Faris) : uncomment this when we can safely purge the environments
5662
// the integrations tests would create in the sidecar
5763
// Successfully provision environment with fractional resource amounts
5864
// c.Run(ctx, fmt.Sprintf(`coder envs create -i %s -c 1.2 -m 1.4 non-whole-resource-amounts`, ubuntuImgID)).Assert(t,
5965
// tcli.Success(),
6066
// tcli.StderrMatches(regexp.QuoteMeta("Successfully created environment \"non-whole-resource-amounts\"")),
6167
// )
62-
63-
// Image does not exist should fail.
64-
c.Run(ctx, "coder envs create --image does-not-exist env-will-not-be-created").Assert(t,
65-
tcli.Error(),
66-
tcli.StderrMatches(regexp.QuoteMeta("does not exist")),
67-
)
6868
})
6969
}

coder-sdk/image.go

Lines changed: 10 additions & 1 deletion
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,12 @@ 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 orgID.
52+
func (c Client) GetOrganizationImages(ctx context.Context, orgID string) ([]Image, error) {
53+
var imgs []Image
54+
if err := c.requestBody(ctx, http.MethodGet, "/api/orgs/"+orgID+"/images", nil, &imgs); err != nil {
55+
return nil, err
56+
}
57+
return imgs, nil
58+
}

internal/clog/error.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ func LogSuccess(header string, lines ...string) {
6868
}.String())
6969
}
7070

71+
// LogWarn prints the given warn message to stderr.
72+
func LogWarn(header string, lines ...string) {
73+
fmt.Fprint(os.Stderr, CLIMessage{
74+
Level: "warning",
75+
Color: color.FgYellow,
76+
Header: header,
77+
Lines: lines,
78+
}.String())
79+
}
80+
7181
// Warn creates an error with the level "warning".
7282
func Warn(header string, lines ...string) CLIError {
7383
return CLIError{

internal/cmd/ceapi.go

Lines changed: 109 additions & 0 deletions
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,111 @@ 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, orgName string) (*coder.Image, error) {
87+
switch {
88+
case email == "":
89+
return nil, xerrors.New("user email unset")
90+
case imgName == "":
91+
return nil, xerrors.New("image name unset")
92+
}
93+
94+
imgs, err := getImgs(ctx,
95+
getImgsConf{
96+
client: client,
97+
email: email,
98+
orgName: orgName,
99+
},
100+
)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
var possibleMatches []coder.Image
106+
107+
// The user may provide an image thats not an exact match
108+
// to one of their imported images but they may be close.
109+
// We can assist the user by collecting images that contain
110+
// the user provided image flag value as a substring.
111+
for _, img := range imgs {
112+
// If it's an exact match we can just return and exit.
113+
if img.Repository == imgName {
114+
return &img, nil
115+
}
116+
if strings.Contains(img.Repository, imgName) {
117+
possibleMatches = append(possibleMatches, img)
118+
}
119+
}
120+
121+
if len(possibleMatches) == 0 {
122+
return nil, xerrors.New("image not found - did you forget to import this image?")
123+
}
124+
125+
lines := []string{clog.Tipf("Did you mean?")}
126+
127+
for _, img := range possibleMatches {
128+
lines = append(lines, img.Repository)
129+
}
130+
return nil, clog.Fatal(
131+
fmt.Sprintf("Found %d possible matches for %q.", len(possibleMatches), imgName),
132+
lines...,
133+
)
134+
}
135+
136+
type getImgsConf struct {
137+
client *coder.Client
138+
email string
139+
orgName string
140+
}
141+
142+
func getImgs(ctx context.Context, conf getImgsConf) ([]coder.Image, error) {
143+
u, err := conf.client.UserByEmail(ctx, conf.email)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
orgs, err := conf.client.Organizations(ctx)
149+
if err != nil {
150+
return nil, err
151+
}
152+
153+
orgs = lookupUserOrgs(u, orgs)
154+
155+
for _, org := range orgs {
156+
imgs, err := conf.client.GetOrganizationImages(ctx, org.ID)
157+
if err != nil {
158+
return nil, err
159+
}
160+
// If orgName is set we know the user is a multi-org member
161+
// so we should only return the imported images that beong to the org they specified.
162+
if conf.orgName != "" && conf.orgName == org.Name {
163+
return imgs, nil
164+
}
165+
166+
if conf.orgName == "" {
167+
// if orgName is unset we know the user is only part of one org.
168+
return imgs, nil
169+
}
170+
}
171+
return nil, xerrors.Errorf("org name %q not found", conf.orgName)
172+
}
173+
174+
func isMultiOrgMember(ctx context.Context, email string) (*bool, error) {
175+
client, err := newClient()
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
u, err := client.UserByEmail(ctx, email)
181+
if err != nil {
182+
return nil, xerrors.New("email not found")
183+
}
184+
185+
orgs, err := client.Organizations(ctx)
186+
if err != nil {
187+
return nil, err
188+
}
189+
190+
isMulti := len(lookupUserOrgs(u, orgs)) > 1
191+
return &isMulti, nil
192+
}

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