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

Commit cb3ac49

Browse files
author
Faris Huskovic
authored
Merge pull request #159 from cdr/faris/ch2149/src-defaults-from-img
Change coder create | edit envs image flag to take image name and source defaults from image
2 parents 7a4312f + dee3f7c commit cb3ac49

File tree

5 files changed

+333
-81
lines changed

5 files changed

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