mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: add support for optional external auth providers (#12021)
This commit is contained in:
committed by
GitHub
parent
78c9f82719
commit
475c3650ca
@ -928,8 +928,7 @@ func (s *MethodTestSuite) TestTemplate() {
|
||||
JobID: jobID,
|
||||
})
|
||||
check.Args(database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams{
|
||||
JobID: jobID,
|
||||
ExternalAuthProviders: []string{},
|
||||
JobID: jobID,
|
||||
}).Asserts(t1, rbac.ActionUpdate).Returns()
|
||||
}))
|
||||
s.Run("GetTemplateInsights", s.Subtest(func(db database.Store, check *expects) {
|
||||
|
@ -219,7 +219,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
CreatedAt: now.Add(-14 * 24 * time.Hour),
|
||||
LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-7 * 24 * time.Hour).Add(time.Minute)},
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.VersionCurrent.String(),
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
|
||||
@ -230,7 +230,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
CreatedAt: now.Add(-8 * 24 * time.Hour),
|
||||
LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-8 * 24 * time.Hour).Add(time.Hour)},
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.VersionCurrent.String(),
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
|
||||
@ -243,7 +243,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
},
|
||||
CreatedAt: now.Add(-9 * 24 * time.Hour),
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.VersionCurrent.String(),
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{
|
||||
@ -257,7 +257,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
|
||||
CreatedAt: now.Add(-6 * 24 * time.Hour),
|
||||
LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-6 * 24 * time.Hour)},
|
||||
Version: "1.0.0",
|
||||
APIVersion: proto.VersionCurrent.String(),
|
||||
APIVersion: proto.CurrentVersion.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
2
coderd/database/dump.sql
generated
2
coderd/database/dump.sql
generated
@ -821,7 +821,7 @@ CREATE TABLE template_versions (
|
||||
readme character varying(1048576) NOT NULL,
|
||||
job_id uuid NOT NULL,
|
||||
created_by uuid NOT NULL,
|
||||
external_auth_providers text[],
|
||||
external_auth_providers jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
message character varying(1048576) DEFAULT ''::character varying NOT NULL,
|
||||
archived boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
@ -0,0 +1,61 @@
|
||||
-- We cannot alter the column type while a view depends on it, so we drop it and recreate it.
|
||||
DROP VIEW template_version_with_user;
|
||||
|
||||
|
||||
-- Does the opposite of `migrate_external_auth_providers_to_jsonb`
|
||||
-- eg. `'[{"id": "github"}, {"id": "gitlab"}]'::jsonb` would become `'{github,gitlab}'::text[]`
|
||||
CREATE OR REPLACE FUNCTION revert_migrate_external_auth_providers_to_jsonb(jsonb)
|
||||
RETURNS text[]
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
result text[];
|
||||
BEGIN
|
||||
SELECT
|
||||
array_agg(id::text) INTO result
|
||||
FROM (
|
||||
SELECT
|
||||
jsonb_array_elements($1) ->> 'id' AS id) AS external_auth_provider_ids;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
-- Remove the non-null constraint and default
|
||||
ALTER TABLE template_versions
|
||||
ALTER COLUMN external_auth_providers DROP DEFAULT;
|
||||
ALTER TABLE template_versions
|
||||
ALTER COLUMN external_auth_providers DROP NOT NULL;
|
||||
|
||||
|
||||
-- Update the column type and migrate the values
|
||||
ALTER TABLE template_versions
|
||||
ALTER COLUMN external_auth_providers TYPE text[]
|
||||
USING revert_migrate_external_auth_providers_to_jsonb(external_auth_providers);
|
||||
|
||||
|
||||
-- Recreate `template_version_with_user` as described in dump.sql
|
||||
CREATE VIEW template_version_with_user AS
|
||||
SELECT
|
||||
template_versions.id,
|
||||
template_versions.template_id,
|
||||
template_versions.organization_id,
|
||||
template_versions.created_at,
|
||||
template_versions.updated_at,
|
||||
template_versions.name,
|
||||
template_versions.readme,
|
||||
template_versions.job_id,
|
||||
template_versions.created_by,
|
||||
template_versions.external_auth_providers,
|
||||
template_versions.message,
|
||||
template_versions.archived,
|
||||
COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url,
|
||||
COALESCE(visible_users.username, ''::text) AS created_by_username
|
||||
FROM (public.template_versions
|
||||
LEFT JOIN visible_users ON (template_versions.created_by = visible_users.id));
|
||||
|
||||
COMMENT ON VIEW template_version_with_user IS 'Joins in the username + avatar url of the created by user.';
|
||||
|
||||
|
||||
-- Cleanup
|
||||
DROP FUNCTION revert_migrate_external_auth_providers_to_jsonb;
|
@ -0,0 +1,63 @@
|
||||
-- We cannot alter the column type while a view depends on it, so we drop it and recreate it.
|
||||
DROP VIEW template_version_with_user;
|
||||
|
||||
|
||||
-- Turns the list of provider names into JSONB with the type `Array<{ id: string; optional?: boolean }>`
|
||||
-- eg. `'{github,gitlab}'::text[]` would become `'[{"id": "github"}, {"id": "gitlab"}]'::jsonb`
|
||||
CREATE OR REPLACE FUNCTION migrate_external_auth_providers_to_jsonb(text[])
|
||||
RETURNS jsonb
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
result jsonb;
|
||||
BEGIN
|
||||
SELECT
|
||||
jsonb_agg(jsonb_build_object('id', value::text)) INTO result
|
||||
FROM
|
||||
unnest($1) AS value;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
-- Update the column type and migrate the values
|
||||
ALTER TABLE template_versions
|
||||
ALTER COLUMN external_auth_providers TYPE jsonb
|
||||
USING migrate_external_auth_providers_to_jsonb(external_auth_providers);
|
||||
|
||||
|
||||
-- Make the column non-nullable to make the types nicer on the Go side
|
||||
UPDATE template_versions
|
||||
SET external_auth_providers = '[]'::jsonb
|
||||
WHERE external_auth_providers IS NULL;
|
||||
ALTER TABLE template_versions
|
||||
ALTER COLUMN external_auth_providers SET DEFAULT '[]'::jsonb;
|
||||
ALTER TABLE template_versions
|
||||
ALTER COLUMN external_auth_providers SET NOT NULL;
|
||||
|
||||
|
||||
-- Recreate `template_version_with_user` as described in dump.sql
|
||||
CREATE VIEW template_version_with_user AS
|
||||
SELECT
|
||||
template_versions.id,
|
||||
template_versions.template_id,
|
||||
template_versions.organization_id,
|
||||
template_versions.created_at,
|
||||
template_versions.updated_at,
|
||||
template_versions.name,
|
||||
template_versions.readme,
|
||||
template_versions.job_id,
|
||||
template_versions.created_by,
|
||||
template_versions.external_auth_providers,
|
||||
template_versions.message,
|
||||
template_versions.archived,
|
||||
COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url,
|
||||
COALESCE(visible_users.username, ''::text) AS created_by_username
|
||||
FROM (public.template_versions
|
||||
LEFT JOIN visible_users ON (template_versions.created_by = visible_users.id));
|
||||
|
||||
COMMENT ON VIEW template_version_with_user IS 'Joins in the username + avatar url of the created by user.';
|
||||
|
||||
|
||||
-- Cleanup
|
||||
DROP FUNCTION migrate_external_auth_providers_to_jsonb;
|
@ -2080,20 +2080,20 @@ type TemplateTable struct {
|
||||
|
||||
// Joins in the username + avatar url of the created by user.
|
||||
type TemplateVersion struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Readme string `db:"readme" json:"readme"`
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
ExternalAuthProviders []string `db:"external_auth_providers" json:"external_auth_providers"`
|
||||
Message string `db:"message" json:"message"`
|
||||
Archived bool `db:"archived" json:"archived"`
|
||||
CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"`
|
||||
CreatedByUsername string `db:"created_by_username" json:"created_by_username"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Readme string `db:"readme" json:"readme"`
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
ExternalAuthProviders json.RawMessage `db:"external_auth_providers" json:"external_auth_providers"`
|
||||
Message string `db:"message" json:"message"`
|
||||
Archived bool `db:"archived" json:"archived"`
|
||||
CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"`
|
||||
CreatedByUsername string `db:"created_by_username" json:"created_by_username"`
|
||||
}
|
||||
|
||||
type TemplateVersionParameter struct {
|
||||
@ -2143,7 +2143,7 @@ type TemplateVersionTable struct {
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
// IDs of External auth providers for a specific template version
|
||||
ExternalAuthProviders []string `db:"external_auth_providers" json:"external_auth_providers"`
|
||||
ExternalAuthProviders json.RawMessage `db:"external_auth_providers" json:"external_auth_providers"`
|
||||
// Message describing the changes in this version of the template, similar to a Git commit message. Like a commit message, this should be a short, high-level description of the changes in this version of the template. This message is immutable and should not be updated after the fact.
|
||||
Message string `db:"message" json:"message"`
|
||||
Archived bool `db:"archived" json:"archived"`
|
||||
|
@ -6870,7 +6870,7 @@ func (q *sqlQuerier) GetPreviousTemplateVersion(ctx context.Context, arg GetPrev
|
||||
&i.Readme,
|
||||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.ExternalAuthProviders),
|
||||
&i.ExternalAuthProviders,
|
||||
&i.Message,
|
||||
&i.Archived,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -6901,7 +6901,7 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) (
|
||||
&i.Readme,
|
||||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.ExternalAuthProviders),
|
||||
&i.ExternalAuthProviders,
|
||||
&i.Message,
|
||||
&i.Archived,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -6932,7 +6932,7 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U
|
||||
&i.Readme,
|
||||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.ExternalAuthProviders),
|
||||
&i.ExternalAuthProviders,
|
||||
&i.Message,
|
||||
&i.Archived,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -6969,7 +6969,7 @@ func (q *sqlQuerier) GetTemplateVersionByTemplateIDAndName(ctx context.Context,
|
||||
&i.Readme,
|
||||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.ExternalAuthProviders),
|
||||
&i.ExternalAuthProviders,
|
||||
&i.Message,
|
||||
&i.Archived,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -7006,7 +7006,7 @@ func (q *sqlQuerier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UU
|
||||
&i.Readme,
|
||||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.ExternalAuthProviders),
|
||||
&i.ExternalAuthProviders,
|
||||
&i.Message,
|
||||
&i.Archived,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -7100,7 +7100,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge
|
||||
&i.Readme,
|
||||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.ExternalAuthProviders),
|
||||
&i.ExternalAuthProviders,
|
||||
&i.Message,
|
||||
&i.Archived,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -7142,7 +7142,7 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create
|
||||
&i.Readme,
|
||||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.ExternalAuthProviders),
|
||||
&i.ExternalAuthProviders,
|
||||
&i.Message,
|
||||
&i.Archived,
|
||||
&i.CreatedByAvatarURL,
|
||||
@ -7292,13 +7292,13 @@ WHERE
|
||||
`
|
||||
|
||||
type UpdateTemplateVersionExternalAuthProvidersByJobIDParams struct {
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
ExternalAuthProviders []string `db:"external_auth_providers" json:"external_auth_providers"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
ExternalAuthProviders json.RawMessage `db:"external_auth_providers" json:"external_auth_providers"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateTemplateVersionExternalAuthProvidersByJobID, arg.JobID, pq.Array(arg.ExternalAuthProviders), arg.UpdatedAt)
|
||||
_, err := q.db.ExecContext(ctx, updateTemplateVersionExternalAuthProvidersByJobID, arg.JobID, arg.ExternalAuthProviders, arg.UpdatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,11 @@ func (t TemplateACL) Value() (driver.Value, error) {
|
||||
return json.Marshal(t)
|
||||
}
|
||||
|
||||
type ExternalAuthProvider struct {
|
||||
ID string `json:"id"`
|
||||
Optional bool `json:"optional,omitempty"`
|
||||
}
|
||||
|
||||
type StringMap map[string]string
|
||||
|
||||
func (m *StringMap) Scan(src interface{}) error {
|
||||
|
Reference in New Issue
Block a user