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

Commit 87b9aaf

Browse files
committed
Add envs create command
1 parent 8917fb8 commit 87b9aaf

File tree

5 files changed

+161
-7
lines changed

5 files changed

+161
-7
lines changed

ci/integration/envs_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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("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("environment name must conform to regex ^[a-z0-9]([a-z0-9-]+)?")),
53+
)
54+
55+
// TODO(Faris) : uncomment this when we can safely purge the environments
56+
// the integrations tests would create in the sidecar
57+
// Successfully provision environment with fractional resource amounts
58+
// c.Run(ctx, fmt.Sprintf(`coder envs create -i %s -c 1.2 -m 1.4 non-whole-resource-amounts`, ubuntuImgID)).Assert(t,
59+
// tcli.Success(),
60+
// tcli.StderrMatches(regexp.QuoteMeta("Successfully created environment \"non-whole-resource-amounts\"")),
61+
// )
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+
)
68+
})
69+
}

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/cmd.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ var verbose bool = false
1313
// Make constructs the "coder" root command
1414
func Make() *cobra.Command {
1515
app := &cobra.Command{
16-
Use: "coder",
17-
Short: "coder provides a CLI for working with an existing Coder Enterprise installation",
18-
SilenceErrors: true,
19-
SilenceUsage: true,
16+
Use: "coder",
17+
Short: "coder provides a CLI for working with an existing Coder Enterprise installation",
18+
SilenceErrors: true,
19+
SilenceUsage: true,
20+
DisableAutoGenTag: true,
2021
}
2122

2223
app.AddCommand(

internal/cmd/envs.go

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import (
1414
"golang.org/x/xerrors"
1515
)
1616

17+
const (
18+
defaultOrg = "default"
19+
defaultImgTag = "latest"
20+
defaultCPUCores float32 = 1
21+
defaultMemGB float32 = 1
22+
defaultDiskGB = 10
23+
defaultGPUs = 0
24+
)
25+
1726
func envsCommand() *cobra.Command {
1827
var outputFmt string
1928
var user string
@@ -64,9 +73,9 @@ func envsCommand() *cobra.Command {
6473
lsCmd.Flags().StringVarP(&outputFmt, "output", "o", "human", "human | json")
6574
cmd.AddCommand(lsCmd)
6675
cmd.AddCommand(stopEnvCommand(&user))
67-
6876
cmd.AddCommand(watchBuildLogCommand())
6977
cmd.AddCommand(rebuildEnvCommand())
78+
cmd.AddCommand(createEnvCommand())
7079
return cmd
7180
}
7281

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

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