Skip to content

Commit

Permalink
ImageWizard: add gcp upload
Browse files Browse the repository at this point in the history
GCE images can now be created and uploaded to gcp. This adds new upload
fields for the Storage region, Bucket, Object key, and Credentials. The
credentials should be a file upload.

This is all only on RHEL.
  • Loading branch information
jkozol committed Jan 30, 2023
1 parent 370fff2 commit 069f152
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/components/Wizard/CreateImageWizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
vmwareDest,
ociAuth,
ociDest,
gcp,
ostreeSettings,
review,
} from "../../forms/steps";
Expand All @@ -33,6 +34,7 @@ import Packages from "../../forms/components/Packages";
import Review from "../../forms/components/Review";
import TextFieldCustom from "../../forms/components/TextFieldCustom";
import UploadOCIFile from "../../forms/components/UploadOCIFile";
import UploadFile from "../../forms/components/UploadFile";
import BlueprintSelect from "../../forms/components/BlueprintSelect";
import { FormSpy, useFormApi } from "@data-driven-forms/react-form-renderer";

Expand Down Expand Up @@ -142,6 +144,17 @@ const CreateImageWizard = (props) => {
tenancy: formValues.image.upload.settings.tenancy,
},
};
} else if (formValues?.image?.type === "gce") {
uploadSettings = {
image_name: formValues.image.upload.image_name,
provider: "gcp",
settings: {
region: formValues.image.upload.settings.region,
bucket: formValues.image.upload.settings.bucket,
object: formValues.image.upload.settings.object,
credentials: formValues.image.upload.settings.credentials,
},
};
}
}

Expand Down Expand Up @@ -208,6 +221,7 @@ const CreateImageWizard = (props) => {
awsDest(intl),
azureAuth(intl),
azureDest(intl),
gcp(intl),
ociAuth(intl),
ociDest(intl),
vmwareAuth(intl),
Expand Down Expand Up @@ -244,6 +258,7 @@ const CreateImageWizard = (props) => {
blueprintNames: blueprintNames,
},
"blueprint-listener": BlueprintListenerWrapper,
"upload-file": UploadFile,
}}
onCancel={handleClose}
/>
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const ImageTypeLabels = {
"edge-simplified-installer": "RHEL for Edge Simplified Installer (.iso)",
vhd: "Microsoft Azure (.vhd)",
vmdk: "VMWare VSphere (.vmdk)",
gce: "Google Cloud Platform (.gce)",
};

export const UNIT_KIB = 1024 ** 1;
Expand Down
174 changes: 174 additions & 0 deletions src/forms/steps/gcp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React from "react";
import { defineMessages, FormattedMessage } from "react-intl";
import validatorTypes from "@data-driven-forms/react-form-renderer/validator-types";
import { Popover, Button } from "@patternfly/react-core";
import { HelpIcon } from "@patternfly/react-icons";

const messages = defineMessages({
imageNamePopoverBody: {
defaultMessage:
"Provide a file name to be used for the image file that will be uploaded.",
},
imageNamePopoverAria: {
defaultMessage: "Image name help",
},
bucketPopoverBody: {
defaultMessage:
"Provide the name of the bucket where the image will be uploaded. This bucket must already exist.",
},
bucketPopoverAria: {
defaultMessage: "Bucket help",
},
regionPopoverBody: {
defaultMessage:
"Provide the region where the bucket is located. This region can be a regular Google storage region, but also a dual or multi region.",
},
regionPopoverAria: {
defaultMessage: "Region help",
},
objectPopoverBody: {
defaultMessage:
"The object is the name of an intermediate storage object. It must not exist before the upload, and it will be deleted when the upload process is done. If the object name does not end with .tar.gz, the extension is automatically added to the object name.",
},
objectPopoverAria: {
defaultMessage: "Object help",
},
credentialsPopoverBody: {
defaultMessage:
"The credentials file is a Base64 version of the JSON file downloaded from GCP. The credentials are used to determine the GCP project to upload the image to.",
},
credentialsPopoverAria: {
defaultMessage: "Credentials help",
},
});

const gcp = (intl) => {
return {
title: <FormattedMessage defaultMessage="GCP" />,
name: "gcp",
nextStep: "review",
fields: [
{
component: "text-field-custom",
name: "image.upload.image_name",
className: "pf-u-w-50",
type: "text",
label: <FormattedMessage defaultMessage="Image name" />,
labelIcon: (
<Popover
bodyContent={intl.formatMessage(messages.imageNamePopoverBody)}
aria-label={intl.formatMessage(messages.imageNamePopoverAria)}
>
<Button
variant="plain"
aria-label={intl.formatMessage(messages.imageNamePopoverAria)}
>
<HelpIcon />
</Button>
</Popover>
),
isRequired: true,
autoFocus: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
{
component: "text-field-custom",
name: "image.upload.settings.region",
className: "pf-u-w-50",
type: "text",
label: <FormattedMessage defaultMessage="Storage region" />,
labelIcon: (
<Popover
bodyContent={intl.formatMessage(messages.regionPopoverBody)}
aria-label={intl.formatMessage(messages.regionPopoverAria)}
>
<Button variant="plain">
<HelpIcon />
</Button>
</Popover>
),
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
{
component: "text-field-custom",
name: "image.upload.settings.bucket",
className: "pf-u-w-50",
type: "text",
label: <FormattedMessage defaultMessage="Bucket" />,
labelIcon: (
<Popover
bodyContent={intl.formatMessage(messages.bucketPopoverBody)}
aria-label={intl.formatMessage(messages.bucketPopoverAria)}
>
<Button variant="plain">
<HelpIcon />
</Button>
</Popover>
),
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
{
component: "text-field-custom",
name: "image.upload.settings.object",
className: "pf-u-w-50",
type: "text",
label: <FormattedMessage defaultMessage="Object key" />,
labelIcon: (
<Popover
bodyContent={intl.formatMessage(messages.objectPopoverBody)}
aria-label={intl.formatMessage(messages.objectPopoverAria)}
>
<Button variant="plain">
<HelpIcon />
</Button>
</Popover>
),
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
{
component: "upload-file",
name: "image.upload.settings.credentials",
className: "pf-u-w-50",
type: "text",
label: <FormattedMessage defaultMessage="Credentials" />,
labelIcon: (
<Popover
bodyContent={intl.formatMessage(messages.credentialsPopoverBody)}
aria-label={intl.formatMessage(messages.credentialsPopoverAria)}
>
<Button variant="plain">
<HelpIcon />
</Button>
</Popover>
),
isRequired: true,
validate: [
{
type: validatorTypes.REQUIRED,
},
],
},
],
};
};

export default gcp;
9 changes: 9 additions & 0 deletions src/forms/steps/imageOutput.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ const imageOutput = (intl) => {
is: "vhd",
},
},
{
name: "image.isUpload",
component: componentTypes.CHECKBOX,
label: <FormattedMessage defaultMessage="Upload to GCP" />,
condition: {
when: "image.type",
is: "gce",
},
},
{
name: "image.isUpload",
component: componentTypes.CHECKBOX,
Expand Down
2 changes: 2 additions & 0 deletions src/forms/steps/imageOutputStepMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export default (props) => {
return "azure-auth";
case "vmdk":
return "vmware-auth";
case "gce":
return "gcp";
default:
return "review";
}
Expand Down
1 change: 1 addition & 0 deletions src/forms/steps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export { default as locale } from "./locale";
export { default as other } from "./other";
export { default as openscap } from "./openscap";
export { default as ignition } from "./ignition";
export { default as gcp } from "./gcp";
29 changes: 29 additions & 0 deletions test/verify/check-imageWizard
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import time
import composerlib
import testlib
import unittest
import os


@testlib.nondestructive
Expand Down Expand Up @@ -33,6 +35,33 @@ class TestImageWizard(composerlib.ComposerCase):
# Create image
b.click("footer button:contains('Create')")

@unittest.skipIf(os.environ.get("TEST_OS").split('-')[0] != "rhel", "Skipping test for non RHEL")
def testUploadGCPFields(self):
b = self.browser

self.login_and_go("/composer", superuser=True)
b.wait_visible("#main")

# Create blueprint
b.click("tr[data-testid=httpd-server] button[aria-label='Create image']")
b.wait_in_text(".pf-c-wizard__main", "httpd-server")
time.sleep(1)
# select qcow2 image type and keep default size
b.select_PF4("#image-output-select-toggle", "Google Cloud Platform (.gce)")
b.click("input[id='image.isUpload']")
b.click("button:contains('Next')")

b.set_input_text("input[id='image.upload.image_name']", "testImageName")
b.set_input_text("input[id='image.upload.settings.region']", "testStorageName")
b.set_input_text("input[id='image.upload.settings.bucket']", "testBucket")
b.set_input_text("input[id='image.upload.settings.object']", "testObjKey")
b.wait_in_text(".pf-c-wizard__main-body", "Credentials")

b.click("button:contains('Next')")

# Cancel upload
b.click("footer button:contains('Cancel')")


if __name__ == '__main__':
testlib.test_main()
Loading

0 comments on commit 069f152

Please sign in to comment.