debian-image-builder-frontend/src/Components/CreateImageWizard/CreateImageWizard.js
2021-06-16 11:35:05 +02:00

371 lines
13 KiB
JavaScript

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { actions } from '../../store/actions';
import { Button, Wizard } from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
import WizardStepImageOutput from './WizardStepImageOutput';
import WizardStepUploadAWS from './WizardStepUploadAWS';
import WizardStepUploadAzure from './WizardStepUploadAzure';
import WizardStepPackages from './WizardStepPackages';
import WizardStepUploadGoogle from './WizardStepUploadGoogle';
import WizardStepRegistration from './WizardStepRegistration';
import WizardStepReview from './WizardStepReview';
import ImageWizardFooter from './ImageWizardFooter';
import './CreateImageWizard.scss';
class CreateImageWizard extends Component {
constructor(props) {
super(props);
this.onStep = this.onStep.bind(this);
this.onSave = this.onSave.bind(this);
this.onClose = this.onClose.bind(this);
this.validate = this.validate.bind(this);
this.validateUploadAmazon = this.validateUploadAmazon.bind(this);
this.state = {
/* errors take form of $fieldId: error */
uploadAWSErrors: {},
uploadAzureErrors: {},
uploadGoogleErrors: {},
isSaveInProgress: false,
isValidSubscription: true,
};
}
async componentDidMount() {
let user = await insights.chrome.auth.getUser();
this.setState({
subscription: {
organization: Number(user.identity.internal.org_id)
}
});
}
onStep(step) {
if (step.name === 'Review') {
this.validate();
}
}
validate() {
/* upload */
Object.keys(this.props.uploadDestinations).forEach(provider => {
switch (provider) {
case 'aws':
this.validateUploadAmazon();
break;
case 'azure':
this.validateUploadAzure();
break;
case 'google':
break;
default:
break;
}
});
/* subscription */
if (this.props.subscribeNow) {
this.setState({ isValidSubscription: this.props.subscription.activationKey ? true : false });
} else {
this.setState({ isValidSubscription: true });
}
}
validateUploadAmazon() {
let uploadAWSErrors = {};
let share = this.props.uploadAWS.shareWithAccounts;
if (share.length === 0 || share[0].length !== 12 || isNaN(share[0])) {
uploadAWSErrors['aws-account-id'] =
{ label: 'AWS account ID', value: 'A 12-digit number is required' };
}
this.setState({ uploadAWSErrors });
}
validateUploadAzure() {
let uploadAzureErrors = {};
let tenant_id = this.props.uploadAzure.tenantId;
if (tenant_id === null || tenant_id === '') {
uploadAzureErrors['azure-resource-group'] =
{ label: 'Azure tenant ID', value: 'A tenant ID is required' };
}
let subscriptionId = this.props.uploadAzure.subscriptionId;
if (subscriptionId === null || subscriptionId === '') {
uploadAzureErrors['azure-subscription-id'] =
{ label: 'Azure subscription ID', value: 'A subscription ID is required' };
}
let resource_group = this.props.uploadAzure.resourceGroup;
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?
}
onSave() {
this.setState({ isSaveInProgress: true });
let customizations = {
packages: this.props.selectedPackages.map(p => p.name),
};
if (this.props.subscribeNow) {
customizations.subscription = {
'activation-key': this.props.subscription.activationKey,
insights: this.props.subscription.insights,
organization: Number(this.props.subscription.organization),
'server-url': 'subscription.rhsm.redhat.com',
'base-url': 'https://cdn.redhat.com/',
};
}
let requests = [];
if (this.props.uploadDestinations.aws) {
let request = {
distribution: this.props.release.distro,
image_requests: [
{
architecture: this.props.release.arch,
image_type: 'ami',
upload_request: {
type: 'aws',
options: {
share_with_accounts: this.props.uploadAWS.shareWithAccounts,
},
},
}],
customizations,
};
requests.push(request);
}
if (this.props.uploadDestinations.google) {
let share = '';
switch (this.props.uploadGoogle.accountType) {
case 'googleAccount':
share = 'user:' + this.props.uploadGoogle.shareWithAccounts[0].user;
break;
case 'serviceAccount':
share = 'serviceAccount:' + this.props.uploadGoogle.shareWithAccounts[0].serviceAccount;
break;
case 'googleGroup':
share = 'group:' + this.props.uploadGoogle.shareWithAccounts[0].group;
break;
case 'domain':
share = 'domain:' + this.props.uploadGoogle.shareWithAccounts[0].domain;
break;
}
let request = {
distribution: this.props.release.distro,
image_requests: [
{
architecture: this.props.release.arch,
image_type: 'vhd',
upload_request: {
type: 'gcp',
options: {
share_with_accounts: [ share ],
},
},
}],
customizations,
};
requests.push(request);
}
if (this.props.uploadDestinations.azure) {
let request = {
distribution: this.props.release.distro,
image_requests: [
{
architecture: this.props.release.arch,
image_type: 'vhd',
upload_request: {
type: 'azure',
options: {
tenant_id: this.props.uploadAzure.tenantId,
subscription_id: this.props.uploadAzure.subscriptionId,
resource_group: this.props.uploadAzure.resourceGroup,
},
},
}],
customizations,
};
requests.push(request);
}
const composeRequests = requests.map(request => this.props.composeStart(request));
Promise.all(composeRequests)
.then(() => {
if (!this.props.composesError) {
this.props.addNotification({
variant: 'success',
title: 'Your image is being created',
});
this.props.history.push('/landing');
}
this.setState({ isSaveInProgress: false });
});
}
onClose () {
this.props.history.push('/landing');
}
render() {
const isValidUploadDestination = this.props.uploadDestinations.aws ||
this.props.uploadDestinations.azure ||
this.props.uploadDestinations.google;
const StepImageOutput = {
name: 'Image output',
component: <WizardStepImageOutput />
};
const StepUploadAWS = {
name: 'Amazon Web Services',
component: <WizardStepUploadAWS
errors={ this.state.uploadAWSErrors } />
};
const StepUploadGoogle = {
name: 'Google Cloud Platform',
component: <WizardStepUploadGoogle
errors={ this.state.uploadGoogleErrors } />
};
const StepUploadAzure = {
name: 'Microsoft Azure',
component: <WizardStepUploadAzure
errors={ this.state.uploadAzureErrors } />
};
const uploadDestinationSteps = [];
if (this.props.uploadDestinations.aws) {
uploadDestinationSteps.push(StepUploadAWS);
}
if (this.props.uploadDestinations.google) {
uploadDestinationSteps.push(StepUploadGoogle);
}
if (this.props.uploadDestinations.azure) {
uploadDestinationSteps.push(StepUploadAzure);
}
const StepTargetEnv = {
name: 'Target environment',
steps: uploadDestinationSteps
};
const StepImageRegistration = {
name: 'Registration',
component: <WizardStepRegistration
isValidSubscription={ this.state.isValidSubscription } />
};
const steps = [
StepImageOutput,
...(StepTargetEnv.steps.length > 0 ? [ StepTargetEnv ] : []),
...(this.props.release.distro === 'rhel-84' ? [ StepImageRegistration ] : []),
{
name: 'Packages',
component: <WizardStepPackages /> },
{
name: 'Review',
component: <WizardStepReview
uploadAWSErrors={ this.state.uploadAWSErrors }
isValidSubscription={ this.state.isValidSubscription } />,
nextButtonText: 'Create',
}
];
return (
<React.Fragment>
<Wizard
className="image-builder"
title={ 'Create image' }
description={ <>
Create a RHEL image and push it to cloud providers.
{' '}
<Button
component="a"
target="_blank"
variant="link"
icon={ <ExternalLinkAltIcon /> }
iconPosition="right"
isInline
href="
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/uploading_a_customized_rhel_system_image_to_cloud_environments/index
">
Documentation
</Button>
</> }
onNext={ this.onStep }
onGoToStep={ this.onStep }
steps={ steps }
onClose={ this.onClose }
onSave={ this.onSave }
footer={ <ImageWizardFooter
isValidUploadDestination={ isValidUploadDestination }
isSaveInProgress={ this.state.isSaveInProgress }
isValidSubscription={ this.state.isValidSubscription }
error={ this.props.composesError } /> }
isOpen />
</React.Fragment>
);
}
}
function mapStateToProps(state) {
return {
composesError: state.composes.error,
release: state.pendingCompose.release,
uploadDestinations: state.pendingCompose.uploadDestinations,
uploadAWS: state.pendingCompose.uploadAWS,
uploadAzure: state.pendingCompose.uploadAzure,
uploadGoogle: state.pendingCompose.uploadGoogle,
selectedPackages: state.pendingCompose.selectedPackages,
subscription: state.pendingCompose.subscription,
subscribeNow: state.pendingCompose.subscribeNow,
};
}
function mapDispatchToProps(dispatch) {
return {
composeUpdated: (compose) => dispatch(actions.composeUpdated(compose)),
composeStart: (composeRequest) => dispatch(actions.composeStart(composeRequest)),
addNotification: (not) => dispatch(addNotification(not)),
};
}
CreateImageWizard.propTypes = {
composesError: PropTypes.string,
composeUpdated: PropTypes.func,
composeStart: PropTypes.func,
addNotification: PropTypes.func,
history: PropTypes.object,
release: PropTypes.object,
uploadDestinations: PropTypes.object,
uploadAWS: PropTypes.object,
uploadAzure: PropTypes.object,
uploadGoogle: PropTypes.object,
subscription: PropTypes.object,
subscribeNow: PropTypes.bool,
selectedPackages: PropTypes.array,
};
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(CreateImageWizard));