Skip to content

Commit e5c9555

Browse files
authored
feat: Remove magical parameters from being injected (#371)
* ci: Update DataDog GitHub branch to fallback to GITHUB_REF This was detecting branches, but not our "main" branch before. Hopefully this fixes it! * Add basic Terraform Provider * Rename post files to upload * Add tests for resources * Skip instance identity test * Add tests for ensuring agent get's passed through properly * Fix linting errors * Add echo path * Fix agent authentication * fix: Convert all jobs to use a common resource and agent type This enables a consistent API for project import and provisioned resources. * Add "coder_workspace" data source * feat: Remove magical parameters from being injected This is a much cleaner abstraction. Explicitly declaring the user parameters for each provisioner makes for significantly simpler testing.
1 parent bd0293a commit e5c9555

File tree

19 files changed

+571
-323
lines changed

19 files changed

+571
-323
lines changed

cli/projectcreate.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"golang.org/x/xerrors"
1919

2020
"github.com/coder/coder/coderd"
21-
"github.com/coder/coder/coderd/parameter"
2221
"github.com/coder/coder/codersdk"
2322
"github.com/coder/coder/database"
2423
"github.com/coder/coder/provisionerd"
@@ -187,9 +186,6 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o
187186
if ok {
188187
continue
189188
}
190-
if parameterSchema.Name == parameter.CoderWorkspaceTransition {
191-
continue
192-
}
193189
value, err := prompt(cmd, &promptui.Prompt{
194190
Label: fmt.Sprintf("Enter value for %s:", color.HiCyanString(parameterSchema.Name)),
195191
})

cli/projectcreate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func TestProjectCreate(t *testing.T) {
8686
matches := []string{
8787
"organization?", "y",
8888
"name?", "test-project",
89-
"somevar:", "value",
89+
"somevar", "value",
9090
"project?", "y",
9191
"created!", "n",
9292
}

coderd/cmd/root.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,15 @@ func Root() *cobra.Command {
3333
Use: "coderd",
3434
RunE: func(cmd *cobra.Command, args []string) error {
3535
logger := slog.Make(sloghuman.Sink(os.Stderr))
36+
accessURL := &url.URL{
37+
Scheme: "http",
38+
Host: address,
39+
}
3640
handler, closeCoderd := coderd.New(&coderd.Options{
37-
Logger: logger,
38-
Database: databasefake.New(),
39-
Pubsub: database.NewPubsubInMemory(),
41+
AccessURL: accessURL,
42+
Logger: logger,
43+
Database: databasefake.New(),
44+
Pubsub: database.NewPubsubInMemory(),
4045
})
4146

4247
listener, err := net.Listen("tcp", address)
@@ -45,10 +50,7 @@ func Root() *cobra.Command {
4550
}
4651
defer listener.Close()
4752

48-
client := codersdk.New(&url.URL{
49-
Scheme: "http",
50-
Host: address,
51-
})
53+
client := codersdk.New(accessURL)
5254
daemonClose, err := newProvisionerDaemon(cmd.Context(), client, logger)
5355
if err != nil {
5456
return xerrors.Errorf("create provisioner daemon: %w", err)

coderd/coderd.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coderd
22

33
import (
44
"net/http"
5+
"net/url"
56
"sync"
67

78
"github.com/go-chi/chi/v5"
@@ -16,9 +17,10 @@ import (
1617

1718
// Options are requires parameters for Coder to start.
1819
type Options struct {
19-
Logger slog.Logger
20-
Database database.Store
21-
Pubsub database.Pubsub
20+
AccessURL *url.URL
21+
Logger slog.Logger
22+
Database database.Store
23+
Pubsub database.Pubsub
2224

2325
GoogleTokenValidator *idtoken.Validator
2426
}

coderd/coderdtest/coderdtest.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,7 @@ func New(t *testing.T, options *Options) *codersdk.Client {
8080
})
8181
}
8282

83-
handler, closeWait := coderd.New(&coderd.Options{
84-
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
85-
Database: db,
86-
Pubsub: pubsub,
87-
88-
GoogleTokenValidator: options.GoogleTokenValidator,
89-
})
90-
srv := httptest.NewUnstartedServer(handler)
83+
srv := httptest.NewUnstartedServer(nil)
9184
srv.Config.BaseContext = func(_ net.Listener) context.Context {
9285
ctx, cancelFunc := context.WithCancel(context.Background())
9386
t.Cleanup(cancelFunc)
@@ -96,6 +89,16 @@ func New(t *testing.T, options *Options) *codersdk.Client {
9689
srv.Start()
9790
serverURL, err := url.Parse(srv.URL)
9891
require.NoError(t, err)
92+
var closeWait func()
93+
// We set the handler after server creation for the access URL.
94+
srv.Config.Handler, closeWait = coderd.New(&coderd.Options{
95+
AccessURL: serverURL,
96+
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
97+
Database: db,
98+
Pubsub: pubsub,
99+
100+
GoogleTokenValidator: options.GoogleTokenValidator,
101+
})
99102
t.Cleanup(func() {
100103
srv.Close()
101104
closeWait()

coderd/parameter/compute.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ import (
1111
"github.com/coder/coder/database"
1212
)
1313

14-
const (
15-
CoderUsername = "coder_username"
16-
CoderWorkspaceTransition = "coder_workspace_transition"
17-
)
18-
1914
// ComputeScope targets identifiers to pull parameters from.
2015
type ComputeScope struct {
2116
ProjectImportJobID uuid.UUID

coderd/provisionerdaemons.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"io"
1010
"net/http"
11+
"net/url"
1112
"reflect"
1213
"time"
1314

@@ -90,6 +91,7 @@ func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request)
9091
}
9192
mux := drpcmux.New()
9293
err = proto.DRPCRegisterProvisionerDaemon(mux, &provisionerdServer{
94+
AccessURL: api.AccessURL,
9395
ID: daemon.ID,
9496
Database: api.Database,
9597
Pubsub: api.Pubsub,
@@ -117,6 +119,7 @@ type workspaceProvisionJob struct {
117119

118120
// Implementation of the provisioner daemon protobuf server.
119121
type provisionerdServer struct {
122+
AccessURL *url.URL
120123
ID uuid.UUID
121124
Logger slog.Logger
122125
Provisioners []database.ProvisionerType
@@ -228,23 +231,30 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
228231
}
229232
protoParameters = append(protoParameters, converted)
230233
}
231-
protoParameters = append(protoParameters, &sdkproto.ParameterValue{
232-
DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE,
233-
Name: parameter.CoderWorkspaceTransition,
234-
Value: string(workspaceHistory.Transition),
235-
})
234+
transition, err := convertWorkspaceTransition(workspaceHistory.Transition)
235+
if err != nil {
236+
return nil, failJob(fmt.Sprint("convert workspace transition: %w", err))
237+
}
236238

237239
protoJob.Type = &proto.AcquiredJob_WorkspaceProvision_{
238240
WorkspaceProvision: &proto.AcquiredJob_WorkspaceProvision{
239241
WorkspaceHistoryId: workspaceHistory.ID.String(),
240242
WorkspaceName: workspace.Name,
241243
State: workspaceHistory.ProvisionerState,
242244
ParameterValues: protoParameters,
245+
Metadata: &sdkproto.Provision_Metadata{
246+
CoderUrl: server.AccessURL.String(),
247+
WorkspaceTransition: transition,
248+
},
243249
},
244250
}
245251
case database.ProvisionerJobTypeProjectVersionImport:
246252
protoJob.Type = &proto.AcquiredJob_ProjectImport_{
247-
ProjectImport: &proto.AcquiredJob_ProjectImport{},
253+
ProjectImport: &proto.AcquiredJob_ProjectImport{
254+
Metadata: &sdkproto.Provision_Metadata{
255+
CoderUrl: server.AccessURL.String(),
256+
},
257+
},
248258
}
249259
}
250260
switch job.StorageMethod {
@@ -660,3 +670,14 @@ func convertComputedParameterValue(param parameter.ComputedValue) (*sdkproto.Par
660670
Value: param.SourceValue,
661671
}, nil
662672
}
673+
674+
func convertWorkspaceTransition(transition database.WorkspaceTransition) (sdkproto.WorkspaceTransition, error) {
675+
switch transition {
676+
case database.WorkspaceTransitionStart:
677+
return sdkproto.WorkspaceTransition_START, nil
678+
case database.WorkspaceTransitionStop:
679+
return sdkproto.WorkspaceTransition_STOP, nil
680+
default:
681+
return 0, xerrors.Errorf("unrecognized transition: %q", transition)
682+
}
683+
}

coderd/provisionerjobs_test.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010

1111
"github.com/coder/coder/coderd"
1212
"github.com/coder/coder/coderd/coderdtest"
13-
"github.com/coder/coder/coderd/parameter"
1413
"github.com/coder/coder/codersdk"
1514
"github.com/coder/coder/database"
1615
"github.com/coder/coder/provisioner/echo"
@@ -180,15 +179,7 @@ func TestProvisionerJobResourcesByID(t *testing.T) {
180179
user := coderdtest.CreateInitialUser(t, client)
181180
_ = coderdtest.NewProvisionerDaemon(t, client)
182181
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{
183-
Parse: []*proto.Parse_Response{{
184-
Type: &proto.Parse_Response_Complete{
185-
Complete: &proto.Parse_Complete{
186-
ParameterSchemas: []*proto.ParameterSchema{{
187-
Name: parameter.CoderWorkspaceTransition,
188-
}},
189-
},
190-
},
191-
}},
182+
Parse: echo.ParseComplete,
192183
Provision: []*proto.Provision_Response{{
193184
Type: &proto.Provision_Response_Complete{
194185
Complete: &proto.Provision_Complete{

codersdk/files.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package codersdk
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"net/http"
8+
"net/url"
79

810
"github.com/coder/coder/coderd"
911
)
@@ -26,3 +28,8 @@ func (c *Client) UploadFile(ctx context.Context, contentType string, content []b
2628
var resp coderd.UploadFileResponse
2729
return resp, json.NewDecoder(res.Body).Decode(&resp)
2830
}
31+
32+
// DownloadURL returns
33+
func (c *Client) DownloadURL(asset string) (*url.URL, error) {
34+
return c.URL.Parse(fmt.Sprintf("/api/v2/downloads/%s", asset))
35+
}

provisioner/terraform/provider/provider.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1212
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1313

14+
"github.com/coder/coder/database"
1415
"github.com/coder/coder/provisionersdk"
1516
)
1617

@@ -54,6 +55,22 @@ func New() *schema.Provider {
5455
}, nil
5556
},
5657
DataSourcesMap: map[string]*schema.Resource{
58+
"coder_workspace": {
59+
Description: "TODO",
60+
ReadContext: func(c context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
61+
rd.SetId(uuid.NewString())
62+
return nil
63+
},
64+
Schema: map[string]*schema.Schema{
65+
"transition": {
66+
Type: schema.TypeString,
67+
Optional: true,
68+
Description: "TODO",
69+
DefaultFunc: schema.EnvDefaultFunc("CODER_WORKSPACE_TRANSITION", ""),
70+
ValidateFunc: validation.StringInSlice([]string{string(database.WorkspaceTransitionStart), string(database.WorkspaceTransitionStop)}, false),
71+
},
72+
},
73+
},
5774
"coder_agent_script": {
5875
Description: "TODO",
5976
ReadContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {

provisioner/terraform/provider/provider_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,35 @@ func TestProvider(t *testing.T) {
1919
require.NoError(t, err)
2020
}
2121

22+
func TestWorkspace(t *testing.T) {
23+
t.Parallel()
24+
resource.Test(t, resource.TestCase{
25+
Providers: map[string]*schema.Provider{
26+
"coder": provider.New(),
27+
},
28+
IsUnitTest: true,
29+
Steps: []resource.TestStep{{
30+
Config: `
31+
provider "coder" {
32+
url = "https://example.com"
33+
}
34+
data "coder_workspace" "me" {
35+
transition = "start"
36+
}`,
37+
Check: func(state *terraform.State) error {
38+
require.Len(t, state.Modules, 1)
39+
require.Len(t, state.Modules[0].Resources, 1)
40+
resource := state.Modules[0].Resources["data.coder_workspace.me"]
41+
require.NotNil(t, resource)
42+
value := resource.Primary.Attributes["transition"]
43+
require.NotNil(t, value)
44+
t.Log(value)
45+
return nil
46+
},
47+
}},
48+
})
49+
}
50+
2251
func TestAgentScript(t *testing.T) {
2352
t.Parallel()
2453
resource.Test(t, resource.TestCase{

provisioner/terraform/provision.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ func (t *terraform) Provision(request *proto.Provision_Request, stream proto.DRP
7474
}
7575

7676
func (t *terraform) runTerraformPlan(ctx context.Context, terraform *tfexec.Terraform, request *proto.Provision_Request, stream proto.DRPCProvisioner_ProvisionStream) error {
77-
env := map[string]string{}
77+
env := map[string]string{
78+
"CODER_URL": request.Metadata.CoderUrl,
79+
"CODER_WORKSPACE_TRANSITION": strings.ToLower(request.Metadata.WorkspaceTransition.String()),
80+
}
7881
planfilePath := filepath.Join(request.Directory, "terraform.tfplan")
7982
options := []tfexec.PlanOption{tfexec.JSON(true), tfexec.Out(planfilePath)}
8083
for _, param := range request.ParameterValues {
@@ -250,7 +253,10 @@ func (t *terraform) runTerraformPlan(ctx context.Context, terraform *tfexec.Terr
250253
}
251254

252255
func (t *terraform) runTerraformApply(ctx context.Context, terraform *tfexec.Terraform, request *proto.Provision_Request, stream proto.DRPCProvisioner_ProvisionStream, statefilePath string) error {
253-
env := map[string]string{}
256+
env := map[string]string{
257+
"CODER_URL": request.Metadata.CoderUrl,
258+
"CODER_WORKSPACE_TRANSITION": strings.ToLower(request.Metadata.WorkspaceTransition.String()),
259+
}
254260
options := []tfexec.ApplyOption{tfexec.JSON(true)}
255261
for _, param := range request.ParameterValues {
256262
switch param.DestinationScheme {

provisioner/terraform/provision_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ terraform {
5151
}
5252
5353
provider "coder" {
54-
url = "https://example.com"
5554
}
5655
`
5756
t.Log(provider)
@@ -174,6 +173,11 @@ provider "coder" {
174173
]
175174
}`,
176175
},
176+
Request: &proto.Provision_Request{
177+
Metadata: &proto.Provision_Metadata{
178+
CoderUrl: "https://example.com",
179+
},
180+
},
177181
Response: &proto.Provision_Response{
178182
Type: &proto.Provision_Response_Complete{
179183
Complete: &proto.Provision_Complete{
@@ -204,6 +208,11 @@ provider "coder" {
204208
}
205209
resource "null_resource" "A" {}`,
206210
},
211+
Request: &proto.Provision_Request{
212+
Metadata: &proto.Provision_Metadata{
213+
CoderUrl: "https://example.com",
214+
},
215+
},
207216
Response: &proto.Provision_Response{
208217
Type: &proto.Provision_Response_Complete{
209218
Complete: &proto.Provision_Complete{
@@ -235,6 +244,9 @@ provider "coder" {
235244
},
236245
Request: &proto.Provision_Request{
237246
DryRun: true,
247+
Metadata: &proto.Provision_Metadata{
248+
CoderUrl: "https://example.com",
249+
},
238250
},
239251
Response: &proto.Provision_Response{
240252
Type: &proto.Provision_Response_Complete{
@@ -266,6 +278,9 @@ provider "coder" {
266278
},
267279
Request: &proto.Provision_Request{
268280
DryRun: true,
281+
Metadata: &proto.Provision_Metadata{
282+
CoderUrl: "https://example.com",
283+
},
269284
},
270285
Response: &proto.Provision_Response{
271286
Type: &proto.Provision_Response_Complete{
@@ -302,6 +317,10 @@ provider "coder" {
302317
request.ParameterValues = testCase.Request.ParameterValues
303318
request.State = testCase.Request.State
304319
request.DryRun = testCase.Request.DryRun
320+
request.Metadata = testCase.Request.Metadata
321+
}
322+
if request.Metadata == nil {
323+
request.Metadata = &proto.Provision_Metadata{}
305324
}
306325
response, err := api.Provision(ctx, request)
307326
require.NoError(t, err)

0 commit comments

Comments
 (0)