Skip to content

Commit 9baecf9

Browse files
authored
Merge branch 'main' into depot-runners
2 parents 12553e3 + 313d4e0 commit 9baecf9

34 files changed

+1717
-223
lines changed

.github/actions/setup-go/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: |
44
inputs:
55
version:
66
description: "The Go version to use."
7-
default: "1.22.3"
7+
default: "1.22.4"
88
runs:
99
using: "composite"
1010
steps:

.github/workflows/ci.yaml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,9 @@ jobs:
254254
- name: Setup Node
255255
uses: ./.github/actions/setup-node
256256

257+
# Use default Go version
257258
- name: Setup Go
258-
uses: actions/setup-go@v5
259-
with:
260-
# This doesn't need caching. It's super fast anyways!
261-
cache: false
262-
go-version: 1.21.9
259+
uses: ./.github/actions/setup-go
263260

264261
- name: Install shfmt
265262
run: go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0

cli/login.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,13 @@ func (r *RootCmd) login() *serpent.Command {
336336
return xerrors.Errorf("write server url: %w", err)
337337
}
338338

339+
// If the current organization cannot be fetched, then reset the organization context.
340+
// Otherwise, organization cli commands will fail.
341+
_, err = CurrentOrganization(r, inv, client)
342+
if err != nil {
343+
_ = config.Organization().Delete()
344+
}
345+
339346
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Welcome to Coder, %s! You're authenticated.\n", pretty.Sprint(cliui.DefaultStyles.Keyword, resp.Username))
340347
return nil
341348
},

cli/login_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"fmt"
66
"net/http"
77
"net/http/httptest"
8+
"os"
89
"runtime"
910
"testing"
1011

12+
"github.com/google/uuid"
1113
"github.com/stretchr/testify/assert"
1214
"github.com/stretchr/testify/require"
1315

@@ -304,4 +306,48 @@ func TestLogin(t *testing.T) {
304306
// This **should not be equal** to the token we passed in.
305307
require.NotEqual(t, client.SessionToken(), sessionFile)
306308
})
309+
310+
// Login should reset the configured organization if the user is not a member
311+
t.Run("ResetOrganization", func(t *testing.T) {
312+
t.Parallel()
313+
client := coderdtest.New(t, nil)
314+
coderdtest.CreateFirstUser(t, client)
315+
root, cfg := clitest.New(t, "login", client.URL.String(), "--token", client.SessionToken())
316+
317+
notRealOrg := uuid.NewString()
318+
err := cfg.Organization().Write(notRealOrg)
319+
require.NoError(t, err, "write bad org to config")
320+
321+
err = root.Run()
322+
require.NoError(t, err)
323+
sessionFile, err := cfg.Session().Read()
324+
require.NoError(t, err)
325+
require.NotEqual(t, client.SessionToken(), sessionFile)
326+
327+
// Organization config should be deleted since the org does not exist
328+
selected, err := cfg.Organization().Read()
329+
require.ErrorIs(t, err, os.ErrNotExist)
330+
require.NotEqual(t, selected, notRealOrg)
331+
})
332+
333+
t.Run("KeepOrganizationContext", func(t *testing.T) {
334+
t.Parallel()
335+
client := coderdtest.New(t, nil)
336+
first := coderdtest.CreateFirstUser(t, client)
337+
root, cfg := clitest.New(t, "login", client.URL.String(), "--token", client.SessionToken())
338+
339+
err := cfg.Organization().Write(first.OrganizationID.String())
340+
require.NoError(t, err, "write bad org to config")
341+
342+
err = root.Run()
343+
require.NoError(t, err)
344+
sessionFile, err := cfg.Session().Read()
345+
require.NoError(t, err)
346+
require.NotEqual(t, client.SessionToken(), sessionFile)
347+
348+
// Organization config should be deleted since the org does not exist
349+
selected, err := cfg.Organization().Read()
350+
require.NoError(t, err)
351+
require.Equal(t, selected, first.OrganizationID.String())
352+
})
307353
}

coderd/audit/diff.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ type Auditable interface {
2222
database.HealthSettings |
2323
database.OAuth2ProviderApp |
2424
database.OAuth2ProviderAppSecret |
25-
database.CustomRole
25+
database.CustomRole |
26+
database.AuditableOrganizationMember
2627
}
2728

2829
// Map is a map of changed fields in an audited resource. It maps field names to

coderd/audit/request.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ func ResourceTarget[T Auditable](tgt T) string {
105105
return typed.DisplaySecret
106106
case database.CustomRole:
107107
return typed.Name
108+
case database.AuditableOrganizationMember:
109+
return typed.Username
108110
default:
109111
panic(fmt.Sprintf("unknown resource %T for ResourceTarget", tgt))
110112
}
@@ -144,6 +146,8 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
144146
return typed.ID
145147
case database.CustomRole:
146148
return typed.ID
149+
case database.AuditableOrganizationMember:
150+
return typed.UserID
147151
default:
148152
panic(fmt.Sprintf("unknown resource %T for ResourceID", tgt))
149153
}
@@ -181,6 +185,8 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
181185
return database.ResourceTypeOauth2ProviderAppSecret
182186
case database.CustomRole:
183187
return database.ResourceTypeCustomRole
188+
case database.AuditableOrganizationMember:
189+
return database.ResourceTypeOrganizationMember
184190
default:
185191
panic(fmt.Sprintf("unknown resource %T for ResourceType", typed))
186192
}
@@ -219,6 +225,8 @@ func ResourceRequiresOrgID[T Auditable]() bool {
219225
return false
220226
case database.CustomRole:
221227
return true
228+
case database.AuditableOrganizationMember:
229+
return true
222230
default:
223231
panic(fmt.Sprintf("unknown resource %T for ResourceRequiresOrgID", tgt))
224232
}

coderd/coderdtest/coderdtest.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,18 @@ func NewTaggedProvisionerDaemon(t testing.TB, coderAPI *coderd.API, name string,
600600
}
601601

602602
func NewExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uuid.UUID, tags map[string]string) io.Closer {
603+
t.Helper()
604+
605+
// Without this check, the provisioner will silently fail.
606+
entitlements, err := client.Entitlements(context.Background())
607+
if err == nil {
608+
feature := entitlements.Features[codersdk.FeatureExternalProvisionerDaemons]
609+
if !feature.Enabled || feature.Entitlement != codersdk.EntitlementEntitled {
610+
require.NoError(t, xerrors.Errorf("external provisioner daemons require an entitled license"))
611+
return nil
612+
}
613+
}
614+
603615
echoClient, echoServer := drpc.MemTransportPipe()
604616
ctx, cancelFunc := context.WithCancel(context.Background())
605617
serveDone := make(chan struct{})
@@ -638,6 +650,7 @@ func NewExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uui
638650
t.Cleanup(func() {
639651
_ = closer.Close()
640652
})
653+
641654
return closer
642655
}
643656

@@ -790,6 +803,37 @@ func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationI
790803
return other, user
791804
}
792805

806+
type CreateOrganizationOptions struct {
807+
// IncludeProvisionerDaemon will spin up an external provisioner for the organization.
808+
// This requires enterprise and the feature 'codersdk.FeatureExternalProvisionerDaemons'
809+
IncludeProvisionerDaemon bool
810+
}
811+
812+
func CreateOrganization(t *testing.T, client *codersdk.Client, opts CreateOrganizationOptions, mutators ...func(*codersdk.CreateOrganizationRequest)) codersdk.Organization {
813+
ctx := testutil.Context(t, testutil.WaitMedium)
814+
req := codersdk.CreateOrganizationRequest{
815+
Name: strings.ReplaceAll(strings.ToLower(namesgenerator.GetRandomName(0)), "_", "-"),
816+
DisplayName: namesgenerator.GetRandomName(1),
817+
Description: namesgenerator.GetRandomName(1),
818+
Icon: "",
819+
}
820+
for _, mutator := range mutators {
821+
mutator(&req)
822+
}
823+
824+
org, err := client.CreateOrganization(ctx, req)
825+
require.NoError(t, err)
826+
827+
if opts.IncludeProvisionerDaemon {
828+
closer := NewExternalProvisionerDaemon(t, client, org.ID, map[string]string{})
829+
t.Cleanup(func() {
830+
_ = closer.Close()
831+
})
832+
}
833+
834+
return org
835+
}
836+
793837
// CreateTemplateVersion creates a template import provisioner job
794838
// with the responses provided. It uses the "echo" provisioner for compatibility
795839
// with testing.

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/migrations/000220_audit_org_member.down.sql

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'organization_member';

coderd/database/modelmethods.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ func (s WorkspaceAgentStatus) Valid() bool {
6060
}
6161
}
6262

63+
type AuditableOrganizationMember struct {
64+
OrganizationMember
65+
Username string `json:"username"`
66+
}
67+
68+
func (m OrganizationMember) Auditable(username string) AuditableOrganizationMember {
69+
return AuditableOrganizationMember{
70+
OrganizationMember: m,
71+
Username: username,
72+
}
73+
}
74+
6375
type AuditableGroup struct {
6476
Group
6577
Members []GroupMember `json:"members"`

coderd/database/models.go

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/members.go

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/google/uuid"
88
"golang.org/x/xerrors"
99

10+
"github.com/coder/coder/v2/coderd/audit"
1011
"github.com/coder/coder/v2/coderd/database"
1112
"github.com/coder/coder/v2/coderd/database/db2sdk"
1213
"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -27,10 +28,19 @@ import (
2728
// @Router /organizations/{organization}/members/{user} [post]
2829
func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request) {
2930
var (
30-
ctx = r.Context()
31-
organization = httpmw.OrganizationParam(r)
32-
user = httpmw.UserParam(r)
31+
ctx = r.Context()
32+
organization = httpmw.OrganizationParam(r)
33+
user = httpmw.UserParam(r)
34+
auditor = api.Auditor.Load()
35+
aReq, commitAudit = audit.InitRequest[database.AuditableOrganizationMember](rw, &audit.RequestParams{
36+
Audit: *auditor,
37+
Log: api.Logger,
38+
Request: r,
39+
Action: database.AuditActionCreate,
40+
})
3341
)
42+
aReq.Old = database.AuditableOrganizationMember{}
43+
defer commitAudit()
3444

3545
member, err := api.Database.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
3646
OrganizationID: organization.ID,
@@ -54,6 +64,7 @@ func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request)
5464
return
5565
}
5666

67+
aReq.New = member.Auditable(user.Username)
5768
resp, err := convertOrganizationMembers(ctx, api.Database, []database.OrganizationMember{member})
5869
if err != nil {
5970
httpapi.InternalServerError(rw, err)
@@ -79,10 +90,19 @@ func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request)
7990
// @Router /organizations/{organization}/members/{user} [delete]
8091
func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request) {
8192
var (
82-
ctx = r.Context()
83-
organization = httpmw.OrganizationParam(r)
84-
member = httpmw.OrganizationMemberParam(r)
93+
ctx = r.Context()
94+
organization = httpmw.OrganizationParam(r)
95+
member = httpmw.OrganizationMemberParam(r)
96+
auditor = api.Auditor.Load()
97+
aReq, commitAudit = audit.InitRequest[database.AuditableOrganizationMember](rw, &audit.RequestParams{
98+
Audit: *auditor,
99+
Log: api.Logger,
100+
Request: r,
101+
Action: database.AuditActionDelete,
102+
})
85103
)
104+
aReq.Old = member.OrganizationMember.Auditable(member.Username)
105+
defer commitAudit()
86106

87107
err := api.Database.DeleteOrganizationMember(ctx, database.DeleteOrganizationMemberParams{
88108
OrganizationID: organization.ID,
@@ -97,6 +117,7 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
97117
return
98118
}
99119

120+
aReq.New = database.AuditableOrganizationMember{}
100121
httpapi.Write(ctx, rw, http.StatusOK, "organization member removed")
101122
}
102123

@@ -149,13 +170,22 @@ func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) {
149170
// @Router /organizations/{organization}/members/{user}/roles [put]
150171
func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
151172
var (
152-
ctx = r.Context()
153-
organization = httpmw.OrganizationParam(r)
154-
member = httpmw.OrganizationMemberParam(r)
155-
apiKey = httpmw.APIKey(r)
173+
ctx = r.Context()
174+
organization = httpmw.OrganizationParam(r)
175+
member = httpmw.OrganizationMemberParam(r)
176+
apiKey = httpmw.APIKey(r)
177+
auditor = api.Auditor.Load()
178+
aReq, commitAudit = audit.InitRequest[database.AuditableOrganizationMember](rw, &audit.RequestParams{
179+
Audit: *auditor,
180+
Log: api.Logger,
181+
Request: r,
182+
Action: database.AuditActionWrite,
183+
})
156184
)
185+
aReq.Old = member.OrganizationMember.Auditable(member.Username)
186+
defer commitAudit()
157187

158-
if apiKey.UserID == member.UserID {
188+
if apiKey.UserID == member.OrganizationMember.UserID {
159189
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
160190
Message: "You cannot change your own organization roles.",
161191
})
@@ -182,6 +212,10 @@ func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
182212
})
183213
return
184214
}
215+
aReq.New = database.AuditableOrganizationMember{
216+
OrganizationMember: updatedUser,
217+
Username: member.Username,
218+
}
185219

186220
resp, err := convertOrganizationMembers(ctx, api.Database, []database.OrganizationMember{updatedUser})
187221
if err != nil {

codersdk/audit.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131
// nolint:gosec // This is not a secret.
3232
ResourceTypeOAuth2ProviderAppSecret ResourceType = "oauth2_provider_app_secret"
3333
ResourceTypeCustomRole ResourceType = "custom_role"
34+
ResourceTypeOrganizationMember = "organization_member"
3435
)
3536

3637
func (r ResourceType) FriendlyString() string {
@@ -69,6 +70,8 @@ func (r ResourceType) FriendlyString() string {
6970
return "oauth2 app secret"
7071
case ResourceTypeCustomRole:
7172
return "custom role"
73+
case ResourceTypeOrganizationMember:
74+
return "organization member"
7275
default:
7376
return "unknown"
7477
}

0 commit comments

Comments
 (0)