diff --git a/src/Components/ImagesTable/ImageDetails.tsx b/src/Components/ImagesTable/ImageDetails.tsx
index d46b7263..809b0394 100644
--- a/src/Components/ImagesTable/ImageDetails.tsx
+++ b/src/Components/ImagesTable/ImageDetails.tsx
@@ -119,7 +119,7 @@ const AwsSourceName = ({ id }: AwsSourceNamePropTypes) => {
return ;
};
-const parseGcpSharedWith = (
+export const parseGcpSharedWith = (
sharedWith: GcpUploadRequestOptions['share_with_accounts'],
) => {
if (sharedWith) {
diff --git a/src/Components/ImagesTable/Instance.tsx b/src/Components/ImagesTable/Instance.tsx
index c876a6a5..4af6f5e9 100644
--- a/src/Components/ImagesTable/Instance.tsx
+++ b/src/Components/ImagesTable/Instance.tsx
@@ -57,6 +57,7 @@ import { resolveRelPath } from '../../Utilities/path';
import { useFlag } from '../../Utilities/useGetEnvironment';
import useProvisioningPermissions from '../../Utilities/useProvisioningPermissions';
import { AWSLaunchModal } from '../Launch/AWSLaunchModal';
+import { GcpLaunchModal } from '../Launch/GcpLaunchModal';
type CloudInstancePropTypes = {
compose: ComposesResponseItem;
@@ -224,6 +225,14 @@ const ProvisioningLink = ({
composeStatus={composeStatus}
/>
)}
+ {launchEofFlag && isModalOpen && provider === 'gcp' && (
+
+ )}
{!launchEofFlag && isModalOpen && (
void;
+ compose: ComposesResponseItem;
+ composeStatus: ComposeStatus | undefined;
+};
+
+export const GcpLaunchModal = ({
+ isOpen,
+ handleModalToggle,
+ compose,
+ composeStatus,
+}: LaunchProps) => {
+ const [customerProjectId, setCustomerProjectId] = useState('');
+
+ const statusOptions = composeStatus?.image_status.upload_status?.options;
+ const composeOptions =
+ compose.request.image_requests[0].upload_request.options;
+
+ if (
+ (statusOptions && !isGcpUploadStatus(statusOptions)) ||
+ !isGcpUploadRequestOptions(composeOptions)
+ ) {
+ throw TypeError(
+ `Error: options must be of type GcpUploadRequestOptions, not ${typeof statusOptions}.`,
+ );
+ }
+
+ const imageName = statusOptions?.image_name;
+ const projectId = statusOptions?.project_id;
+ if (!imageName || !projectId) {
+ throw TypeError(
+ `Error: Image name not found, unable to generate a command to copy ${typeof statusOptions}.`,
+ );
+ }
+ const uniqueImageName = generateDefaultName(imageName);
+ const authorizeString =
+ composeOptions.share_with_accounts &&
+ composeOptions.share_with_accounts.length === 1
+ ? `Authorize gcloud CLI to the following
+ account: ${parseGcpSharedWith(composeOptions.share_with_accounts)}.`
+ : composeOptions.share_with_accounts
+ ? `Authorize gcloud CLI to use one of the following
+ accounts: ${parseGcpSharedWith(composeOptions.share_with_accounts)}.`
+ : 'Authorize gcloud CLI to use the account that the image is shared with.';
+ const installationCommand = `sudo dnf install google-cloud-cli`;
+ const createImage = `gcloud compute images create ${uniqueImageName} --source-image=${imageName} --source-image-project=${projectId} --project=${
+ customerProjectId || ''
+ }`;
+ const createInstance = `gcloud compute instances create ${uniqueImageName} --image=${uniqueImageName} --project=${
+ customerProjectId || ''
+ }`;
+ return (
+
+
+
+
+
+ Install the gcloud CLI. See the{' '}
+ }
+ iconPosition='right'
+ href={`https://cloud.google.com/sdk/docs/install`}
+ className='pf-v6-u-pl-0'
+ >
+ Install gcloud CLI
+
+ documentation.
+
+ {installationCommand}
+
+
+ {authorizeString}
+
+ Enter your GCP project ID, and run the command to create the image
+ in your project.
+ setCustomerProjectId(value)}
+ aria-label='Project ID input'
+ placeholder='Project ID'
+ />
+
+ {createImage}
+
+
+
+ Create an instance of your image by either accessing the{' '}
+ }
+ iconPosition='right'
+ href={`https://console.cloud.google.com/compute/images`}
+ className='pf-v6-u-pl-0'
+ >
+ GCP console
+ {' '}
+ or by running the following command:
+
+ {createInstance}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/Components/Launch/useGenerateDefaultName.ts b/src/Components/Launch/useGenerateDefaultName.ts
new file mode 100644
index 00000000..ac7a2cc6
--- /dev/null
+++ b/src/Components/Launch/useGenerateDefaultName.ts
@@ -0,0 +1,34 @@
+export const generateDefaultName = (imageName: string) => {
+ const date = new Date();
+ const day = date.getDate().toString().padStart(2, '0');
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
+ const year = date.getFullYear().toString();
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+
+ const dateTimeString = `${month}${day}${year}-${hours}${minutes}`;
+
+ // gcloud images are valid in the form of: (?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)
+ let newBlueprintName = imageName
+ .toLowerCase()
+ .replace(/[^a-z0-9-]/g, '-')
+ .replace(/-{2,}/g, '-')
+ .replace(/^-+|-+$/g, '');
+
+ if (!/^[a-z]/.test(newBlueprintName)) {
+ newBlueprintName = 'i' + newBlueprintName;
+ }
+
+ const maxLength = 63;
+ const uniquePartLength = dateTimeString.length + 1;
+ const baseNameMaxLength = maxLength - uniquePartLength;
+ if (newBlueprintName.length > baseNameMaxLength) {
+ newBlueprintName = newBlueprintName.substring(0, baseNameMaxLength);
+ }
+
+ while (newBlueprintName.endsWith('-')) {
+ newBlueprintName = newBlueprintName.slice(0, -1);
+ }
+
+ return `${newBlueprintName}-${dateTimeString}`;
+};