Skip to content

Commit 6f20a64

Browse files
authored
chore: add multi-org flag to develop.sh (coder#13923)
1 parent f975701 commit 6f20a64

File tree

6 files changed

+268
-32
lines changed

6 files changed

+268
-32
lines changed

codersdk/organizations.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,24 @@ func (c *Client) ProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, e
290290
return daemons, json.NewDecoder(res.Body).Decode(&daemons)
291291
}
292292

293+
func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerDaemon, error) {
294+
res, err := c.Request(ctx, http.MethodGet,
295+
fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons", organizationID.String()),
296+
nil,
297+
)
298+
if err != nil {
299+
return nil, xerrors.Errorf("execute request: %w", err)
300+
}
301+
defer res.Body.Close()
302+
303+
if res.StatusCode != http.StatusOK {
304+
return nil, ReadBodyAsError(res)
305+
}
306+
307+
var daemons []ProvisionerDaemon
308+
return daemons, json.NewDecoder(res.Body).Decode(&daemons)
309+
}
310+
293311
// CreateTemplateVersion processes source-code and optionally associates the version with a template.
294312
// Executing without a template is useful for validating source-code.
295313
func (c *Client) CreateTemplateVersion(ctx context.Context, organizationID uuid.UUID, req CreateTemplateVersionRequest) (TemplateVersion, error) {

docs/cli/provisionerd_start.md

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/cli/provisionerdaemons.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package cli
44

55
import (
66
"context"
7+
"errors"
78
"fmt"
9+
"net/http"
810
"os"
911
"regexp"
1012
"time"
@@ -76,6 +78,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
7678
prometheusEnable bool
7779
prometheusAddress string
7880
)
81+
orgContext := agpl.NewOrganizationContext()
7982
client := new(codersdk.Client)
8083
cmd := &serpent.Command{
8184
Use: "start",
@@ -95,6 +98,35 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
9598
interruptCtx, interruptCancel := inv.SignalNotifyContext(ctx, agpl.InterruptSignals...)
9699
defer interruptCancel()
97100

101+
// This can fail to get the current organization
102+
// if the client is not authenticated as a user,
103+
// like when only PSK is provided.
104+
// This will be cleaner once PSK is replaced
105+
// with org scoped authentication tokens.
106+
org, err := orgContext.Selected(inv, client)
107+
if err != nil {
108+
var cErr *codersdk.Error
109+
if !errors.As(err, &cErr) || cErr.StatusCode() != http.StatusUnauthorized {
110+
return xerrors.Errorf("current organization: %w", err)
111+
}
112+
113+
if preSharedKey == "" {
114+
return xerrors.New("must provide a pre-shared key when not authenticated as a user")
115+
}
116+
117+
org = codersdk.Organization{ID: uuid.Nil}
118+
if orgContext.FlagSelect != "" {
119+
// If we are using PSK, we can't fetch the organization
120+
// to validate org name so we need the user to provide
121+
// a valid organization ID.
122+
orgID, err := uuid.Parse(orgContext.FlagSelect)
123+
if err != nil {
124+
return xerrors.New("must provide an org ID when not authenticated as a user and organization is specified")
125+
}
126+
org = codersdk.Organization{ID: orgID}
127+
}
128+
}
129+
98130
tags, err := agpl.ParseProvisionerTags(rawTags)
99131
if err != nil {
100132
return err
@@ -198,16 +230,16 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
198230
connector := provisionerd.LocalProvisioners{
199231
string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(terraformClient),
200232
}
201-
id := uuid.New()
202233
srv := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
203234
return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
204-
ID: id,
235+
ID: uuid.New(),
205236
Name: name,
206237
Provisioners: []codersdk.ProvisionerType{
207238
codersdk.ProvisionerTypeTerraform,
208239
},
209240
Tags: tags,
210241
PreSharedKey: preSharedKey,
242+
Organization: org.ID,
211243
})
212244
}, &provisionerd.Options{
213245
Logger: logger,
@@ -348,6 +380,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
348380
Default: "127.0.0.1:2112",
349381
},
350382
}
383+
orgContext.AttachOptions(cmd)
351384

352385
return cmd
353386
}

enterprise/cli/provisionerdaemons_test.go

Lines changed: 162 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,134 @@ import (
2727
func TestProvisionerDaemon_PSK(t *testing.T) {
2828
t.Parallel()
2929

30-
client, _ := coderdenttest.New(t, &coderdenttest.Options{
31-
ProvisionerDaemonPSK: "provisionersftw",
32-
LicenseOptions: &coderdenttest.LicenseOptions{
33-
Features: license.Features{
34-
codersdk.FeatureExternalProvisionerDaemons: 1,
30+
t.Run("OK", func(t *testing.T) {
31+
t.Parallel()
32+
33+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
34+
ProvisionerDaemonPSK: "provisionersftw",
35+
LicenseOptions: &coderdenttest.LicenseOptions{
36+
Features: license.Features{
37+
codersdk.FeatureExternalProvisionerDaemons: 1,
38+
},
3539
},
36-
},
40+
})
41+
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name=matt-daemon")
42+
err := conf.URL().Write(client.URL.String())
43+
require.NoError(t, err)
44+
pty := ptytest.New(t).Attach(inv)
45+
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
46+
defer cancel()
47+
clitest.Start(t, inv)
48+
pty.ExpectNoMatchBefore(ctx, "check entitlement", "starting provisioner daemon")
49+
pty.ExpectMatchContext(ctx, "matt-daemon")
50+
51+
var daemons []codersdk.ProvisionerDaemon
52+
require.Eventually(t, func() bool {
53+
daemons, err = client.ProvisionerDaemons(ctx)
54+
if err != nil {
55+
return false
56+
}
57+
return len(daemons) == 1
58+
}, testutil.WaitShort, testutil.IntervalSlow)
59+
require.Equal(t, "matt-daemon", daemons[0].Name)
60+
require.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope])
61+
require.Equal(t, buildinfo.Version(), daemons[0].Version)
62+
require.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
63+
})
64+
65+
t.Run("AnotherOrg", func(t *testing.T) {
66+
t.Parallel()
67+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
68+
ProvisionerDaemonPSK: "provisionersftw",
69+
LicenseOptions: &coderdenttest.LicenseOptions{
70+
Features: license.Features{
71+
codersdk.FeatureExternalProvisionerDaemons: 1,
72+
},
73+
},
74+
})
75+
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
76+
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name", "org-daemon", "--org", anotherOrg.ID.String())
77+
err := conf.URL().Write(client.URL.String())
78+
require.NoError(t, err)
79+
pty := ptytest.New(t).Attach(inv)
80+
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
81+
defer cancel()
82+
clitest.Start(t, inv)
83+
pty.ExpectMatchContext(ctx, "starting provisioner daemon")
84+
85+
var daemons []codersdk.ProvisionerDaemon
86+
require.Eventually(t, func() bool {
87+
daemons, err = client.OrganizationProvisionerDaemons(ctx, anotherOrg.ID)
88+
if err != nil {
89+
return false
90+
}
91+
return len(daemons) == 1
92+
}, testutil.WaitShort, testutil.IntervalSlow)
93+
assert.Equal(t, "org-daemon", daemons[0].Name)
94+
assert.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope])
95+
assert.Equal(t, buildinfo.Version(), daemons[0].Version)
96+
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
97+
})
98+
99+
t.Run("AnotherOrgByNameWithUser", func(t *testing.T) {
100+
t.Parallel()
101+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
102+
ProvisionerDaemonPSK: "provisionersftw",
103+
LicenseOptions: &coderdenttest.LicenseOptions{
104+
Features: license.Features{
105+
codersdk.FeatureExternalProvisionerDaemons: 1,
106+
},
107+
},
108+
})
109+
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
110+
anotherClient, _ := coderdtest.CreateAnotherUser(t, client, anotherOrg.ID, rbac.RoleTemplateAdmin())
111+
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name", "org-daemon", "--org", anotherOrg.Name)
112+
clitest.SetupConfig(t, anotherClient, conf)
113+
pty := ptytest.New(t).Attach(inv)
114+
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
115+
defer cancel()
116+
clitest.Start(t, inv)
117+
pty.ExpectMatchContext(ctx, "starting provisioner daemon")
118+
})
119+
120+
t.Run("AnotherOrgByNameNoUser", func(t *testing.T) {
121+
t.Parallel()
122+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
123+
ProvisionerDaemonPSK: "provisionersftw",
124+
LicenseOptions: &coderdenttest.LicenseOptions{
125+
Features: license.Features{
126+
codersdk.FeatureExternalProvisionerDaemons: 1,
127+
},
128+
},
129+
})
130+
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
131+
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name", "org-daemon", "--org", anotherOrg.Name)
132+
err := conf.URL().Write(client.URL.String())
133+
require.NoError(t, err)
134+
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
135+
defer cancel()
136+
err = inv.WithContext(ctx).Run()
137+
require.ErrorContains(t, err, "must provide an org ID when not authenticated as a user and organization is specified")
138+
})
139+
140+
t.Run("NoUserNoPSK", func(t *testing.T) {
141+
t.Parallel()
142+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
143+
ProvisionerDaemonPSK: "provisionersftw",
144+
LicenseOptions: &coderdenttest.LicenseOptions{
145+
Features: license.Features{
146+
codersdk.FeatureExternalProvisionerDaemons: 1,
147+
},
148+
},
149+
})
150+
inv, conf := newCLI(t, "provisionerd", "start", "--name", "org-daemon")
151+
err := conf.URL().Write(client.URL.String())
152+
require.NoError(t, err)
153+
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
154+
defer cancel()
155+
err = inv.WithContext(ctx).Run()
156+
require.ErrorContains(t, err, "must provide a pre-shared key when not authenticated as a user")
37157
})
38-
inv, conf := newCLI(t, "provisionerd", "start", "--psk=provisionersftw", "--name=matt-daemon")
39-
err := conf.URL().Write(client.URL.String())
40-
require.NoError(t, err)
41-
pty := ptytest.New(t).Attach(inv)
42-
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
43-
defer cancel()
44-
clitest.Start(t, inv)
45-
pty.ExpectNoMatchBefore(ctx, "check entitlement", "starting provisioner daemon")
46-
pty.ExpectMatchContext(ctx, "matt-daemon")
47-
48-
var daemons []codersdk.ProvisionerDaemon
49-
require.Eventually(t, func() bool {
50-
daemons, err = client.ProvisionerDaemons(ctx)
51-
if err != nil {
52-
return false
53-
}
54-
return len(daemons) == 1
55-
}, testutil.WaitShort, testutil.IntervalSlow)
56-
require.Equal(t, "matt-daemon", daemons[0].Name)
57-
require.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope])
58-
require.Equal(t, buildinfo.Version(), daemons[0].Version)
59-
require.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
60158
}
61159

62160
func TestProvisionerDaemon_SessionToken(t *testing.T) {
@@ -166,6 +264,42 @@ func TestProvisionerDaemon_SessionToken(t *testing.T) {
166264
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
167265
})
168266

267+
t.Run("ScopeUserAnotherOrg", func(t *testing.T) {
268+
t.Parallel()
269+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
270+
ProvisionerDaemonPSK: "provisionersftw",
271+
LicenseOptions: &coderdenttest.LicenseOptions{
272+
Features: license.Features{
273+
codersdk.FeatureExternalProvisionerDaemons: 1,
274+
},
275+
},
276+
})
277+
anotherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
278+
anotherClient, anotherUser := coderdtest.CreateAnotherUser(t, client, anotherOrg.ID, rbac.RoleTemplateAdmin())
279+
inv, conf := newCLI(t, "provisionerd", "start", "--tag", "scope=user", "--name", "org-daemon", "--org", anotherOrg.ID.String())
280+
clitest.SetupConfig(t, anotherClient, conf)
281+
pty := ptytest.New(t).Attach(inv)
282+
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
283+
defer cancel()
284+
clitest.Start(t, inv)
285+
pty.ExpectMatchContext(ctx, "starting provisioner daemon")
286+
287+
var daemons []codersdk.ProvisionerDaemon
288+
var err error
289+
require.Eventually(t, func() bool {
290+
daemons, err = client.OrganizationProvisionerDaemons(ctx, anotherOrg.ID)
291+
if err != nil {
292+
return false
293+
}
294+
return len(daemons) == 1
295+
}, testutil.WaitShort, testutil.IntervalSlow)
296+
assert.Equal(t, "org-daemon", daemons[0].Name)
297+
assert.Equal(t, provisionersdk.ScopeUser, daemons[0].Tags[provisionersdk.TagScope])
298+
assert.Equal(t, anotherUser.ID.String(), daemons[0].Tags[provisionersdk.TagOwner])
299+
assert.Equal(t, buildinfo.Version(), daemons[0].Version)
300+
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
301+
})
302+
169303
t.Run("PrometheusEnabled", func(t *testing.T) {
170304
t.Parallel()
171305

enterprise/cli/testdata/coder_provisionerd_start_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ USAGE:
66
Run a provisioner daemon
77

88
OPTIONS:
9+
-O, --org string, $CODER_ORGANIZATION
10+
Select which organization (uuid or name) to use.
11+
912
-c, --cache-dir string, $CODER_CACHE_DIRECTORY (default: [cache dir])
1013
Directory to store cached data.
1114

0 commit comments

Comments
 (0)