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

Commit 5c51b63

Browse files
committed
Add envs create command
1 parent 282f351 commit 5c51b63

File tree

4 files changed

+164
-3
lines changed

4 files changed

+164
-3
lines changed

ci/integration/envs_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"regexp"
6+
"testing"
7+
8+
"cdr.dev/coder-cli/ci/tcli"
9+
)
10+
11+
// From Coder organization images
12+
const ubuntuImgID = "5f443b16-30652892427b955601330fa5"
13+
14+
func TestEnvsCLI(t *testing.T) {
15+
t.Parallel()
16+
17+
run(t, "coder-cli-env-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) {
18+
headlessLogin(ctx, t, c)
19+
20+
// Ensure binary is present.
21+
c.Run(ctx, "which coder").Assert(t,
22+
tcli.Success(),
23+
tcli.StdoutMatches("/usr/sbin/coder"),
24+
tcli.StderrEmpty(),
25+
)
26+
27+
// Minimum args not received.
28+
c.Run(ctx, "coder envs create").Assert(t,
29+
tcli.StderrMatches(regexp.QuoteMeta("Error: accepts 1 arg(s), received 0")),
30+
tcli.Error(),
31+
)
32+
33+
// Successfully output help.
34+
c.Run(ctx, "coder envs create --help").Assert(t,
35+
tcli.Success(),
36+
tcli.StdoutMatches(regexp.QuoteMeta("Create a new environment under the active user.")),
37+
tcli.StderrEmpty(),
38+
)
39+
40+
// TODO(Faris) : uncomment this when we can safely purge the environments
41+
// the integrations tests would create in the sidecar
42+
// Successfully create environment.
43+
// c.Run(ctx, "coder envs create --image "+ubuntuImgID+" test-ubuntu").Assert(t,
44+
// tcli.Success(),
45+
// // why does flog.Success write to stderr?
46+
// tcli.StderrMatches(regexp.QuoteMeta("Successfully created environment \"test-ubuntu\"")),
47+
// )
48+
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("HTTP/2.0 400 Bad Request")),
53+
tcli.StderrMatches(regexp.QuoteMeta("environment name must conform to regex ^[a-z0-9]([a-z0-9-]+)?")),
54+
)
55+
56+
// TODO(Faris) : uncomment this when we can safely purge the environments
57+
// the integrations tests would create in the sidecar
58+
// Successfully provision environment with fractional resource amounts
59+
// c.Run(ctx, fmt.Sprintf(`coder envs create -i %s -c 1.2 -m 1.4 non-whole-resource-amounts`, ubuntuImgID)).Assert(t,
60+
// tcli.Success(),
61+
// tcli.StderrMatches(regexp.QuoteMeta("Successfully created environment \"non-whole-resource-amounts\"")),
62+
// )
63+
64+
// Image does not exist should fail.
65+
c.Run(ctx, "coder envs create --image does-not-exist env-will-not-be-created").Assert(t,
66+
tcli.Error(),
67+
tcli.StderrMatches(regexp.QuoteMeta("does not exist")),
68+
)
69+
})
70+
}

ci/steps/gendocs.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ go run ./cmd/coder gen-docs ./docs
1313

1414
# remove cobra footer from each file
1515
for filename in ./docs/*.md; do
16-
trimmed=$(head -n -1 "$filename")
16+
trimmed=$(head -n $(( $(wc -l "$filename" | awk '{print $1}') - 1 )) "$filename")
1717
echo "$trimmed" >$filename
1818
done
1919

coder-sdk/env.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ type CreateEnvironmentRequest struct {
7878
ImageID string `json:"image_id"`
7979
ImageTag string `json:"image_tag"`
8080
CPUCores float32 `json:"cpu_cores"`
81-
MemoryGB int `json:"memory_gb"`
81+
MemoryGB float32 `json:"memory_gb"`
8282
DiskGB int `json:"disk_gb"`
8383
GPUs int `json:"gpus"`
8484
Services []string `json:"services"`

internal/cmd/envs.go

+92-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ import (
1313
"go.coder.com/flog"
1414
)
1515

16+
const (
17+
defaultOrg = "default"
18+
defaultImgTag = "latest"
19+
defaultCPUCores float32 = 1
20+
defaultMemGB float32 = 1
21+
defaultDiskGB = 10
22+
defaultGPUs = 0
23+
)
24+
1625
func envsCommand() *cobra.Command {
1726
var outputFmt string
1827
var user string
@@ -63,9 +72,9 @@ func envsCommand() *cobra.Command {
6372
lsCmd.Flags().StringVarP(&outputFmt, "output", "o", "human", "human | json")
6473
cmd.AddCommand(lsCmd)
6574
cmd.AddCommand(stopEnvCommand(&user))
66-
6775
cmd.AddCommand(watchBuildLogCommand())
6876
cmd.AddCommand(rebuildEnvCommand())
77+
cmd.AddCommand(createEnvCommand())
6978
return cmd
7079
}
7180

@@ -117,3 +126,85 @@ coder envs --user charlie@coder.com ls -o json \
117126
},
118127
}
119128
}
129+
130+
func createEnvCommand() *cobra.Command {
131+
var (
132+
org string
133+
img string
134+
tag string
135+
services []string
136+
)
137+
138+
cmd := &cobra.Command{
139+
Use: "create [flags] [environment-name]",
140+
Short: "create a new environment.",
141+
Args: cobra.ExactArgs(1),
142+
Hidden: true,
143+
Long: "Create a new environment under the active user.",
144+
Example: `
145+
# create a new environment using default resource amounts
146+
coder envs create \
147+
--image id-of-imported-image \
148+
my-env-name
149+
150+
See Flags section for default values.
151+
152+
# create a new environment using custom resource amounts
153+
coder envs create \
154+
--cores 4 \
155+
--disk 100 \
156+
--memory 8 \
157+
--image id-of-imported-image \
158+
--org id-of-existing-organization \
159+
my-env-name
160+
161+
# same command this time using short-hand flags for a more succinct command experience.
162+
coder envs create \
163+
-c 4 \
164+
-d 100 \
165+
-m 8 \
166+
-i id-of-imported-image my-env-name \
167+
-o id-of-existing-organization \
168+
my-env-name
169+
`,
170+
RunE: func(cmd *cobra.Command, args []string) error {
171+
if img == "" {
172+
return xerrors.New("image id unset")
173+
}
174+
// ExactArgs(1) ensures our name value can't panic on an out of bounds.
175+
createReq := &coder.CreateEnvironmentRequest{
176+
Name: args[0],
177+
ImageID: img,
178+
ImageTag: tag,
179+
}
180+
// We're explicitly ignoring errors for these because all of these flags
181+
// have a non-zero-value default value set already.
182+
createReq.CPUCores, _ = cmd.Flags().GetFloat32("cores")
183+
createReq.MemoryGB, _ = cmd.Flags().GetFloat32("memory")
184+
createReq.DiskGB, _ = cmd.Flags().GetInt("disk")
185+
createReq.GPUs, _ = cmd.Flags().GetInt("gpus")
186+
187+
client, err := newClient()
188+
if err != nil {
189+
return err
190+
}
191+
192+
env, err := client.CreateEnvironment(cmd.Context(), org, *createReq)
193+
if err != nil {
194+
return xerrors.Errorf("create environment: %w", err)
195+
}
196+
flog.Success("Successfully created environment %q", env.Name)
197+
return nil
198+
},
199+
}
200+
cmd.Flags().StringVarP(&org, "org", "o", defaultOrg, "ID of the organization the environment should be created under.")
201+
cmd.Flags().StringVarP(&tag, "tag", "t", defaultImgTag, "The particular tag of the image the environment will be based off of.")
202+
cmd.Flags().Float32P("cores", "c", defaultCPUCores, "The number of cpu cores the environment should be provisioned with.")
203+
cmd.Flags().Float32P("memory", "m", defaultMemGB, "GB of RAM an environment should be provisioned with.")
204+
cmd.Flags().IntP("disk", "d", defaultDiskGB, "GB of disk storage an environment should be provisioned with.")
205+
cmd.Flags().IntP("gpus", "g", defaultGPUs, "The number GPUs an environment should be provisioned with.")
206+
cmd.Flags().StringSliceP("services", "s", services, "The services that the environment should be initialized with.")
207+
cmd.Flags().StringVarP(&img, "image", "i", "", "ID of the image to base the environment off of.")
208+
cmd.MarkFlagRequired("image")
209+
return cmd
210+
}

0 commit comments

Comments
 (0)