Skip to content

Commit 0a07c7e

Browse files
authored
feat: get org scoped provisioners (coder#13953)
1 parent 695afb8 commit 0a07c7e

File tree

11 files changed

+158
-27
lines changed

11 files changed

+158
-27
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,10 @@ func (q *querier) GetProvisionerDaemons(ctx context.Context) ([]database.Provisi
16271627
return fetchWithPostFilter(q.auth, policy.ActionRead, fetch)(ctx, nil)
16281628
}
16291629

1630+
func (q *querier) GetProvisionerDaemonsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.ProvisionerDaemon, error) {
1631+
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetProvisionerDaemonsByOrganization)(ctx, organizationID)
1632+
}
1633+
16301634
func (q *querier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (database.ProvisionerJob, error) {
16311635
job, err := q.db.GetProvisionerJobByID(ctx, id)
16321636
if err != nil {
@@ -3727,7 +3731,7 @@ func (q *querier) UpsertOAuthSigningKey(ctx context.Context, value string) error
37273731
}
37283732

37293733
func (q *querier) UpsertProvisionerDaemon(ctx context.Context, arg database.UpsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
3730-
res := rbac.ResourceProvisionerDaemon.All()
3734+
res := rbac.ResourceProvisionerDaemon.InOrg(arg.OrganizationID)
37313735
if arg.Tags[provisionersdk.TagScope] == provisionersdk.ScopeUser {
37323736
res.Owner = arg.Tags[provisionersdk.TagOwner]
37333737
}

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,19 @@ func (s *MethodTestSuite) TestExtraMethods() {
18631863
s.NoError(err, "insert provisioner daemon")
18641864
check.Args().Asserts(d, policy.ActionRead)
18651865
}))
1866+
s.Run("GetProvisionerDaemonsByOrganization", s.Subtest(func(db database.Store, check *expects) {
1867+
org := dbgen.Organization(s.T(), db, database.Organization{})
1868+
d, err := db.UpsertProvisionerDaemon(context.Background(), database.UpsertProvisionerDaemonParams{
1869+
OrganizationID: org.ID,
1870+
Tags: database.StringMap(map[string]string{
1871+
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
1872+
}),
1873+
})
1874+
s.NoError(err, "insert provisioner daemon")
1875+
ds, err := db.GetProvisionerDaemonsByOrganization(context.Background(), org.ID)
1876+
s.NoError(err, "get provisioner daemon by org")
1877+
check.Args(org.ID).Asserts(d, policy.ActionRead).Returns(ds)
1878+
}))
18661879
s.Run("DeleteOldProvisionerDaemons", s.Subtest(func(db database.Store, check *expects) {
18671880
_, err := db.UpsertProvisionerDaemon(context.Background(), database.UpsertProvisionerDaemonParams{
18681881
Tags: database.StringMap(map[string]string{
@@ -2328,13 +2341,16 @@ func (s *MethodTestSuite) TestSystemFunctions() {
23282341
}).Asserts( /*rbac.ResourceSystem, policy.ActionCreate*/ )
23292342
}))
23302343
s.Run("UpsertProvisionerDaemon", s.Subtest(func(db database.Store, check *expects) {
2331-
pd := rbac.ResourceProvisionerDaemon.All()
2344+
org := dbgen.Organization(s.T(), db, database.Organization{})
2345+
pd := rbac.ResourceProvisionerDaemon.InOrg(org.ID)
23322346
check.Args(database.UpsertProvisionerDaemonParams{
2347+
OrganizationID: org.ID,
23332348
Tags: database.StringMap(map[string]string{
23342349
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
23352350
}),
23362351
}).Asserts(pd, policy.ActionCreate)
23372352
check.Args(database.UpsertProvisionerDaemonParams{
2353+
OrganizationID: org.ID,
23382354
Tags: database.StringMap(map[string]string{
23392355
provisionersdk.TagScope: provisionersdk.ScopeUser,
23402356
provisionersdk.TagOwner: "11111111-1111-1111-1111-111111111111",

coderd/database/dbmem/dbmem.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3140,6 +3140,21 @@ func (q *FakeQuerier) GetProvisionerDaemons(_ context.Context) ([]database.Provi
31403140
return out, nil
31413141
}
31423142

3143+
func (q *FakeQuerier) GetProvisionerDaemonsByOrganization(_ context.Context, organizationID uuid.UUID) ([]database.ProvisionerDaemon, error) {
3144+
q.mutex.RLock()
3145+
defer q.mutex.RUnlock()
3146+
3147+
daemons := make([]database.ProvisionerDaemon, 0)
3148+
for _, daemon := range q.provisionerDaemons {
3149+
if daemon.OrganizationID == organizationID {
3150+
daemon.Tags = maps.Clone(daemon.Tags)
3151+
daemons = append(daemons, daemon)
3152+
}
3153+
}
3154+
3155+
return daemons, nil
3156+
}
3157+
31433158
func (q *FakeQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (database.ProvisionerJob, error) {
31443159
q.mutex.RLock()
31453160
defer q.mutex.RUnlock()

coderd/database/dbmetrics/dbmetrics.go

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

coderd/database/dbmock/dbmock.go

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

coderd/database/modelmethods.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ func (o Organization) RBACObject() rbac.Object {
209209
}
210210

211211
func (p ProvisionerDaemon) RBACObject() rbac.Object {
212-
return rbac.ResourceProvisionerDaemon.WithID(p.ID)
212+
return rbac.ResourceProvisionerDaemon.
213+
WithID(p.ID).
214+
InOrg(p.OrganizationID)
213215
}
214216

215217
func (p ProvisionerKey) RBACObject() rbac.Object {

coderd/database/querier.go

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

coderd/database/queries.sql.go

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

coderd/database/queries/provisionerdaemons.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ SELECT
44
FROM
55
provisioner_daemons;
66

7+
-- name: GetProvisionerDaemonsByOrganization :many
8+
SELECT
9+
*
10+
FROM
11+
provisioner_daemons
12+
WHERE
13+
organization_id = @organization_id;
14+
715
-- name: DeleteOldProvisionerDaemons :exec
816
-- Delete provisioner daemons that have been created at least a week ago
917
-- and have not connected to coderd since a week.

enterprise/coderd/provisionerdaemons.go

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package coderd
33
import (
44
"context"
55
"database/sql"
6-
"errors"
76
"fmt"
87
"io"
98
"net/http"
@@ -21,7 +20,6 @@ import (
2120

2221
"cdr.dev/slog"
2322

24-
"github.com/coder/coder/v2/coderd"
2523
"github.com/coder/coder/v2/coderd/database"
2624
"github.com/coder/coder/v2/coderd/database/db2sdk"
2725
"github.com/coder/coder/v2/coderd/database/dbauthz"
@@ -65,21 +63,9 @@ func (api *API) provisionerDaemonsEnabledMW(next http.Handler) http.Handler {
6563
// @Router /organizations/{organization}/provisionerdaemons [get]
6664
func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
6765
ctx := r.Context()
68-
daemons, err := api.Database.GetProvisionerDaemons(ctx)
69-
if errors.Is(err, sql.ErrNoRows) {
70-
err = nil
71-
}
72-
if err != nil {
73-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
74-
Message: "Internal error fetching provisioner daemons.",
75-
Detail: err.Error(),
76-
})
77-
return
78-
}
79-
if daemons == nil {
80-
daemons = []database.ProvisionerDaemon{}
81-
}
82-
daemons, err = coderd.AuthorizeFilter(api.AGPL.HTTPAuth, r, policy.ActionRead, daemons)
66+
org := httpmw.OrganizationParam(r)
67+
68+
daemons, err := api.Database.GetProvisionerDaemonsByOrganization(ctx, org.ID)
8369
if err != nil {
8470
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
8571
Message: "Internal error fetching provisioner daemons.",
@@ -98,7 +84,7 @@ type provisionerDaemonAuth struct {
9884

9985
// authorize returns mutated tags and true if the given HTTP request is authorized to access the provisioner daemon
10086
// protobuf API, and returns nil, false otherwise.
101-
func (p *provisionerDaemonAuth) authorize(r *http.Request, tags map[string]string) (map[string]string, bool) {
87+
func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags map[string]string) (map[string]string, bool) {
10288
ctx := r.Context()
10389
apiKey, ok := httpmw.APIKeyOptional(r)
10490
if ok {
@@ -109,7 +95,7 @@ func (p *provisionerDaemonAuth) authorize(r *http.Request, tags map[string]strin
10995
return tags, true
11096
}
11197
ua := httpmw.UserAuthorization(r)
112-
if err := p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon); err == nil {
98+
if err := p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(orgID)); err == nil {
11399
// User is allowed to create provisioner daemons
114100
return tags, true
115101
}
@@ -185,7 +171,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
185171
api.Logger.Warn(ctx, "unnamed provisioner daemon")
186172
}
187173

188-
tags, authorized := api.provisionerDaemonAuth.authorize(r, tags)
174+
tags, authorized := api.provisionerDaemonAuth.authorize(r, organization.ID, tags)
189175
if !authorized {
190176
api.Logger.Warn(ctx, "unauthorized provisioner daemon serve request", slog.F("tags", tags))
191177
httpapi.Write(ctx, rw, http.StatusForbidden,

enterprise/coderd/provisionerdaemons_test.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,7 @@ func TestProvisionerDaemonServe(t *testing.T) {
211211
provisionersdk.TagScope: provisionersdk.ScopeOrganization,
212212
},
213213
})
214-
require.Error(t, err)
215-
var apiError *codersdk.Error
216-
require.ErrorAs(t, err, &apiError)
217-
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
214+
require.NoError(t, err)
218215
})
219216

220217
t.Run("OrganizationNoPerms", func(t *testing.T) {
@@ -556,3 +553,40 @@ func TestProvisionerDaemonServe(t *testing.T) {
556553
require.Len(t, daemons, 0)
557554
})
558555
}
556+
557+
func TestGetProvisionerDaemons(t *testing.T) {
558+
t.Parallel()
559+
560+
t.Run("OK", func(t *testing.T) {
561+
t.Parallel()
562+
client, _ := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
563+
Features: license.Features{
564+
codersdk.FeatureExternalProvisionerDaemons: 1,
565+
},
566+
}})
567+
org := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
568+
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, org.ID, rbac.ScopedRoleOrgAdmin(org.ID))
569+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
570+
defer cancel()
571+
daemonName := testutil.MustRandString(t, 63)
572+
srv, err := orgAdmin.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
573+
ID: uuid.New(),
574+
Name: daemonName,
575+
Organization: org.ID,
576+
Provisioners: []codersdk.ProvisionerType{
577+
codersdk.ProvisionerTypeEcho,
578+
},
579+
Tags: map[string]string{},
580+
})
581+
require.NoError(t, err)
582+
srv.DRPCConn().Close()
583+
584+
daemons, err := orgAdmin.OrganizationProvisionerDaemons(ctx, org.ID)
585+
require.NoError(t, err)
586+
if assert.Len(t, daemons, 1) {
587+
assert.Equal(t, daemonName, daemons[0].Name)
588+
assert.Equal(t, buildinfo.Version(), daemons[0].Version)
589+
assert.Equal(t, proto.CurrentVersion.String(), daemons[0].APIVersion)
590+
}
591+
})
592+
}

0 commit comments

Comments
 (0)