mirror of
https://github.com/coder/coder.git
synced 2026-11-16 04:40:43 +08:00
355 lines
13 KiB
TypeScript
355 lines
13 KiB
TypeScript
import {
|
|
checkAuthorization,
|
|
createWorkspace,
|
|
getTemplates,
|
|
getTemplateVersionGitAuth,
|
|
getTemplateVersionRichParameters,
|
|
getTemplateVersionSchema,
|
|
} from "api/api"
|
|
import {
|
|
CreateWorkspaceRequest,
|
|
ParameterSchema,
|
|
Template,
|
|
TemplateVersionGitAuth,
|
|
TemplateVersionParameter,
|
|
User,
|
|
Workspace,
|
|
} from "api/typesGenerated"
|
|
import { assign, createMachine } from "xstate"
|
|
|
|
export const REFRESH_GITAUTH_BROADCAST_CHANNEL = "gitauth_refresh"
|
|
|
|
type CreateWorkspaceContext = {
|
|
organizationId: string
|
|
owner: User | null
|
|
templateName: string
|
|
templates?: Template[]
|
|
selectedTemplate?: Template
|
|
templateParameters?: TemplateVersionParameter[]
|
|
templateSchema?: ParameterSchema[]
|
|
templateGitAuth?: TemplateVersionGitAuth[]
|
|
createWorkspaceRequest?: CreateWorkspaceRequest
|
|
createdWorkspace?: Workspace
|
|
createWorkspaceError?: Error | unknown
|
|
getTemplatesError?: Error | unknown
|
|
getTemplateParametersError?: Error | unknown
|
|
getTemplateGitAuthError?: Error | unknown
|
|
getTemplateSchemaError?: Error | unknown
|
|
permissions?: Record<string, boolean>
|
|
checkPermissionsError?: Error | unknown
|
|
}
|
|
|
|
type CreateWorkspaceEvent = {
|
|
type: "CREATE_WORKSPACE"
|
|
request: CreateWorkspaceRequest
|
|
owner: User | null
|
|
}
|
|
|
|
type SelectOwnerEvent = {
|
|
type: "SELECT_OWNER"
|
|
owner: User | null
|
|
}
|
|
|
|
type RefreshGitAuthEvent = {
|
|
type: "REFRESH_GITAUTH"
|
|
}
|
|
|
|
export const createWorkspaceMachine =
|
|
/** @xstate-layout N4IgpgJg5mDOIC5QGMBOYCGAXMB1A9qgNawAOGyYAyltmAHQxZYCWAdlACpgC2pANnVgBiCPjYN2AN3xEGaTDgLEyFarRyMwzdl14ChCafmTYW4gNoAGALrWbiUKXywWrcY5AAPRABYATAA0IACeiACMvgCs9ADssQBsVgDM-v5JyQnJUQkAvrnBCnTKJOSUNHRaOhzcfII4ImIS9MZy9EVKhKVqFZpMrDX69XBGbDKm7mz2FuEOSCDOrpOePgjJAJzr9AAcVr4J677bCeFWB3vBYQgBvvQJB-5R6+GxVulPUfmF6MVdquUaBj9XS1AwNYRgVCoQj0MEAM0IPHaP06KjK6kqwMGdUMxgm5imtnsnkWbgJK0QGy2u32h2Op3OvkuiH8vis9HCDxO0XC2z5CX8XxAHTwf3RvSB2gGehxOCoyAAFrwMKJxJIxrJ5CjRWieoCqtLQcN5UqeBhRuMzJYibYSS4yR55qtTv52bFeYlfPEor4XuFmddwlscqz1mdfMlfZlfEKRSV-hi+lKQUM6CblRCoTD4YjkYodd0AZjk9iwdRFcqLSYrYS7Lb5qTlk6Im83R6El7Yj6-QH4gl6M8ctsoht7skrJ8CsLtfHxfqsTKywAFDCoDA8bSQxpqloatpxsV64vVRfDFdrjc4VCwKv4611uZOe1N0DOgXhOKRjZf-axR4Btl2XWWJngSKJtgCHJwnSWMZ0PIskxPI06HPddN2vTNoVQWF6gRVAkQPXUEMlJDUxwVDLy3W8a2mesnyWclmwQTl-A-WIv3WH8Ej-KIAyyW4Em2Z5XV5fx1m48JYPzWcj0Qw0yLAABxNwAEEAFcsAVVVmlaLVpPgxMSPk2UlNUjSFWoyZaMfBZn0Y18WSDfsfUEvlfEHViA2SbZ-HoDZwPSe4omCwUp0IwtDINFMTOUrB1M0zDs1w3NwoTCUotLYZYviiy8Rom0bMbezvEc8T6BcvkII8-1Qj8dZfP2R4fXEziUliKTfiIyKK2QIhdCXSEeBYWBXHEbcdL3eQlV6gb8OG0a2FgYkGzsx0HIQfwfJiKJXmSZJoO88IRwAqwP22aD3OeLshP2DrUQi9Ker6jhZqGkaCRESEsJw7A8II6aiFe+aPuW+iHTYCkNqiKxtgHVi+XWYK2SZWqNr5HZslAiNoJSSdvn0rr0rhFh+H4frV3XEQAGEACUAFEVM4OmAH1cAAeRpgBpKglxUqm6dB2yGLWkq0cecrdv2-xDuO1GXluRH9s5H1wKObi7oLNL9WJ0nyYvEQqDpgAZOmqc4Zm2dwAA5OmacFoqRdWcd0asSWkZuoJUbSftpc2vZ6rZDsXg1mTiLzMwOFDsBtPVGR9zgwn9Q6XQo8sglrLtYWIaYzbxZ2lIpZl5IA3O+hLvWeleSiVj6pDgzHpRFODMS7Cc3w8P7q1ypk8jgy0-ve3Vuz9bc+2yWDvO2WrjE2HMcybZYiOHJYm2fIpzYfAIDgTxUrnOhM-ByGAFoEgDE-6CsS+3n8d0nl9aW8enAmHvnEtTyEA+X1F4cOWryM0k5JyTiAZWS3DSKBdIC9zjZDronY8xkyzpjNJ-YqqxXjORXuEfaEYAh9gAtLO4aQNgenqkdSMsCX7wOisuCmlFrwoMdhETazl9hCQ2NLKwFd1gnRiCkMM51og8k2BQruclqFZTMppBhw9RZBldvQTa0FzgdnuF5Y4ZdgrumyI8JyC8RF700E9fqg1gZjWkZDUBWwDgjjElgnawE1GxAUUJPYglwIjjeDGMKCdKGaB1mTF6tD4ArSzpDfabwdiXxApseqtjT6o1SE4txrVXTpHSF4-GnVfF6QjlAKO5ic5RCOn5LsbwjpHWeJyAMZVThHUgvcCSvp9GyRyTgCABT1rhN8rsV2MTYmgQDC6BR7xuznByCcZpYcvqEA6aLSxdxFa2OyCBWIAYimwxXvwoMrp3GrzXkAA */
|
|
createMachine(
|
|
{
|
|
id: "createWorkspaceState",
|
|
predictableActionArguments: true,
|
|
tsTypes: {} as import("./createWorkspaceXService.typegen").Typegen0,
|
|
schema: {
|
|
context: {} as CreateWorkspaceContext,
|
|
events: {} as
|
|
| CreateWorkspaceEvent
|
|
| SelectOwnerEvent
|
|
| RefreshGitAuthEvent,
|
|
services: {} as {
|
|
getTemplates: {
|
|
data: Template[]
|
|
}
|
|
getTemplateGitAuth: {
|
|
data: TemplateVersionGitAuth[]
|
|
}
|
|
getTemplateParameters: {
|
|
data: TemplateVersionParameter[]
|
|
}
|
|
getTemplateSchema: {
|
|
data: ParameterSchema[]
|
|
}
|
|
createWorkspace: {
|
|
data: Workspace
|
|
}
|
|
},
|
|
},
|
|
initial: "gettingTemplates",
|
|
states: {
|
|
gettingTemplates: {
|
|
entry: "clearGetTemplatesError",
|
|
invoke: {
|
|
src: "getTemplates",
|
|
onDone: [
|
|
{
|
|
actions: ["assignTemplates"],
|
|
cond: "areTemplatesEmpty",
|
|
},
|
|
{
|
|
actions: ["assignTemplates", "assignSelectedTemplate"],
|
|
target: "gettingTemplateSchema",
|
|
},
|
|
],
|
|
onError: {
|
|
actions: ["assignGetTemplatesError"],
|
|
target: "error",
|
|
},
|
|
},
|
|
},
|
|
gettingTemplateSchema: {
|
|
entry: "clearGetTemplateSchemaError",
|
|
invoke: {
|
|
src: "getTemplateSchema",
|
|
onDone: {
|
|
actions: ["assignTemplateSchema"],
|
|
target: "gettingTemplateParameters",
|
|
},
|
|
onError: {
|
|
actions: ["assignGetTemplateSchemaError"],
|
|
target: "error",
|
|
},
|
|
},
|
|
},
|
|
gettingTemplateParameters: {
|
|
entry: "clearGetTemplateParametersError",
|
|
invoke: {
|
|
src: "getTemplateParameters",
|
|
onDone: {
|
|
actions: ["assignTemplateParameters"],
|
|
target: "checkingPermissions",
|
|
},
|
|
onError: {
|
|
actions: ["assignGetTemplateParametersError"],
|
|
target: "error",
|
|
},
|
|
},
|
|
},
|
|
checkingPermissions: {
|
|
entry: "clearCheckPermissionsError",
|
|
invoke: {
|
|
src: "checkPermissions",
|
|
id: "checkPermissions",
|
|
onDone: {
|
|
actions: "assignPermissions",
|
|
target: "gettingTemplateGitAuth",
|
|
},
|
|
onError: {
|
|
actions: ["assignCheckPermissionsError"],
|
|
},
|
|
},
|
|
},
|
|
gettingTemplateGitAuth: {
|
|
entry: "clearTemplateGitAuthError",
|
|
invoke: {
|
|
src: "getTemplateGitAuth",
|
|
onDone: {
|
|
actions: ["assignTemplateGitAuth"],
|
|
target: "fillingParams",
|
|
},
|
|
onError: {
|
|
actions: ["assignTemplateGitAuthError"],
|
|
target: "error",
|
|
},
|
|
},
|
|
},
|
|
fillingParams: {
|
|
invoke: {
|
|
id: "listenForRefreshGitAuth",
|
|
src: () => (callback) => {
|
|
// eslint-disable-next-line compat/compat -- It actually is supported... not sure why eslint is complaining.
|
|
const bc = new BroadcastChannel(REFRESH_GITAUTH_BROADCAST_CHANNEL)
|
|
bc.addEventListener("message", () => {
|
|
callback("REFRESH_GITAUTH")
|
|
})
|
|
return () => bc.close()
|
|
},
|
|
},
|
|
on: {
|
|
CREATE_WORKSPACE: {
|
|
actions: ["assignCreateWorkspaceRequest", "assignOwner"],
|
|
target: "creatingWorkspace",
|
|
},
|
|
SELECT_OWNER: {
|
|
actions: ["assignOwner"],
|
|
target: ["fillingParams"],
|
|
},
|
|
REFRESH_GITAUTH: {
|
|
target: "gettingTemplateGitAuth",
|
|
},
|
|
},
|
|
},
|
|
creatingWorkspace: {
|
|
entry: "clearCreateWorkspaceError",
|
|
invoke: {
|
|
src: "createWorkspace",
|
|
onDone: {
|
|
actions: ["onCreateWorkspace"],
|
|
target: "created",
|
|
},
|
|
onError: {
|
|
actions: ["assignCreateWorkspaceError"],
|
|
target: "fillingParams",
|
|
},
|
|
},
|
|
},
|
|
created: {
|
|
type: "final",
|
|
},
|
|
error: {},
|
|
},
|
|
},
|
|
{
|
|
services: {
|
|
getTemplates: (context) => getTemplates(context.organizationId),
|
|
getTemplateGitAuth: (context) => {
|
|
const { selectedTemplate } = context
|
|
|
|
if (!selectedTemplate) {
|
|
throw new Error("No selected template")
|
|
}
|
|
|
|
return getTemplateVersionGitAuth(selectedTemplate.active_version_id)
|
|
},
|
|
getTemplateParameters: (context) => {
|
|
const { selectedTemplate } = context
|
|
|
|
if (!selectedTemplate) {
|
|
throw new Error("No selected template")
|
|
}
|
|
|
|
return getTemplateVersionRichParameters(
|
|
selectedTemplate.active_version_id,
|
|
)
|
|
},
|
|
getTemplateSchema: (context) => {
|
|
const { selectedTemplate } = context
|
|
|
|
if (!selectedTemplate) {
|
|
throw new Error("No selected template")
|
|
}
|
|
|
|
return getTemplateVersionSchema(selectedTemplate.active_version_id)
|
|
},
|
|
checkPermissions: async (context) => {
|
|
if (!context.organizationId) {
|
|
throw new Error("No organization ID")
|
|
}
|
|
|
|
// HACK: below, we pass in * for the owner_id, which is a hacky way of checking if the
|
|
// current user can create a workspace on behalf of anyone within the org (only org owners should be able to do this).
|
|
// This pattern should not be replicated outside of this narrow use case.
|
|
const permissionsToCheck = {
|
|
createWorkspaceForUser: {
|
|
object: {
|
|
resource_type: "workspace",
|
|
organization_id: `${context.organizationId}`,
|
|
owner_id: "*",
|
|
},
|
|
action: "create",
|
|
},
|
|
} as const
|
|
|
|
return checkAuthorization({
|
|
checks: permissionsToCheck,
|
|
})
|
|
},
|
|
createWorkspace: (context) => {
|
|
const { createWorkspaceRequest, organizationId, owner } = context
|
|
|
|
if (!createWorkspaceRequest) {
|
|
throw new Error("No create workspace request")
|
|
}
|
|
|
|
return createWorkspace(
|
|
organizationId,
|
|
owner?.id ?? "me",
|
|
createWorkspaceRequest,
|
|
)
|
|
},
|
|
},
|
|
guards: {
|
|
areTemplatesEmpty: (_, event) => event.data.length === 0,
|
|
},
|
|
actions: {
|
|
assignTemplates: assign({
|
|
templates: (_, event) => event.data,
|
|
}),
|
|
assignSelectedTemplate: assign({
|
|
selectedTemplate: (ctx, event) => {
|
|
const templates = event.data.filter(
|
|
(template) => template.name === ctx.templateName,
|
|
)
|
|
return templates.length > 0 ? templates[0] : undefined
|
|
},
|
|
}),
|
|
assignTemplateParameters: assign({
|
|
templateParameters: (_, event) => event.data,
|
|
}),
|
|
assignTemplateSchema: assign({
|
|
// Only show parameters that are allowed to be overridden.
|
|
// CLI code: https://github.com/coder/coder/blob/main/cli/create.go#L152-L155
|
|
templateSchema: (_, event) => event.data,
|
|
}),
|
|
assignPermissions: assign({
|
|
permissions: (_, event) => event.data as Record<string, boolean>,
|
|
}),
|
|
assignCheckPermissionsError: assign({
|
|
checkPermissionsError: (_, event) => event.data,
|
|
}),
|
|
clearCheckPermissionsError: assign({
|
|
checkPermissionsError: (_) => undefined,
|
|
}),
|
|
assignCreateWorkspaceRequest: assign({
|
|
createWorkspaceRequest: (_, event) => event.request,
|
|
}),
|
|
assignOwner: assign({
|
|
owner: (_, event) => event.owner,
|
|
}),
|
|
assignCreateWorkspaceError: assign({
|
|
createWorkspaceError: (_, event) => event.data,
|
|
}),
|
|
clearCreateWorkspaceError: assign({
|
|
createWorkspaceError: (_) => undefined,
|
|
}),
|
|
assignGetTemplatesError: assign({
|
|
getTemplatesError: (_, event) => event.data,
|
|
}),
|
|
clearGetTemplatesError: assign({
|
|
getTemplatesError: (_) => undefined,
|
|
}),
|
|
assignGetTemplateParametersError: assign({
|
|
getTemplateParametersError: (_, event) => event.data,
|
|
}),
|
|
clearGetTemplateParametersError: assign({
|
|
getTemplateParametersError: (_) => undefined,
|
|
}),
|
|
assignGetTemplateSchemaError: assign({
|
|
getTemplateSchemaError: (_, event) => event.data,
|
|
}),
|
|
clearGetTemplateSchemaError: assign({
|
|
getTemplateSchemaError: (_) => undefined,
|
|
}),
|
|
clearTemplateGitAuthError: assign({
|
|
getTemplateGitAuthError: (_) => undefined,
|
|
}),
|
|
assignTemplateGitAuthError: assign({
|
|
getTemplateGitAuthError: (_, event) => event.data,
|
|
}),
|
|
assignTemplateGitAuth: assign({
|
|
templateGitAuth: (_, event) => event.data,
|
|
}),
|
|
},
|
|
},
|
|
)
|