Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e027a3a

Browse files
committed
Merge branch 'main' into lilac/persist-terraform-modules
2 parents 0cf5df5 + d9ef6ed commit e027a3a

File tree

209 files changed

+1469
-2269
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

209 files changed

+1469
-2269
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ agent/proto/ @spikecurtis @johnstcn
44
tailnet/proto/ @spikecurtis @johnstcn
55
vpn/vpn.proto @spikecurtis @johnstcn
66
vpn/version.go @spikecurtis @johnstcn
7+
provisionerd/proto/ @spikecurtis @johnstcn
8+
provisionersdk/proto/ @spikecurtis @johnstcn

agent/agentscripts/agentscripts.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"os/user"
1111
"path/filepath"
1212
"sync"
13-
"sync/atomic"
1413
"time"
1514

1615
"github.com/google/uuid"
@@ -104,7 +103,6 @@ type Runner struct {
104103
closed chan struct{}
105104
closeMutex sync.Mutex
106105
cron *cron.Cron
107-
initialized atomic.Bool
108106
scripts []runnerScript
109107
dataDir string
110108
scriptCompleted ScriptCompletedFunc
@@ -113,6 +111,9 @@ type Runner struct {
113111
// execute startup scripts, and scripts on a cron schedule. Both will increment
114112
// this counter.
115113
scriptsExecuted *prometheus.CounterVec
114+
115+
initMutex sync.Mutex
116+
initialized bool
116117
}
117118

118119
// DataDir returns the directory where scripts data is stored.
@@ -154,10 +155,12 @@ func WithPostStartScripts(scripts ...codersdk.WorkspaceAgentScript) InitOption {
154155
// It also schedules any scripts that have a schedule.
155156
// This function must be called before Execute.
156157
func (r *Runner) Init(scripts []codersdk.WorkspaceAgentScript, scriptCompleted ScriptCompletedFunc, opts ...InitOption) error {
157-
if r.initialized.Load() {
158+
r.initMutex.Lock()
159+
defer r.initMutex.Unlock()
160+
if r.initialized {
158161
return xerrors.New("init: already initialized")
159162
}
160-
r.initialized.Store(true)
163+
r.initialized = true
161164
r.scripts = toRunnerScript(scripts...)
162165
r.scriptCompleted = scriptCompleted
163166
for _, opt := range opts {
@@ -227,6 +230,18 @@ const (
227230

228231
// Execute runs a set of scripts according to a filter.
229232
func (r *Runner) Execute(ctx context.Context, option ExecuteOption) error {
233+
initErr := func() error {
234+
r.initMutex.Lock()
235+
defer r.initMutex.Unlock()
236+
if !r.initialized {
237+
return xerrors.New("execute: not initialized")
238+
}
239+
return nil
240+
}()
241+
if initErr != nil {
242+
return initErr
243+
}
244+
230245
var eg errgroup.Group
231246
for _, script := range r.scripts {
232247
runScript := (option == ExecuteStartScripts && script.RunOnStart) ||

cli/ssh.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,10 @@ func writeCoderConnectNetInfo(ctx context.Context, networkInfoDir string) error
15421542
if !ok {
15431543
fs = afero.NewOsFs()
15441544
}
1545+
if err := fs.MkdirAll(networkInfoDir, 0o700); err != nil {
1546+
return xerrors.Errorf("mkdir: %w", err)
1547+
}
1548+
15451549
// The VS Code extension obtains the PID of the SSH process to
15461550
// find the log file associated with a SSH session.
15471551
//

coderd/database/dump.sql

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
DROP TRIGGER IF EXISTS protect_deleting_organizations ON organizations;
2+
3+
-- Replace the function with the new implementation
4+
CREATE OR REPLACE FUNCTION protect_deleting_organizations()
5+
RETURNS TRIGGER AS
6+
$$
7+
DECLARE
8+
workspace_count int;
9+
template_count int;
10+
group_count int;
11+
member_count int;
12+
provisioner_keys_count int;
13+
BEGIN
14+
workspace_count := (
15+
SELECT count(*) as count FROM workspaces
16+
WHERE
17+
workspaces.organization_id = OLD.id
18+
AND workspaces.deleted = false
19+
);
20+
21+
template_count := (
22+
SELECT count(*) as count FROM templates
23+
WHERE
24+
templates.organization_id = OLD.id
25+
AND templates.deleted = false
26+
);
27+
28+
group_count := (
29+
SELECT count(*) as count FROM groups
30+
WHERE
31+
groups.organization_id = OLD.id
32+
);
33+
34+
member_count := (
35+
SELECT count(*) as count FROM organization_members
36+
WHERE
37+
organization_members.organization_id = OLD.id
38+
);
39+
40+
provisioner_keys_count := (
41+
Select count(*) as count FROM provisioner_keys
42+
WHERE
43+
provisioner_keys.organization_id = OLD.id
44+
);
45+
46+
-- Fail the deletion if one of the following:
47+
-- * the organization has 1 or more workspaces
48+
-- * the organization has 1 or more templates
49+
-- * the organization has 1 or more groups other than "Everyone" group
50+
-- * the organization has 1 or more members other than the organization owner
51+
-- * the organization has 1 or more provisioner keys
52+
53+
-- Only create error message for resources that actually exist
54+
IF (workspace_count + template_count + provisioner_keys_count) > 0 THEN
55+
DECLARE
56+
error_message text := 'cannot delete organization: organization has ';
57+
error_parts text[] := '{}';
58+
BEGIN
59+
IF workspace_count > 0 THEN
60+
error_parts := array_append(error_parts, workspace_count || ' workspaces');
61+
END IF;
62+
63+
IF template_count > 0 THEN
64+
error_parts := array_append(error_parts, template_count || ' templates');
65+
END IF;
66+
67+
IF provisioner_keys_count > 0 THEN
68+
error_parts := array_append(error_parts, provisioner_keys_count || ' provisioner keys');
69+
END IF;
70+
71+
error_message := error_message || array_to_string(error_parts, ', ') || ' that must be deleted first';
72+
RAISE EXCEPTION '%', error_message;
73+
END;
74+
END IF;
75+
76+
IF (group_count) > 1 THEN
77+
RAISE EXCEPTION 'cannot delete organization: organization has % groups that must be deleted first', group_count - 1;
78+
END IF;
79+
80+
-- Allow 1 member to exist, because you cannot remove yourself. You can
81+
-- remove everyone else. Ideally, we only omit the member that matches
82+
-- the user_id of the caller, however in a trigger, the caller is unknown.
83+
IF (member_count) > 1 THEN
84+
RAISE EXCEPTION 'cannot delete organization: organization has % members that must be deleted first', member_count - 1;
85+
END IF;
86+
87+
RETURN NEW;
88+
END;
89+
$$ LANGUAGE plpgsql;
90+
91+
-- Trigger to protect organizations from being soft deleted with existing resources
92+
CREATE TRIGGER protect_deleting_organizations
93+
BEFORE UPDATE ON organizations
94+
FOR EACH ROW
95+
WHEN (NEW.deleted = true AND OLD.deleted = false)
96+
EXECUTE FUNCTION protect_deleting_organizations();
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
DROP TRIGGER IF EXISTS protect_deleting_organizations ON organizations;
2+
3+
-- Replace the function with the new implementation
4+
CREATE OR REPLACE FUNCTION protect_deleting_organizations()
5+
RETURNS TRIGGER AS
6+
$$
7+
DECLARE
8+
workspace_count int;
9+
template_count int;
10+
group_count int;
11+
member_count int;
12+
provisioner_keys_count int;
13+
BEGIN
14+
workspace_count := (
15+
SELECT count(*) as count FROM workspaces
16+
WHERE
17+
workspaces.organization_id = OLD.id
18+
AND workspaces.deleted = false
19+
);
20+
21+
template_count := (
22+
SELECT count(*) as count FROM templates
23+
WHERE
24+
templates.organization_id = OLD.id
25+
AND templates.deleted = false
26+
);
27+
28+
group_count := (
29+
SELECT count(*) as count FROM groups
30+
WHERE
31+
groups.organization_id = OLD.id
32+
);
33+
34+
member_count := (
35+
SELECT
36+
count(*) AS count
37+
FROM
38+
organization_members
39+
LEFT JOIN users ON users.id = organization_members.user_id
40+
WHERE
41+
organization_members.organization_id = OLD.id
42+
AND users.deleted = FALSE
43+
);
44+
45+
provisioner_keys_count := (
46+
Select count(*) as count FROM provisioner_keys
47+
WHERE
48+
provisioner_keys.organization_id = OLD.id
49+
);
50+
51+
-- Fail the deletion if one of the following:
52+
-- * the organization has 1 or more workspaces
53+
-- * the organization has 1 or more templates
54+
-- * the organization has 1 or more groups other than "Everyone" group
55+
-- * the organization has 1 or more members other than the organization owner
56+
-- * the organization has 1 or more provisioner keys
57+
58+
-- Only create error message for resources that actually exist
59+
IF (workspace_count + template_count + provisioner_keys_count) > 0 THEN
60+
DECLARE
61+
error_message text := 'cannot delete organization: organization has ';
62+
error_parts text[] := '{}';
63+
BEGIN
64+
IF workspace_count > 0 THEN
65+
error_parts := array_append(error_parts, workspace_count || ' workspaces');
66+
END IF;
67+
68+
IF template_count > 0 THEN
69+
error_parts := array_append(error_parts, template_count || ' templates');
70+
END IF;
71+
72+
IF provisioner_keys_count > 0 THEN
73+
error_parts := array_append(error_parts, provisioner_keys_count || ' provisioner keys');
74+
END IF;
75+
76+
error_message := error_message || array_to_string(error_parts, ', ') || ' that must be deleted first';
77+
RAISE EXCEPTION '%', error_message;
78+
END;
79+
END IF;
80+
81+
IF (group_count) > 1 THEN
82+
RAISE EXCEPTION 'cannot delete organization: organization has % groups that must be deleted first', group_count - 1;
83+
END IF;
84+
85+
-- Allow 1 member to exist, because you cannot remove yourself. You can
86+
-- remove everyone else. Ideally, we only omit the member that matches
87+
-- the user_id of the caller, however in a trigger, the caller is unknown.
88+
IF (member_count) > 1 THEN
89+
RAISE EXCEPTION 'cannot delete organization: organization has % members that must be deleted first', member_count - 1;
90+
END IF;
91+
92+
RETURN NEW;
93+
END;
94+
$$ LANGUAGE plpgsql;
95+
96+
-- Trigger to protect organizations from being soft deleted with existing resources
97+
CREATE TRIGGER protect_deleting_organizations
98+
BEFORE UPDATE ON organizations
99+
FOR EACH ROW
100+
WHEN (NEW.deleted = true AND OLD.deleted = false)
101+
EXECUTE FUNCTION protect_deleting_organizations();

coderd/database/querier_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3586,6 +3586,43 @@ func TestOrganizationDeleteTrigger(t *testing.T) {
35863586
require.ErrorContains(t, err, "cannot delete organization")
35873587
require.ErrorContains(t, err, "has 1 members")
35883588
})
3589+
3590+
t.Run("UserDeletedButNotRemovedFromOrg", func(t *testing.T) {
3591+
t.Parallel()
3592+
db, _ := dbtestutil.NewDB(t)
3593+
3594+
orgA := dbfake.Organization(t, db).Do()
3595+
3596+
userA := dbgen.User(t, db, database.User{})
3597+
userB := dbgen.User(t, db, database.User{})
3598+
userC := dbgen.User(t, db, database.User{})
3599+
3600+
dbgen.OrganizationMember(t, db, database.OrganizationMember{
3601+
OrganizationID: orgA.Org.ID,
3602+
UserID: userA.ID,
3603+
})
3604+
dbgen.OrganizationMember(t, db, database.OrganizationMember{
3605+
OrganizationID: orgA.Org.ID,
3606+
UserID: userB.ID,
3607+
})
3608+
dbgen.OrganizationMember(t, db, database.OrganizationMember{
3609+
OrganizationID: orgA.Org.ID,
3610+
UserID: userC.ID,
3611+
})
3612+
3613+
// Delete one of the users but don't remove them from the org
3614+
ctx := testutil.Context(t, testutil.WaitShort)
3615+
db.UpdateUserDeletedByID(ctx, userB.ID)
3616+
3617+
err := db.UpdateOrganizationDeletedByID(ctx, database.UpdateOrganizationDeletedByIDParams{
3618+
UpdatedAt: dbtime.Now(),
3619+
ID: orgA.Org.ID,
3620+
})
3621+
require.Error(t, err)
3622+
// cannot delete organization: organization has 1 members that must be deleted first
3623+
require.ErrorContains(t, err, "cannot delete organization")
3624+
require.ErrorContains(t, err, "has 1 members")
3625+
})
35893626
}
35903627

35913628
type templateVersionWithPreset struct {

coderd/database/queries.sql.go

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

0 commit comments

Comments
 (0)