diff --git a/src/components/Wizard/CreateImageWizard.js b/src/components/Wizard/CreateImageWizard.js index 6d9776179..66a09e683 100644 --- a/src/components/Wizard/CreateImageWizard.js +++ b/src/components/Wizard/CreateImageWizard.js @@ -14,6 +14,7 @@ import { vmwareDest, ociAuth, ociDest, + gcp, ostreeSettings, review, } from "../../forms/steps"; @@ -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"; @@ -142,6 +144,19 @@ const CreateImageWizard = (props) => { tenancy: formValues.image.upload.settings.tenancy, }, }; + } else if (formValues?.image?.type === "gce") { + const credentialsEncoded = window.btoa( + formValues.image.upload.settings.credentials + ); + uploadSettings = { + image_name: formValues.image.upload.image_name, + provider: "gcp", + settings: { + region: formValues.image.upload.settings.region, + bucket: formValues.image.upload.settings.bucket, + credentials: credentialsEncoded, + }, + }; } } @@ -208,6 +223,7 @@ const CreateImageWizard = (props) => { awsDest(intl), azureAuth(intl), azureDest(intl), + gcp(intl), ociAuth(intl), ociDest(intl), vmwareAuth(intl), @@ -244,6 +260,7 @@ const CreateImageWizard = (props) => { blueprintNames: blueprintNames, }, "blueprint-listener": BlueprintListenerWrapper, + "upload-file": UploadFile, }} onCancel={handleClose} /> diff --git a/src/constants.js b/src/constants.js index 6d414d96f..214215423 100644 --- a/src/constants.js +++ b/src/constants.js @@ -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 (.tar.gz)", }; export const UNIT_KIB = 1024 ** 1; diff --git a/src/forms/steps/gcp.js b/src/forms/steps/gcp.js new file mode 100644 index 000000000..3f0f57172 --- /dev/null +++ b/src/forms/steps/gcp.js @@ -0,0 +1,144 @@ +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", + }, + credentialsPopoverBody: { + defaultMessage: + "The credentials file is a 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: , + name: "gcp", + nextStep: "review", + fields: [ + { + component: "text-field-custom", + name: "image.upload.image_name", + className: "pf-u-w-50", + type: "text", + label: , + labelIcon: ( + + + + ), + 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: , + labelIcon: ( + + + + ), + isRequired: true, + validate: [ + { + type: validatorTypes.REQUIRED, + }, + ], + }, + { + component: "text-field-custom", + name: "image.upload.settings.bucket", + className: "pf-u-w-50", + type: "text", + label: , + labelIcon: ( + + + + ), + isRequired: true, + validate: [ + { + type: validatorTypes.REQUIRED, + }, + ], + }, + { + component: "upload-file", + name: "image.upload.settings.credentials", + className: "pf-u-w-50", + type: "text", + label: , + labelIcon: ( + + + + ), + isRequired: true, + validate: [ + { + type: validatorTypes.REQUIRED, + }, + ], + }, + ], + }; +}; + +export default gcp; diff --git a/src/forms/steps/imageOutput.js b/src/forms/steps/imageOutput.js index b9e3e43c5..8607ef1fb 100644 --- a/src/forms/steps/imageOutput.js +++ b/src/forms/steps/imageOutput.js @@ -222,6 +222,15 @@ const imageOutput = (intl) => { is: "vhd", }, }, + { + name: "image.isUpload", + component: componentTypes.CHECKBOX, + label: , + condition: { + when: "image.type", + is: "gce", + }, + }, { name: "image.isUpload", component: componentTypes.CHECKBOX, diff --git a/src/forms/steps/imageOutputStepMapper.js b/src/forms/steps/imageOutputStepMapper.js index aa742415a..a8213ada9 100644 --- a/src/forms/steps/imageOutputStepMapper.js +++ b/src/forms/steps/imageOutputStepMapper.js @@ -9,6 +9,8 @@ export default (props) => { return "azure-auth"; case "vmdk": return "vmware-auth"; + case "gce": + return "gcp"; default: return "review"; } diff --git a/src/forms/steps/index.js b/src/forms/steps/index.js index b09997807..46a30bfa0 100644 --- a/src/forms/steps/index.js +++ b/src/forms/steps/index.js @@ -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"; diff --git a/test/verify/check-imageWizard b/test/verify/check-imageWizard index 965e51b3c..e31ffa3d8 100755 --- a/test/verify/check-imageWizard +++ b/test/verify/check-imageWizard @@ -3,6 +3,8 @@ import time import composerlib import testlib +import unittest +import os @testlib.nondestructive @@ -33,6 +35,30 @@ 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.wait_in_text(".pf-c-wizard__main-body", "Credentials") + + # Cancel upload + b.click("footer button:contains('Cancel')") + if __name__ == '__main__': testlib.test_main() diff --git a/translations/en.json b/translations/en.json index cf1d2c6bc..bb0b7847f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -3,6 +3,7 @@ "+YJ/Sw": "Append", "+ubJEs": "Manufacturing server URL", "/9I/o9": "Add partition", + "/Ktg9J": "GCP", "/VnDMl": "Other", "/f2U0m": "{blueprint} Image creation is {queue}.", "/nwCxv": "File system configurations", @@ -34,6 +35,7 @@ "9+Ddtu": "Next", "96NKhh": "Add zone", "9OVLWd": "Filesystem info", + "9RX5kq": "Provide a file name to be used for the image file that will be uploaded.", "9XUYQt": "Import", "9fE5XX": "No root partition configured.", "9llaed": "Download logs", @@ -58,6 +60,7 @@ "GsBRWL": "Languages", "HAlOn1": "Name", "HIQslE": "Image output", + "HLZ9CC": "Credentials", "I3b4hn": "Version", "IHusTg": "Firstboot URL", "IMpl6/": "Secret access key", @@ -78,6 +81,7 @@ "M8FAMU": "Manually configure partitions", "MKV/jm": "Image Builder may extend this size based on requirements, selected packages, and configurations.", "N26JOi": "{blueprint} Image creation has been added to the {queue}.", + "N9tOr0": "Bucket help", "NNkgEU": "Source names cannot contain spaces.", "NOOrvy": "Zone sources", "ORGv1Q": "Created", @@ -96,6 +100,7 @@ "RcxFT1": "Locale", "Ry6SwO": "Disabled Services", "SNzHd8": "Remove key", + "Sb1B69": "Storage region", "TADV5r": "Are you sure you want to stop the image build?", "TLiG9d": "NTP Servers", "TP/O/b": "Remove user", @@ -109,20 +114,24 @@ "Ucrl50": "Disabled services", "V52jNn": "Enabled", "VC1pOq": "Add locale", + "VH3Xt/": "Bucket", "VcpIj8": "No blueprints", "Vp6WM3": "Mount point", "VzWTJu": "Blueprints", "VzzYJk": "Create", "WWUaVx": "Enter kernel name.", "WyriYH": "Storage container", + "Wz9Z1Z": "Credentials help", "XZCsHB": "Upload to VMWare", "Xe56d/": "Bucket region", + "XsPqPW": "Provide the region where the bucket is located. This region can be a regular Google storage region, but also a dual or multi region.", "XyfAvU": "Mirrorlist", "YDMrKK": "Users", "YIf1rr": "Image ID", "YMk8tG": "Enter valid device node such as /dev/sda1. Only used for the simplified-installer image type.", "YeKWbP": "Authentication", "ZuzLh4": "Blueprints table", + "ZvnTwM": "Provide the name of the bucket where the image will be uploaded. This bucket must already exist.", "aQ7PKk": "Manufacturing Server URL", "agOXPD": "Size", "ax75cZ": "Minimum size", @@ -139,10 +148,12 @@ "eALudX": "Add services", "eKEL/g": "Pending", "f3funQ": "Installation device", + "f7FU1f": "Image name help", "fE1QI4": "User OCID", "fhwTpA": "Sources", "gL86bv": "Group name", "gP2Vek": "Enter values", + "gQL2CA": "Region help", "guBlw5": "NTP servers", "hpiPhh": "Enter kernel commandline arguments.", "hzmswI": "Groups", @@ -156,6 +167,7 @@ "kPQ+0U": "A source with this name already exists.", "kkxJBT": "Create image", "l8uwaa": "Configure partitions", + "lX0x2F": "The credentials file is a JSON file downloaded from GCP. The credentials are used to determine the GCP project to upload the image to.", "leet9B": "Yum repository", "m+E44N": "No packages selected", "mA1RDm": "Edit source", @@ -187,6 +199,7 @@ "tthToS": "Disabled", "tzMNF3": "Status", "uPDgth": "Hostname", + "uRdfZs": "Upload to GCP", "vAPLDS": "Toggle which package list to show", "vXCeIi": "Failed", "vr2saa": "{blueprint} Image creation failed.",