diff --git a/packages/api-v4/src/images/images.ts b/packages/api-v4/src/images/images.ts index 2f52d2910f8..3b8973a27a2 100644 --- a/packages/api-v4/src/images/images.ts +++ b/packages/api-v4/src/images/images.ts @@ -40,16 +40,19 @@ export const getImages = (params: any = {}, filters: any = {}) => * @param diskId { number } The ID of the Linode Disk that this Image will be created from. * @param label { string } A short description of the Image. Labels cannot contain special characters. * @param description { string } A detailed description of this Image. + * @param cloud_init { boolean } An indicator of whether Image supports cloud-init. */ export const createImage = ( diskId: number, label?: string, - description?: string + description?: string, + cloud_init?: boolean ) => { const data = { disk_id: diskId, ...(label && { label }), ...(description && { description }), + ...(cloud_init && { cloud_init }), }; return Request( diff --git a/packages/api-v4/src/images/types.ts b/packages/api-v4/src/images/types.ts index 39c292ae259..b2fcf7f6d35 100644 --- a/packages/api-v4/src/images/types.ts +++ b/packages/api-v4/src/images/types.ts @@ -29,6 +29,7 @@ export interface UploadImageResponse { export interface BaseImagePayload { label?: string; description?: string; + cloud_init?: boolean; } export interface CreateImagePayload extends BaseImagePayload { diff --git a/packages/manager/src/components/CheckBox/CheckBox.tsx b/packages/manager/src/components/CheckBox/CheckBox.tsx index e40ab9037ea..bb9823bff90 100644 --- a/packages/manager/src/components/CheckBox/CheckBox.tsx +++ b/packages/manager/src/components/CheckBox/CheckBox.tsx @@ -30,15 +30,19 @@ const useStyles = makeStyles((theme: Theme) => ({ fill: `${theme.bg.main}`, }, }, + formControlStyles: { + marginRight: 0, + }, })); interface Props extends CheckboxProps { text?: string | JSX.Element; - toolTipText?: string; + toolTipText?: string | JSX.Element; + toolTipInteractive?: boolean; } -const LinodeCheckBox: React.FC = (props) => { - const { toolTipText, text, ...rest } = props; +const LinodeCheckBox = (props: Props) => { + const { toolTipInteractive, toolTipText, text, ...rest } = props; const classes = useStyles(); const classnames = classNames({ @@ -51,6 +55,7 @@ const LinodeCheckBox: React.FC = (props) => { return ( = (props) => { {...rest} /> } - label={props.text} + label={text} /> - {toolTipText && } + {toolTipText ? ( + + ) : null} ); } diff --git a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx index 9bf5cca0a5e..134f4e806a4 100644 --- a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx +++ b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx @@ -23,6 +23,7 @@ import { useGrants, useProfile } from 'src/queries/profile'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; import getAPIErrorFor from 'src/utilities/getAPIErrorFor'; import Link from 'src/components/Link'; +import CheckBox from 'src/components/CheckBox'; const useStyles = makeStyles((theme: Theme) => ({ container: { @@ -57,21 +58,37 @@ const useStyles = makeStyles((theme: Theme) => ({ width: '100%', }, }, + cloudInitCheckboxWrapper: { + marginTop: theme.spacing(2), + marginLeft: 3, + }, })); +const cloudInitTooltipMessage = ( + + Many Linode supported distributions are compatible with cloud-init by + default, or you may have installed cloud-init.{' '} + Learn more. + +); + export interface Props { label?: string; description?: string; + isCloudInit?: boolean; changeLabel: (e: React.ChangeEvent) => void; changeDescription: (e: React.ChangeEvent) => void; + changeIsCloudInit: (e: React.ChangeEvent) => void; } export const CreateImageTab: React.FC = (props) => { const { label, description, + isCloudInit, changeLabel, changeDescription, + changeIsCloudInit, createImage, } = props; @@ -150,6 +167,7 @@ export const CreateImageTab: React.FC = (props) => { diskID: Number(selectedDisk), label, description: safeDescription, + cloud_init: isCloudInit ? isCloudInit : undefined, }) .then((_) => { resetEventsPolling(); @@ -265,6 +283,15 @@ export const CreateImageTab: React.FC = (props) => { /> {isRawDisk ? rawDiskWarning : null} + + + <> = (props) => { const [description, setDescription] = React.useState( location?.state ? location.state.imageDescription : '' ); + const [isCloudInit, setIsCloudInit] = React.useState(false); const handleSetLabel = (e: React.ChangeEvent) => { const value = e.target.value; @@ -37,8 +38,10 @@ export const ImageCreate: React.FC = (props) => { setIsCloudInit(!isCloudInit)} /> ), }, diff --git a/packages/manager/src/store/image/image.requests.ts b/packages/manager/src/store/image/image.requests.ts index 1ee60c661e5..75a8ea1b6ca 100644 --- a/packages/manager/src/store/image/image.requests.ts +++ b/packages/manager/src/store/image/image.requests.ts @@ -29,7 +29,8 @@ export const requestImages = createRequestThunk(requestImagesActions, () => export const createImage = createRequestThunk( createImageActions, - ({ diskID, label, description }) => _create(diskID, label, description) + ({ diskID, label, description, cloud_init }) => + _create(diskID, label, description, cloud_init) ); export const uploadImage = createRequestThunk(uploadImageActions, (payload) => diff --git a/packages/validation/src/images.schema.ts b/packages/validation/src/images.schema.ts index 921017a6b27..5ff64c8ec49 100644 --- a/packages/validation/src/images.schema.ts +++ b/packages/validation/src/images.schema.ts @@ -1,4 +1,4 @@ -import { number, object, string } from 'yup'; +import { boolean, number, object, string } from 'yup'; const labelSchema = string() .max(50, 'Length must be 50 characters or less.') @@ -10,6 +10,7 @@ const labelSchema = string() export const baseImageSchema = object().shape({ label: labelSchema.notRequired(), description: string().notRequired().min(1).max(65000), + cloud_init: boolean().notRequired(), }); export const createImageSchema = baseImageSchema.shape({