From 398eeb8cb26ac39c96c1fd59031342804ccaed34 Mon Sep 17 00:00:00 2001 From: Sanne Raymaekers Date: Wed, 24 Feb 2021 20:23:43 +0100 Subject: [PATCH] CreateImageWizard: Add Azure upload step --- .../WizardStepImageOutput.js | 3 +- .../CreateImageWizard/WizardStepReview.scss | 4 +- .../WizardStepUploadAzure.js | 77 ++++++++++++++++++ .../WizardStepUploadAzure.scss | 11 +++ .../CreateImageWizard/CreateImageWizard.js | 81 ++++++++++++++++--- .../CreateImageWizard.test.js | 54 +++++++++++++ 6 files changed, 216 insertions(+), 14 deletions(-) create mode 100644 src/PresentationalComponents/CreateImageWizard/WizardStepUploadAzure.js create mode 100644 src/PresentationalComponents/CreateImageWizard/WizardStepUploadAzure.scss diff --git a/src/PresentationalComponents/CreateImageWizard/WizardStepImageOutput.js b/src/PresentationalComponents/CreateImageWizard/WizardStepImageOutput.js index 423482b3..bd33157b 100644 --- a/src/PresentationalComponents/CreateImageWizard/WizardStepImageOutput.js +++ b/src/PresentationalComponents/CreateImageWizard/WizardStepImageOutput.js @@ -44,8 +44,7 @@ const WizardStepImageOutput = (props) => { onClick={ () => props.toggleUploadDestination('azure') } isSelected={ props.uploadDestinations.azure } isStacked - isDisplayLarge - isDisabled /> + isDisplayLarge /> { + return ( + <> + + Target Environment - Upload to Azure + + Image Builder will send an image to an authorized Azure account. + + OAuth permissions + + In order to use Image Builder to push images to Azure, Image Builder must + be configured as an authorized application, and given the role of "Contributor" to at least one resource group.
+ Image Builder must be authorized by an account owner.
+ + Learn more +
+ + + Authorize Image Builder on Azure + +
+ + Destination + + Your image will be uploaded to the resource group in the subscription you specify. + +
+ + + props.setUploadOptions('azure', Object.assign(props.uploadAzure.options, { tenant_id: value })) } /> + + + + props.setUploadOptions('azure', Object.assign(props.uploadAzure.options, { subscription_id: value })) } /> + + + + props.setUploadOptions('azure', Object.assign(props.uploadAzure.options, { resource_group: value })) } /> + +
+ + ); +}; + +WizardStepUploadAzure.propTypes = { + setUploadOptions: PropTypes.func, + uploadAzure: PropTypes.object, + errors: PropTypes.object, +}; + +export default WizardStepUploadAzure; diff --git a/src/PresentationalComponents/CreateImageWizard/WizardStepUploadAzure.scss b/src/PresentationalComponents/CreateImageWizard/WizardStepUploadAzure.scss new file mode 100644 index 00000000..6e93139b --- /dev/null +++ b/src/PresentationalComponents/CreateImageWizard/WizardStepUploadAzure.scss @@ -0,0 +1,11 @@ +.textcontent-azure { + margin-bottom: var(--pf-global--spacer--lg); + h3, h4 { + margin-top: var(--pf-global--spacer--sm); + margin-bottom: var(--pf-global--spacer--xs); + } + p { + margin-bottom: 0; + } +} + diff --git a/src/SmartComponents/CreateImageWizard/CreateImageWizard.js b/src/SmartComponents/CreateImageWizard/CreateImageWizard.js index 97633e71..3168e125 100644 --- a/src/SmartComponents/CreateImageWizard/CreateImageWizard.js +++ b/src/SmartComponents/CreateImageWizard/CreateImageWizard.js @@ -8,6 +8,7 @@ import { Wizard, TextContent } from '@patternfly/react-core'; import WizardStepImageOutput from '../../PresentationalComponents/CreateImageWizard/WizardStepImageOutput'; import WizardStepUploadAWS from '../../PresentationalComponents/CreateImageWizard/WizardStepUploadAWS'; +import WizardStepUploadAzure from '../../PresentationalComponents/CreateImageWizard/WizardStepUploadAzure'; import WizardStepPackages from '../../PresentationalComponents/CreateImageWizard/WizardStepPackages'; import WizardStepUploadGoogle from '../../PresentationalComponents/CreateImageWizard/WizardStepUploadGoogle'; import WizardStepRegistration from '../../PresentationalComponents/CreateImageWizard/WizardStepRegistration'; @@ -51,7 +52,9 @@ class CreateImageWizard extends Component { uploadAzure: { type: 'azure', options: { - temp: '' + tenant_id: null, + subscription_id: null, + resource_group: null, } }, uploadGoogle: { @@ -103,14 +106,21 @@ class CreateImageWizard extends Component { } validate() { - if (this.state.uploadDestinations.aws) {this.validateUploadAmazon();} - else { - this.setState({ - uploadAWSErrors: {}, - uploadAzureErrors: {}, - uploadGoogleErrors: {}, - }); - } + /* upload */ + Object.keys(this.state.uploadDestinations).forEach(provider => { + switch (provider) { + case 'aws': + this.validateUploadAmazon(); + break; + case 'azure': + this.validateUploadAzure(); + break; + case 'google': + break; + default: + break; + } + }); /* subscription */ if (this.state.subscribeNow) { @@ -131,6 +141,29 @@ class CreateImageWizard extends Component { this.setState({ uploadAWSErrors }); } + validateUploadAzure() { + let uploadAzureErrors = {}; + + let tenant_id = this.state.uploadAzure.options.tenant_id; + if (tenant_id === null || tenant_id === '') { + uploadAzureErrors['azure-resource-group'] = + { label: 'Azure tenant ID', value: 'A tenant ID is required' }; + } + + let subscriptionId = this.state.uploadAzure.options.subscription_id; + if (subscriptionId === null || subscriptionId === '') { + uploadAzureErrors['azure-subscription-id'] = + { label: 'Azure subscription ID', value: 'A subscription ID is required' }; + } + + let resource_group = this.state.uploadAzure.options.resource_group; + if (resource_group === null || resource_group === '') { + uploadAzureErrors['azure-resource-group'] = + { label: 'Azure resource group', value: 'A resource group is required' }; + } + // TODO check oauth2 thing too here? + } + validateSubscription() { let subscriptionErrors = {}; if (!this.state.subscription['activation-key']) { @@ -306,6 +339,30 @@ class CreateImageWizard extends Component { requests.push(request); } + if (this.state.uploadDestinations.azure) { + let request = { + distribution: this.state.release, + image_requests: [ + { + architecture: this.state.arch, + image_type: 'vhd', + upload_requests: [{ + type: 'azure', + options: { + tenant_id: this.state.uploadAzure.options.tenant_id, + subscription_id: this.state.uploadAzure.options.subscription_id, + resource_group: this.state.uploadAzure.options.resource_group, + }, + }], + }], + customizations: { + subscription: this.state.subscription, + }, + }; + requests.push(request); + + } + const composeRequests = []; requests.forEach(request => { const composeRequest = api.composeImage(request).then(response => { @@ -348,7 +405,11 @@ class CreateImageWizard extends Component { }; const StepUploadAzure = { - name: 'Microsoft Azure' + name: 'Microsoft Azure', + component: }; const StepUploadGoogle = { diff --git a/src/test/SmartComponents/CreateImageWizard/CreateImageWizard.test.js b/src/test/SmartComponents/CreateImageWizard/CreateImageWizard.test.js index fd56471a..e2c24476 100644 --- a/src/test/SmartComponents/CreateImageWizard/CreateImageWizard.test.js +++ b/src/test/SmartComponents/CreateImageWizard/CreateImageWizard.test.js @@ -213,6 +213,60 @@ describe('Step Upload to Google', () => { }); }); +describe('Step Upload to Azure', () => { + beforeEach(() => { + const { _component, history } = renderWithReduxRouter(); + historySpy = jest.spyOn(history, 'push'); + + // select aws as upload destination + const azureTile = screen.getByTestId('upload-azure'); + azureTile.click(); + + // left sidebar navigation + const sidebar = screen.getByRole('navigation'); + const anchor = getByText(sidebar, 'Microsoft Azure'); + + // load from sidebar + anchor.click(); + }); + + test('clicking Next loads Registration', () => { + const [ next, , ] = verifyButtons(); + next.click(); + + screen.getByText('Register the system'); + }); + + test('clicking Back loads Release', () => { + const [ , back, ] = verifyButtons(); + back.click(); + + screen.getByTestId('release-select'); + }); + + test('clicking Cancel loads landing page', () => { + const [ , , cancel ] = verifyButtons(); + verifyCancelButton(cancel, historySpy); + }); + + test('the azure upload fields are shown and required', () => { + const tenantId = screen.getByTestId('azure-tenant-id'); + expect(tenantId).toHaveValue(''); + expect(tenantId).toBeEnabled(); + expect(tenantId).toBeRequired(); + + const subscription = screen.getByTestId('azure-subscription-id'); + expect(subscription).toHaveValue(''); + expect(subscription).toBeEnabled(); + expect(subscription).toBeRequired(); + + const resourceGroup = screen.getByTestId('azure-resource-group'); + expect(resourceGroup).toHaveValue(''); + expect(resourceGroup).toBeEnabled(); + expect(resourceGroup).toBeRequired(); + }); +}); + describe('Step Registration', () => { beforeEach(() => { const { _component, history } = renderWithReduxRouter();