Files
coder/site/src/xServices/createWorkspace/createWorkspaceXService.ts
T

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,
}),
},
},
)