feat: add orphan support (#3849)

* feat: add resource orphanage

* feat: deny custom state in build for regular users

* Minor protoc improvements
This commit is contained in:
Ammar Bandukwala
2022-09-06 17:07:00 +00:00
committed by GitHub
parent 209e011404
commit 4f0105ef7e
16 changed files with 334 additions and 51 deletions
+1
View File
@@ -170,6 +170,7 @@ export interface CreateWorkspaceBuildRequest {
readonly transition: WorkspaceTransition
readonly dry_run?: boolean
readonly state?: string
readonly orphan?: boolean
readonly parameter_values?: CreateParameterRequest[]
}
+26 -18
View File
@@ -1,4 +1,4 @@
import { makeStyles } from "@material-ui/core/styles"
import { makeStyles, Theme } from "@material-ui/core/styles"
import { FC } from "react"
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
import { combineClasses } from "../../util/combineClasses"
@@ -9,6 +9,7 @@ export interface CodeExampleProps {
className?: string
buttonClassName?: string
tooltipTitle?: string
inline?: boolean
}
/**
@@ -19,8 +20,9 @@ export const CodeExample: FC<React.PropsWithChildren<CodeExampleProps>> = ({
className,
buttonClassName,
tooltipTitle,
inline,
}) => {
const styles = useStyles()
const styles = useStyles({ inline: inline })
return (
<div className={combineClasses([styles.root, className])}>
@@ -34,35 +36,41 @@ export const CodeExample: FC<React.PropsWithChildren<CodeExampleProps>> = ({
)
}
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
interface styleProps {
inline?: boolean
}
const useStyles = makeStyles<Theme, styleProps>((theme) => ({
root: (props) => ({
display: props.inline ? "inline-flex" : "flex",
flexDirection: "row",
alignItems: "center",
background: "hsl(223, 27%, 3%)",
border: `1px solid ${theme.palette.divider}`,
background: props.inline ? "rgb(0 0 0 / 30%)" : "hsl(223, 27%, 3%)",
border: props.inline ? undefined : `1px solid ${theme.palette.divider}`,
color: theme.palette.primary.contrastText,
fontFamily: MONOSPACE_FONT_FAMILY,
fontSize: 14,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(0.5),
},
code: {
padding: props.inline ? "0px" : theme.spacing(0.5),
}),
code: (props) => ({
padding: `
${theme.spacing(0.5)}px
${props.inline ? 0 : theme.spacing(0.5)}px
${theme.spacing(0.75)}px
${theme.spacing(0.5)}px
${theme.spacing(2)}px
${props.inline ? 0 : theme.spacing(0.5)}px
${props.inline ? theme.spacing(1) : theme.spacing(2)}px
`,
width: "100%",
display: "flex",
alignItems: "center",
wordBreak: "break-all",
},
button: {
}),
button: (props) => ({
border: 0,
minWidth: 42,
minHeight: 42,
minWidth: props.inline ? 30 : 42,
minHeight: props.inline ? 30 : 42,
borderRadius: theme.shape.borderRadius,
},
padding: props.inline ? theme.spacing(0.4) : undefined,
background: "transparent",
}),
}))
@@ -86,7 +86,6 @@ export const CopyButton: React.FC<React.PropsWithChildren<CopyButtonProps>> = ({
const useStyles = makeStyles((theme) => ({
copyButtonWrapper: {
display: "flex",
marginLeft: theme.spacing(1),
},
copyButton: {
borderRadius: theme.shape.borderRadius,
@@ -1,5 +1,9 @@
import { ComponentMeta, Story } from "@storybook/react"
import { MockWorkspaceBuild, MockWorkspaceBuildLogs } from "../../testHelpers/entities"
import {
MockFailedWorkspaceBuild,
MockWorkspaceBuild,
MockWorkspaceBuildLogs,
} from "../../testHelpers/entities"
import { WorkspaceBuildPageView, WorkspaceBuildPageViewProps } from "./WorkspaceBuildPageView"
export default {
@@ -13,5 +17,10 @@ export const Example = Template.bind({})
Example.args = {
build: MockWorkspaceBuild,
logs: MockWorkspaceBuildLogs,
isWaitingForLogs: false,
}
export const FailedDelete = Template.bind({})
FailedDelete.args = {
build: MockFailedWorkspaceBuild("delete"),
logs: MockWorkspaceBuildLogs,
}
@@ -6,6 +6,7 @@ import { PageHeader, PageHeaderTitle } from "../../components/PageHeader/PageHea
import { Stack } from "../../components/Stack/Stack"
import { WorkspaceBuildLogs } from "../../components/WorkspaceBuildLogs/WorkspaceBuildLogs"
import { WorkspaceBuildStats } from "../../components/WorkspaceBuildStats/WorkspaceBuildStats"
import { WorkspaceBuildStateError } from "./WorkspaceBuildStateError"
const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => {
return [...logs].sort(
@@ -26,6 +27,9 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({ logs,
</PageHeader>
<Stack>
{build && build.transition === "delete" && build.job.status === "failed" && (
<WorkspaceBuildStateError build={build} />
)}
{build && <WorkspaceBuildStats build={build} />}
{!logs && <Loader />}
{logs && <WorkspaceBuildLogs logs={sortLogsByCreatedAt(logs)} />}
@@ -0,0 +1,54 @@
import { makeStyles } from "@material-ui/core/styles"
import { WorkspaceBuild } from "api/typesGenerated"
import { CodeExample } from "components/CodeExample/CodeExample"
import { Stack } from "components/Stack/Stack"
const Language = {
stateMessage: "The workspace may have failed to delete due to a Terraform state mismatch.",
}
export interface WorkspaceBuildStateErrorProps {
build: WorkspaceBuild
}
export const WorkspaceBuildStateError: React.FC<WorkspaceBuildStateErrorProps> = ({ build }) => {
const styles = useStyles()
const orphanCommand = `coder rm ${
build.workspace_owner_name + "/" + build.workspace_name
} --orphan`
return (
<Stack className={styles.root}>
<Stack direction="row" alignItems="center" className={styles.messageBox}>
<Stack direction="row" spacing={0}>
<span className={styles.errorMessage}>
{Language.stateMessage} A template admin may run{" "}
<CodeExample inline code={orphanCommand} /> to delete the workspace skipping resource
destruction.
</span>
</Stack>
</Stack>
</Stack>
)
}
const useStyles = makeStyles((theme) => ({
root: {
background: theme.palette.warning.main,
padding: `${theme.spacing(2)}px`,
borderRadius: theme.shape.borderRadius,
gap: 0,
},
flex: {
display: "flex",
},
messageBox: {
justifyContent: "space-between",
},
errorMessage: {
marginRight: `${theme.spacing(1)}px`,
},
iconButton: {
padding: 0,
},
}))
+22
View File
@@ -132,6 +132,7 @@ export const MockFailedProvisionerJob: TypesGen.ProvisionerJob = {
...MockProvisionerJob,
status: "failed",
}
export const MockCancelingProvisionerJob: TypesGen.ProvisionerJob = {
...MockProvisionerJob,
status: "canceling",
@@ -212,6 +213,27 @@ export const MockWorkspaceBuild: TypesGen.WorkspaceBuild = {
reason: "initiator",
}
export const MockFailedWorkspaceBuild = (
transition: TypesGen.WorkspaceTransition = "start",
): TypesGen.WorkspaceBuild => ({
build_number: 1,
created_at: "2022-05-17T17:39:01.382927298Z",
id: "1",
initiator_id: MockUser.id,
initiator_name: MockUser.username,
job: MockFailedProvisionerJob,
name: "a-workspace-build",
template_version_id: "",
transition: transition,
updated_at: "2022-05-17T17:39:01.382927298Z",
workspace_name: "test-workspace",
workspace_owner_id: MockUser.id,
workspace_owner_name: MockUser.username,
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
deadline: "2022-05-17T23:39:00.00Z",
reason: "initiator",
})
export const MockWorkspaceBuildStop: TypesGen.WorkspaceBuild = {
...MockWorkspaceBuild,
id: "2",