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

Lines changed: 70 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 92 additions & 1 deletion
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)
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