Skip to content

Commit cf1221b

Browse files
committed
Add feature for external daemons
1 parent 5255b13 commit cf1221b

File tree

7 files changed

+148
-77
lines changed

7 files changed

+148
-77
lines changed

coderd/coderdtest/coderdtest.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import (
6666
"github.com/coder/coder/cryptorand"
6767
"github.com/coder/coder/provisioner/echo"
6868
"github.com/coder/coder/provisionerd"
69+
provisionerdproto "github.com/coder/coder/provisionerd/proto"
6970
"github.com/coder/coder/provisionersdk"
7071
"github.com/coder/coder/provisionersdk/proto"
7172
"github.com/coder/coder/tailnet"
@@ -337,6 +338,41 @@ func NewProvisionerDaemon(t *testing.T, coderAPI *coderd.API) io.Closer {
337338
return closer
338339
}
339340

341+
func NewExternalProvisionerDaemon(t *testing.T, client *codersdk.Client, org uuid.UUID, tags map[string]string) io.Closer {
342+
echoClient, echoServer := provisionersdk.TransportPipe()
343+
ctx, cancelFunc := context.WithCancel(context.Background())
344+
t.Cleanup(func() {
345+
_ = echoClient.Close()
346+
_ = echoServer.Close()
347+
cancelFunc()
348+
})
349+
fs := afero.NewMemMapFs()
350+
go func() {
351+
err := echo.Serve(ctx, fs, &provisionersdk.ServeOptions{
352+
Listener: echoServer,
353+
})
354+
assert.NoError(t, err)
355+
}()
356+
357+
closer := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
358+
return client.ServeProvisionerDaemon(ctx, org, []codersdk.ProvisionerType{codersdk.ProvisionerTypeEcho}, tags)
359+
}, &provisionerd.Options{
360+
Filesystem: fs,
361+
Logger: slogtest.Make(t, nil).Named("provisionerd").Leveled(slog.LevelDebug),
362+
PollInterval: 50 * time.Millisecond,
363+
UpdateInterval: 250 * time.Millisecond,
364+
ForceCancelInterval: time.Second,
365+
Provisioners: provisionerd.Provisioners{
366+
string(database.ProvisionerTypeEcho): proto.NewDRPCProvisionerClient(provisionersdk.Conn(echoClient)),
367+
},
368+
WorkDirectory: t.TempDir(),
369+
})
370+
t.Cleanup(func() {
371+
_ = closer.Close()
372+
})
373+
return closer
374+
}
375+
340376
var FirstUserParams = codersdk.CreateFirstUserRequest{
341377
Email: "testuser@coder.com",
342378
Username: "testuser",

codersdk/features.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ const (
1515
)
1616

1717
const (
18-
FeatureUserLimit = "user_limit"
19-
FeatureAuditLog = "audit_log"
20-
FeatureBrowserOnly = "browser_only"
21-
FeatureSCIM = "scim"
22-
FeatureWorkspaceQuota = "workspace_quota"
23-
FeatureTemplateRBAC = "template_rbac"
24-
FeatureHighAvailability = "high_availability"
25-
FeatureMultipleGitAuth = "multiple_git_auth"
18+
FeatureUserLimit = "user_limit"
19+
FeatureAuditLog = "audit_log"
20+
FeatureBrowserOnly = "browser_only"
21+
FeatureSCIM = "scim"
22+
FeatureWorkspaceQuota = "workspace_quota"
23+
FeatureTemplateRBAC = "template_rbac"
24+
FeatureHighAvailability = "high_availability"
25+
FeatureMultipleGitAuth = "multiple_git_auth"
26+
FeatureExternalProvisionerDaemons = "external_provisioner_daemons"
2627
)
2728

2829
var FeatureNames = []string{
@@ -34,6 +35,7 @@ var FeatureNames = []string{
3435
FeatureTemplateRBAC,
3536
FeatureHighAvailability,
3637
FeatureMultipleGitAuth,
38+
FeatureExternalProvisionerDaemons,
3739
}
3840

3941
type Feature struct {

enterprise/coderd/coderdenttest/coderdenttest.go

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,21 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c
100100
}
101101

102102
type LicenseOptions struct {
103-
AccountType string
104-
AccountID string
105-
Trial bool
106-
AllFeatures bool
107-
GraceAt time.Time
108-
ExpiresAt time.Time
109-
UserLimit int64
110-
AuditLog bool
111-
BrowserOnly bool
112-
SCIM bool
113-
WorkspaceQuota bool
114-
TemplateRBAC bool
115-
HighAvailability bool
116-
MultipleGitAuth bool
103+
AccountType string
104+
AccountID string
105+
Trial bool
106+
AllFeatures bool
107+
GraceAt time.Time
108+
ExpiresAt time.Time
109+
UserLimit int64
110+
AuditLog bool
111+
BrowserOnly bool
112+
SCIM bool
113+
WorkspaceQuota bool
114+
TemplateRBAC bool
115+
HighAvailability bool
116+
MultipleGitAuth bool
117+
ExternalProvisionerDaemons bool
117118
}
118119

119120
// AddLicense generates a new license with the options provided and inserts it.
@@ -164,6 +165,11 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
164165
multipleGitAuth = 1
165166
}
166167

168+
externalProvisionerDaemons := int64(0)
169+
if options.ExternalProvisionerDaemons {
170+
externalProvisionerDaemons = 1
171+
}
172+
167173
c := &license.Claims{
168174
RegisteredClaims: jwt.RegisteredClaims{
169175
Issuer: "test@testing.test",
@@ -178,14 +184,15 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
178184
Version: license.CurrentVersion,
179185
AllFeatures: options.AllFeatures,
180186
Features: license.Features{
181-
UserLimit: options.UserLimit,
182-
AuditLog: auditLog,
183-
BrowserOnly: browserOnly,
184-
SCIM: scim,
185-
WorkspaceQuota: workspaceQuota,
186-
HighAvailability: highAvailability,
187-
TemplateRBAC: rbacEnabled,
188-
MultipleGitAuth: multipleGitAuth,
187+
UserLimit: options.UserLimit,
188+
AuditLog: auditLog,
189+
BrowserOnly: browserOnly,
190+
SCIM: scim,
191+
WorkspaceQuota: workspaceQuota,
192+
HighAvailability: highAvailability,
193+
TemplateRBAC: rbacEnabled,
194+
MultipleGitAuth: multipleGitAuth,
195+
ExternalProvisionerDaemons: externalProvisionerDaemons,
189196
},
190197
}
191198
tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, c)

enterprise/coderd/license/license.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ func Entitlements(
123123
Enabled: true,
124124
}
125125
}
126+
if claims.Features.ExternalProvisionerDaemons > 0 {
127+
entitlements.Features[codersdk.FeatureExternalProvisionerDaemons] = codersdk.Feature{
128+
Entitlement: entitlement,
129+
Enabled: true,
130+
}
131+
}
126132
if claims.AllFeatures {
127133
allFeatures = true
128134
}
@@ -244,14 +250,15 @@ var (
244250
)
245251

246252
type Features struct {
247-
UserLimit int64 `json:"user_limit"`
248-
AuditLog int64 `json:"audit_log"`
249-
BrowserOnly int64 `json:"browser_only"`
250-
SCIM int64 `json:"scim"`
251-
WorkspaceQuota int64 `json:"workspace_quota"`
252-
TemplateRBAC int64 `json:"template_rbac"`
253-
HighAvailability int64 `json:"high_availability"`
254-
MultipleGitAuth int64 `json:"multiple_git_auth"`
253+
UserLimit int64 `json:"user_limit"`
254+
AuditLog int64 `json:"audit_log"`
255+
BrowserOnly int64 `json:"browser_only"`
256+
SCIM int64 `json:"scim"`
257+
WorkspaceQuota int64 `json:"workspace_quota"`
258+
TemplateRBAC int64 `json:"template_rbac"`
259+
HighAvailability int64 `json:"high_availability"`
260+
MultipleGitAuth int64 `json:"multiple_git_auth"`
261+
ExternalProvisionerDaemons int64 `json:"external_provisioner_daemons"`
255262
}
256263

257264
type Claims struct {

enterprise/coderd/license/license_test.go

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ import (
2020
func TestEntitlements(t *testing.T) {
2121
t.Parallel()
2222
all := map[string]bool{
23-
codersdk.FeatureAuditLog: true,
24-
codersdk.FeatureBrowserOnly: true,
25-
codersdk.FeatureSCIM: true,
26-
codersdk.FeatureWorkspaceQuota: true,
27-
codersdk.FeatureHighAvailability: true,
28-
codersdk.FeatureTemplateRBAC: true,
29-
codersdk.FeatureMultipleGitAuth: true,
23+
codersdk.FeatureAuditLog: true,
24+
codersdk.FeatureBrowserOnly: true,
25+
codersdk.FeatureSCIM: true,
26+
codersdk.FeatureWorkspaceQuota: true,
27+
codersdk.FeatureHighAvailability: true,
28+
codersdk.FeatureTemplateRBAC: true,
29+
codersdk.FeatureMultipleGitAuth: true,
30+
codersdk.FeatureExternalProvisionerDaemons: true,
3031
}
3132

3233
t.Run("Defaults", func(t *testing.T) {
@@ -62,14 +63,15 @@ func TestEntitlements(t *testing.T) {
6263
db := databasefake.New()
6364
db.InsertLicense(context.Background(), database.InsertLicenseParams{
6465
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
65-
UserLimit: 100,
66-
AuditLog: true,
67-
BrowserOnly: true,
68-
SCIM: true,
69-
WorkspaceQuota: true,
70-
HighAvailability: true,
71-
TemplateRBAC: true,
72-
MultipleGitAuth: true,
66+
UserLimit: 100,
67+
AuditLog: true,
68+
BrowserOnly: true,
69+
SCIM: true,
70+
WorkspaceQuota: true,
71+
HighAvailability: true,
72+
TemplateRBAC: true,
73+
MultipleGitAuth: true,
74+
ExternalProvisionerDaemons: true,
7375
}),
7476
Exp: time.Now().Add(time.Hour),
7577
})
@@ -86,15 +88,16 @@ func TestEntitlements(t *testing.T) {
8688
db := databasefake.New()
8789
db.InsertLicense(context.Background(), database.InsertLicenseParams{
8890
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
89-
UserLimit: 100,
90-
AuditLog: true,
91-
BrowserOnly: true,
92-
SCIM: true,
93-
WorkspaceQuota: true,
94-
HighAvailability: true,
95-
TemplateRBAC: true,
96-
GraceAt: time.Now().Add(-time.Hour),
97-
ExpiresAt: time.Now().Add(time.Hour),
91+
UserLimit: 100,
92+
AuditLog: true,
93+
BrowserOnly: true,
94+
SCIM: true,
95+
WorkspaceQuota: true,
96+
HighAvailability: true,
97+
TemplateRBAC: true,
98+
ExternalProvisionerDaemons: true,
99+
GraceAt: time.Now().Add(-time.Hour),
100+
ExpiresAt: time.Now().Add(time.Hour),
98101
}),
99102
Exp: time.Now().Add(time.Hour),
100103
})

enterprise/coderd/provisionerdaemons.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ import (
2929
"github.com/coder/coder/provisionerd/proto"
3030
)
3131

32+
func (api *API) provisionerDaemonsEnabledMW(next http.Handler) http.Handler {
33+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
34+
api.entitlementsMu.RLock()
35+
epd := api.entitlements.Features[codersdk.FeatureExternalProvisionerDaemons].Enabled
36+
api.entitlementsMu.RUnlock()
37+
38+
if !epd {
39+
httpapi.Write(r.Context(), rw, http.Status)
40+
return
41+
}
42+
43+
next.ServeHTTP(rw, r)
44+
})
45+
}
46+
3247
func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
3348
ctx := r.Context()
3449
org := httpmw.OrganizationParam(r)
@@ -64,6 +79,7 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
6479

6580
// Serves the provisioner daemon protobuf API over a WebSocket.
6681
func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) {
82+
6783
tags := map[string]string{}
6884
if r.URL.Query().Has("tag") {
6985
for _, tag := range r.URL.Query()["tag"] {

enterprise/coderd/provisionerdaemons_test.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"net/http"
66
"testing"
7-
"time"
87

98
"github.com/google/uuid"
109
"github.com/stretchr/testify/require"
@@ -14,8 +13,6 @@ import (
1413
"github.com/coder/coder/codersdk"
1514
"github.com/coder/coder/enterprise/coderd/coderdenttest"
1615
"github.com/coder/coder/provisioner/echo"
17-
"github.com/coder/coder/provisionerd"
18-
provisionerdproto "github.com/coder/coder/provisionerd/proto"
1916
"github.com/coder/coder/provisionersdk/proto"
2017
)
2118

@@ -52,14 +49,10 @@ func TestProvisionerDaemonServe(t *testing.T) {
5249
t.Parallel()
5350
client := coderdenttest.New(t, nil)
5451
user := coderdtest.CreateFirstUser(t, client)
55-
srv := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
56-
return client.ServeProvisionerDaemon(context.Background(), user.OrganizationID, []codersdk.ProvisionerType{
57-
codersdk.ProvisionerTypeEcho,
58-
}, map[string]string{
59-
provisionerdserver.TagScope: provisionerdserver.ScopeUser,
60-
})
61-
}, nil)
62-
defer srv.Close()
52+
closer := coderdtest.NewExternalProvisionerDaemon(t, client, user.OrganizationID, map[string]string{
53+
provisionerdserver.TagScope: provisionerdserver.ScopeUser,
54+
})
55+
defer closer.Close()
6356

6457
authToken := uuid.NewString()
6558
data, err := echo.Tar(&echo.Responses{
@@ -100,7 +93,7 @@ func TestProvisionerDaemonServe(t *testing.T) {
10093
file, err := client.Upload(context.Background(), codersdk.ContentTypeTar, data)
10194
require.NoError(t, err)
10295

103-
_, err = client.CreateTemplateVersion(context.Background(), user.OrganizationID, codersdk.CreateTemplateVersionRequest{
96+
version, err := client.CreateTemplateVersion(context.Background(), user.OrganizationID, codersdk.CreateTemplateVersionRequest{
10497
Name: "example",
10598
StorageMethod: codersdk.ProvisionerStorageMethodFile,
10699
FileID: file.ID,
@@ -110,9 +103,16 @@ func TestProvisionerDaemonServe(t *testing.T) {
110103
},
111104
})
112105
require.NoError(t, err)
113-
// coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
114-
115-
time.Sleep(time.Second)
106+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
107+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
108+
another := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
109+
_ = closer.Close()
110+
closer = coderdtest.NewExternalProvisionerDaemon(t, another, user.OrganizationID, map[string]string{
111+
provisionerdserver.TagScope: provisionerdserver.ScopeUser,
112+
})
113+
defer closer.Close()
114+
workspace := coderdtest.CreateWorkspace(t, another, user.OrganizationID, template.ID)
115+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
116116
})
117117
}
118118

0 commit comments

Comments
 (0)