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

Commit 1a85240

Browse files
authored
feat: split parsing config from creating env (#234)
- Add an endpoint to parse a WAC template. - Add support for specifying a local file. - Decoupled creating an environment from parsing a template (in the API).
1 parent 0da2ebf commit 1a85240

File tree

4 files changed

+176
-57
lines changed

4 files changed

+176
-57
lines changed

coder-sdk/env.go

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coder
22

33
import (
44
"context"
5+
"io"
56
"net/http"
67
"net/url"
78
"time"
@@ -73,17 +74,16 @@ const (
7374

7475
// CreateEnvironmentRequest is used to configure a new environment.
7576
type CreateEnvironmentRequest struct {
76-
Name string `json:"name"`
77-
ImageID string `json:"image_id"`
78-
OrgID string `json:"org_id"`
79-
ImageTag string `json:"image_tag"`
80-
CPUCores float32 `json:"cpu_cores"`
81-
MemoryGB float32 `json:"memory_gb"`
82-
DiskGB int `json:"disk_gb"`
83-
GPUs int `json:"gpus"`
84-
Services []string `json:"services"`
85-
UseContainerVM bool `json:"use_container_vm"`
86-
Template *Template `json:"template"`
77+
Name string `json:"name"`
78+
ImageID string `json:"image_id"`
79+
OrgID string `json:"org_id"`
80+
ImageTag string `json:"image_tag"`
81+
CPUCores float32 `json:"cpu_cores"`
82+
MemoryGB float32 `json:"memory_gb"`
83+
DiskGB int `json:"disk_gb"`
84+
GPUs int `json:"gpus"`
85+
Services []string `json:"services"`
86+
UseContainerVM bool `json:"use_container_vm"`
8787
}
8888

8989
// CreateEnvironment sends a request to create an environment.
@@ -95,14 +95,60 @@ func (c Client) CreateEnvironment(ctx context.Context, req CreateEnvironmentRequ
9595
return &env, nil
9696
}
9797

98-
// Template is used to configure a new environment from a repo.
99-
// It is currently in alpha and subject to API-breaking change.
98+
// ParseTemplateRequest parses a template. If Local is a non-nil reader
99+
// it will obviate any other fields on the request.
100+
type ParseTemplateRequest struct {
101+
RepoURL string `json:"repo_url"`
102+
Ref string `json:"ref"`
103+
Local io.Reader `json:"-"`
104+
}
105+
106+
// Template is a Workspaces As Code (WAC) template.
100107
type Template struct {
101-
RepositoryURL string `json:"repository_url"`
102-
// Optional. The default branch will be used if not provided.
103-
Branch string `json:"branch"`
104-
// Optional. The template name will be used if not provided.
105-
FileName string `json:"file_name"`
108+
Workspace Workspace `json:"workspace"`
109+
}
110+
111+
// Workspace defines values on the workspace that can be configured.
112+
type Workspace struct {
113+
Name string `json:"name"`
114+
Image string `json:"image"`
115+
ContainerBasedVM bool `json:"container-based-vm"`
116+
Resources Resources `json:"resources"`
117+
}
118+
119+
// Resources defines compute values that can be configured for a workspace.
120+
type Resources struct {
121+
CPU float32 `json:"cpu" `
122+
Memory float32 `json:"memory"`
123+
Disk int `json:"disk"`
124+
}
125+
126+
// ParseTemplate parses a template config. It support both remote repositories and local files.
127+
// If a local file is specified then all other values in the request are ignored.
128+
func (c Client) ParseTemplate(ctx context.Context, req ParseTemplateRequest) (Template, error) {
129+
const path = "/api/private/environments/template/parse"
130+
var (
131+
tpl Template
132+
opts []requestOption
133+
headers = http.Header{}
134+
)
135+
136+
if req.Local == nil {
137+
if err := c.requestBody(ctx, http.MethodPost, path, req, &tpl); err != nil {
138+
return tpl, err
139+
}
140+
return tpl, nil
141+
}
142+
143+
headers.Set("Content-Type", "application/octet-stream")
144+
opts = append(opts, withBody(req.Local), withHeaders(headers))
145+
146+
err := c.requestBody(ctx, http.MethodPost, path, nil, &tpl, opts...)
147+
if err != nil {
148+
return tpl, err
149+
}
150+
151+
return tpl, nil
106152
}
107153

108154
// CreateEnvironmentFromRepo sends a request to create an environment from a repository.

coder-sdk/request.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,45 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10+
"net/url"
1011

1112
"golang.org/x/xerrors"
1213
)
1314

15+
type requestOptions struct {
16+
BaseURLOverride *url.URL
17+
Query url.Values
18+
Headers http.Header
19+
Reader io.Reader
20+
}
21+
22+
type requestOption func(*requestOptions)
23+
24+
// withQueryParams sets the provided query parameters on the request.
25+
func withQueryParams(q url.Values) func(o *requestOptions) {
26+
return func(o *requestOptions) {
27+
o.Query = q
28+
}
29+
}
30+
31+
func withHeaders(h http.Header) func(o *requestOptions) {
32+
return func(o *requestOptions) {
33+
o.Headers = h
34+
}
35+
}
36+
37+
func withBaseURL(base *url.URL) func(o *requestOptions) {
38+
return func(o *requestOptions) {
39+
o.BaseURLOverride = base
40+
}
41+
}
42+
43+
func withBody(w io.Reader) func(o *requestOptions) {
44+
return func(o *requestOptions) {
45+
o.Reader = w
46+
}
47+
}
48+
1449
// request is a helper to set the cookie, marshal the payload and execute the request.
1550
func (c Client) request(ctx context.Context, method, path string, in interface{}, options ...requestOption) (*http.Response, error) {
1651
// Create a default http client with the auth in the cookie.
@@ -30,7 +65,6 @@ func (c Client) request(ctx context.Context, method, path string, in interface{}
3065
if config.Query != nil {
3166
url.RawQuery = config.Query.Encode()
3267
}
33-
3468
url.Path = path
3569

3670
// If we have incoming data, encode it as json.
@@ -43,12 +77,20 @@ func (c Client) request(ctx context.Context, method, path string, in interface{}
4377
payload = bytes.NewReader(body)
4478
}
4579

80+
if config.Reader != nil {
81+
payload = config.Reader
82+
}
83+
4684
// Create the http request.
4785
req, err := http.NewRequestWithContext(ctx, method, url.String(), payload)
4886
if err != nil {
4987
return nil, xerrors.Errorf("create request: %w", err)
5088
}
5189

90+
if config.Headers != nil {
91+
req.Header = config.Headers
92+
}
93+
5294
// Execute the request.
5395
return client.Do(req)
5496
}

coder-sdk/ws.go

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,10 @@ package coder
33
import (
44
"context"
55
"net/http"
6-
"net/url"
76

87
"nhooyr.io/websocket"
98
)
109

11-
type requestOptions struct {
12-
BaseURLOverride *url.URL
13-
Query url.Values
14-
}
15-
16-
type requestOption func(*requestOptions)
17-
18-
// withQueryParams sets the provided query parameters on the request.
19-
func withQueryParams(q url.Values) func(o *requestOptions) {
20-
return func(o *requestOptions) {
21-
o.Query = q
22-
}
23-
}
24-
25-
func withBaseURL(base *url.URL) func(o *requestOptions) {
26-
return func(o *requestOptions) {
27-
o.BaseURLOverride = base
28-
}
29-
}
30-
3110
// dialWebsocket establish the websocket connection while setting the authentication header.
3211
func (c Client) dialWebsocket(ctx context.Context, path string, options ...requestOption) (*websocket.Conn, error) {
3312
// Make a copy of the url so we can update the scheme to ws(s) without mutating the state.

internal/cmd/envs.go

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"fmt"
8+
"io"
9+
"io/ioutil"
10+
"net/url"
711
"os"
812

913
"cdr.dev/coder-cli/coder-sdk"
@@ -254,20 +258,21 @@ coder envs create my-new-powerful-env --cpu 12 --disk 100 --memory 16 --image ub
254258

255259
func createEnvFromRepoCmd() *cobra.Command {
256260
var (
257-
branch string
258-
name string
259-
follow bool
261+
ref string
262+
repo string
263+
follow bool
264+
filepath string
265+
org string
260266
)
261267

262268
cmd := &cobra.Command{
263-
Use: "create-from-repo [environment_name]",
264-
Short: "create a new environment from a git repository.",
265-
Args: xcobra.ExactArgs(1),
266-
Long: "Create a new Coder environment from a Git repository.",
269+
Use: "create-from-config",
270+
Short: "create a new environment from a config file.",
271+
Long: "Create a new Coder environment from a config file.",
267272
Hidden: true,
268273
Example: `# create a new environment from git repository template
269-
coder envs create-from-repo github.com/cdr/m
270-
coder envs create-from-repo github.com/cdr/m --branch envs-as-code`,
274+
coder envs create-from-repo --repo-url github.com/cdr/m --branch my-branch
275+
coder envs create-from-repo -f coder.yaml`,
271276
RunE: func(cmd *cobra.Command, args []string) error {
272277
ctx := cmd.Context()
273278

@@ -276,15 +281,60 @@ coder envs create-from-repo github.com/cdr/m --branch envs-as-code`,
276281
return err
277282
}
278283

279-
// ExactArgs(1) ensures our name value can't panic on an out of bounds.
280-
createReq := &coder.Template{
281-
RepositoryURL: args[0],
282-
Branch: branch,
283-
FileName: name,
284+
if repo != "" {
285+
_, err = url.Parse(repo)
286+
if err != nil {
287+
return xerrors.Errorf("'repo' must be a valid url: %w", err)
288+
}
289+
}
290+
291+
multiOrgMember, err := isMultiOrgMember(ctx, client, coder.Me)
292+
if err != nil {
293+
return err
294+
}
295+
296+
if multiOrgMember && org == "" {
297+
return xerrors.New("org is required for multi-org members")
298+
}
299+
300+
var rd io.Reader
301+
if filepath != "" {
302+
b, err := ioutil.ReadFile(filepath)
303+
if err != nil {
304+
return xerrors.Errorf("read local file: %w", err)
305+
}
306+
rd = bytes.NewReader(b)
307+
}
308+
309+
req := coder.ParseTemplateRequest{
310+
RepoURL: repo,
311+
Ref: ref,
312+
Local: rd,
313+
}
314+
315+
tpl, err := client.ParseTemplate(ctx, req)
316+
if err != nil {
317+
return xerrors.Errorf("parse environment template config: %w", err)
318+
}
319+
320+
importedImg, err := findImg(ctx, client, findImgConf{
321+
email: coder.Me,
322+
imgName: tpl.Workspace.Image,
323+
orgName: org,
324+
})
325+
if err != nil {
326+
return err
284327
}
285328

286329
env, err := client.CreateEnvironment(ctx, coder.CreateEnvironmentRequest{
287-
Template: createReq,
330+
Name: tpl.Workspace.Name,
331+
ImageID: importedImg.ID,
332+
OrgID: importedImg.OrganizationID,
333+
ImageTag: importedImg.DefaultTag.Tag,
334+
CPUCores: tpl.Workspace.Resources.CPU,
335+
MemoryGB: tpl.Workspace.Resources.Memory,
336+
DiskGB: tpl.Workspace.Resources.Disk,
337+
UseContainerVM: tpl.Workspace.ContainerBasedVM,
288338
})
289339
if err != nil {
290340
return xerrors.Errorf("create environment: %w", err)
@@ -305,8 +355,10 @@ coder envs create-from-repo github.com/cdr/m --branch envs-as-code`,
305355
return nil
306356
},
307357
}
308-
cmd.Flags().StringVarP(&branch, "branch", "b", "master", "name of the branch to create the environment from.")
309-
cmd.Flags().StringVarP(&name, "name", "n", "coder.yaml", "name of the config file.")
358+
cmd.Flags().StringVarP(&org, "org", "o", "", "name of the organization the environment should be created under.")
359+
cmd.Flags().StringVarP(&filepath, "filepath", "f", "", "path to local template file.")
360+
cmd.Flags().StringVarP(&ref, "ref", "", "master", "git reference to pull template from. May be a branch, tag, or commit hash.")
361+
cmd.Flags().StringVarP(&repo, "repo-url", "r", "", "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'.")
310362
cmd.Flags().BoolVar(&follow, "follow", false, "follow buildlog after initiating rebuild")
311363
return cmd
312364
}

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