Merge "Add container images configuration wizard"
This commit is contained in:
commit
6e16cd4034
|
@ -0,0 +1,92 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { startSubmit, stopSubmit, reset } from 'redux-form';
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { CONTAINER_IMAGES_PREPARE_SUCCESS } from '../constants/ContainerImagesConstants';
|
||||||
|
import MistralConstants from '../constants/MistralConstants';
|
||||||
|
import { getCurrentPlanName } from '../selectors/plans';
|
||||||
|
import { startWorkflow } from './WorkflowActions';
|
||||||
|
import { handleErrors } from './ErrorActions';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
configureImagesFailed: {
|
||||||
|
id: 'ContainerImagesActions.configureImagesFailed',
|
||||||
|
defaultMessage: 'Generating container images configuration failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const startContainerImagesPrepare = values => (
|
||||||
|
dispatch,
|
||||||
|
getState,
|
||||||
|
{ getIntl }
|
||||||
|
) => {
|
||||||
|
const currentPlanName = getCurrentPlanName(getState());
|
||||||
|
const { formatMessage } = getIntl(getState());
|
||||||
|
dispatch(startSubmit('containerImagesPrepareForm'));
|
||||||
|
return dispatch(
|
||||||
|
startWorkflow(
|
||||||
|
MistralConstants.CONTAINER_IMAGES_PREPARE_DEFAULT,
|
||||||
|
{
|
||||||
|
container: currentPlanName,
|
||||||
|
container_image_values: { ...values }
|
||||||
|
},
|
||||||
|
containerImagesPrepareFinished,
|
||||||
|
60 * 1000
|
||||||
|
)
|
||||||
|
).catch(error => {
|
||||||
|
dispatch(
|
||||||
|
stopSubmit('containerImagesPrepareForm', {
|
||||||
|
_error: {
|
||||||
|
title: formatMessage(messages.configureImagesFailed),
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
handleErrors(error, formatMessage(messages.configureImagesFailed), false)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const containerImagesPrepareFinished = execution => (
|
||||||
|
dispatch,
|
||||||
|
getState,
|
||||||
|
{ getIntl }
|
||||||
|
) => {
|
||||||
|
const { formatMessage } = getIntl(getState());
|
||||||
|
const { output: { message, status, params } } = execution;
|
||||||
|
if (status === 'SUCCESS') {
|
||||||
|
dispatch(containerImagesPrepareSuccess(params.ContainerImagePrepare));
|
||||||
|
dispatch(reset('containerImagesPrepareForm'));
|
||||||
|
dispatch(stopSubmit('containerImagesPrepareForm'));
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
stopSubmit('containerImagesPrepareForm', {
|
||||||
|
_error: {
|
||||||
|
title: formatMessage(messages.configureImagesFailed),
|
||||||
|
message: sanitizeMessage(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const containerImagesPrepareSuccess = value => ({
|
||||||
|
type: CONTAINER_IMAGES_PREPARE_SUCCESS,
|
||||||
|
payload: value
|
||||||
|
});
|
|
@ -55,6 +55,7 @@ import {
|
||||||
getDeploymentFailuresFinished
|
getDeploymentFailuresFinished
|
||||||
} from './DeploymentActions';
|
} from './DeploymentActions';
|
||||||
import { fetchNetworksFinished } from './NetworksActions';
|
import { fetchNetworksFinished } from './NetworksActions';
|
||||||
|
import { containerImagesPrepareFinished } from './ContainerImagesActions';
|
||||||
|
|
||||||
export const handleAuthenticationSuccess = (message, dispatch) => {
|
export const handleAuthenticationSuccess = (message, dispatch) => {
|
||||||
message = get(message, ['body', 'message']);
|
message = get(message, ['body', 'message']);
|
||||||
|
@ -244,6 +245,16 @@ export const messageReceived = message => (dispatch, getState) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MistralConstants.CONTAINER_IMAGES_PREPARE_DEFAULT: {
|
||||||
|
dispatch(
|
||||||
|
handleWorkflowMessage(
|
||||||
|
payload.execution.id,
|
||||||
|
containerImagesPrepareFinished
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
import { Form, FieldLevelHelp, Wizard } from 'patternfly-react';
|
||||||
|
import { reduxForm, Field } from 'redux-form';
|
||||||
|
import { format } from 'redux-form-validators';
|
||||||
|
|
||||||
|
import { OverlayLoader } from '../ui/Loader';
|
||||||
|
import FormErrorList from '../ui/forms/FormErrorList';
|
||||||
|
import HorizontalInput from '../ui/reduxForm/HorizontalInput';
|
||||||
|
import { startContainerImagesPrepare } from '../../actions/ContainerImagesActions';
|
||||||
|
import PushDestinationInput from './PushDestinationInput';
|
||||||
|
import {
|
||||||
|
IPV4_WITH_PORT_REGEX,
|
||||||
|
DOCKER_TAG_REGEX,
|
||||||
|
DOCKER_REGISTRY_NAMESPACE_REGEX
|
||||||
|
} from '../../utils/regex';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
generatingConfiguration: {
|
||||||
|
id: 'ContainerImagesPrepareForm.generatingConfiguration',
|
||||||
|
defaultMessage: 'Generating container images configuration...'
|
||||||
|
},
|
||||||
|
namespaceLabel: {
|
||||||
|
id: 'ContainerImagesPrepareForm.namespaceLabel',
|
||||||
|
defaultMessage: 'Registry Namespace'
|
||||||
|
},
|
||||||
|
namespaceInvalid: {
|
||||||
|
id: 'ContainerImagesPrepareForm.namespaceInvalid',
|
||||||
|
defaultMessage: 'Provide a valid registry address and images namespace'
|
||||||
|
},
|
||||||
|
namePrefixLabel: {
|
||||||
|
id: 'ContainerImagesPrepareForm.namePrefixLabel',
|
||||||
|
defaultMessage: 'Name Prefix'
|
||||||
|
},
|
||||||
|
nameSuffixLabel: {
|
||||||
|
id: 'ContainerImagesPrepareForm.nameSuffixLabel',
|
||||||
|
defaultMessage: 'Name Suffix'
|
||||||
|
},
|
||||||
|
tagLabel: {
|
||||||
|
id: 'ContainerImagesPrepareForm.tagLabel',
|
||||||
|
defaultMessage: 'Tag'
|
||||||
|
},
|
||||||
|
tagFromLabelLabel: {
|
||||||
|
id: 'ContainerImagesPrepareForm.tagFromLabelLabel',
|
||||||
|
defaultMessage: 'Tag from Label'
|
||||||
|
},
|
||||||
|
pushDestinationLabel: {
|
||||||
|
id: 'ContainerImagesPrepareForm.pushDestinationLabel',
|
||||||
|
defaultMessage: 'Push Destination'
|
||||||
|
},
|
||||||
|
pushDestinationUndercloud: {
|
||||||
|
id: 'ContainerImagesPrepareForm.pushDestinationUndercloud',
|
||||||
|
defaultMessage: 'Undercloud image registry'
|
||||||
|
},
|
||||||
|
namespaceDescription: {
|
||||||
|
id: 'ContainerImagesPrepareForm.namespaceDescription',
|
||||||
|
defaultMessage:
|
||||||
|
'Namespace of the remote registry from which the container images will be pulled during deployment.'
|
||||||
|
},
|
||||||
|
namePrefixDescription: {
|
||||||
|
id: 'ContainerImagesPrepareForm.namePrefixDescription',
|
||||||
|
defaultMessage: 'Container image name prefix'
|
||||||
|
},
|
||||||
|
nameSuffixDescription: {
|
||||||
|
id: 'ContainerImagesPrepareForm.nameSuffixDescription',
|
||||||
|
defaultMessage: 'Container image name suffix'
|
||||||
|
},
|
||||||
|
tagDescription: {
|
||||||
|
id: 'ContainerImagesPrepareForm.tagDescription',
|
||||||
|
defaultMessage: 'Tag representing the latest image version.'
|
||||||
|
},
|
||||||
|
pushDestinationDescription: {
|
||||||
|
id: 'ContainerImagesPrepareForm.pushDestinationDescription',
|
||||||
|
defaultMessage:
|
||||||
|
'By specifying a Push Destination, the required images will be copied \
|
||||||
|
from provided namespace to this registry. As part of the undercloud \
|
||||||
|
install, an image registry is configured on port 8787. This can be used \
|
||||||
|
to increase reliability of image pulls, and minimise overall network \
|
||||||
|
transfers. Alternatively it is possible to explicitly specify the \
|
||||||
|
registry to push the images to.'
|
||||||
|
},
|
||||||
|
pushDestinationValidationMessage: {
|
||||||
|
id: 'ContainerImagesPrepareForm.pushDestinationValidationMessage',
|
||||||
|
defaultMessage: 'Please enter a valid IPv4 address and port'
|
||||||
|
},
|
||||||
|
tagFromLabelDescription: {
|
||||||
|
id: 'ContainerImagesPrepareForm.tagFromLabelDescription',
|
||||||
|
defaultMessage:
|
||||||
|
'Provide a label to discover the versioned tag for images. Some build \
|
||||||
|
pipelines have a versioned tag which can only be discovered via a \
|
||||||
|
combination of labels. For this case, a template format can be specified \
|
||||||
|
instead, e.g. {labelExample}. If you want these parameters to have \
|
||||||
|
the actual tag instead of the discovered tag, this entry can be omitted.'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class ContainerImagesPrepareForm extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
const { initialValues: { namespace }, resetToDefaults } = this.props;
|
||||||
|
if (!namespace) {
|
||||||
|
resetToDefaults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validatePushDestination(value) {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return undefined;
|
||||||
|
} else if (new RegExp(IPV4_WITH_PORT_REGEX).test(value)) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<FormattedMessage {...messages.pushDestinationValidationMessage} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
error,
|
||||||
|
intl: { formatMessage },
|
||||||
|
submitting,
|
||||||
|
handleSubmit
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit} horizontal>
|
||||||
|
<OverlayLoader
|
||||||
|
loaded={!submitting}
|
||||||
|
content={formatMessage(messages.generatingConfiguration)}
|
||||||
|
>
|
||||||
|
<Wizard.Row>
|
||||||
|
<Wizard.Main>
|
||||||
|
<Wizard.Contents stepIndex={0} activeStepIndex={0}>
|
||||||
|
<FormErrorList errors={error ? [error] : []} />
|
||||||
|
<fieldset>
|
||||||
|
<Field
|
||||||
|
name="namespace"
|
||||||
|
component={HorizontalInput}
|
||||||
|
id="namespace"
|
||||||
|
label={
|
||||||
|
<Fragment>
|
||||||
|
<FormattedMessage {...messages.namespaceLabel} />
|
||||||
|
<FieldLevelHelp
|
||||||
|
placement="right"
|
||||||
|
style={{ maxWidth: 400 }}
|
||||||
|
content={formatMessage(messages.namespaceDescription)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
labelColumns={4}
|
||||||
|
validate={format({
|
||||||
|
with: DOCKER_REGISTRY_NAMESPACE_REGEX,
|
||||||
|
message: formatMessage(messages.namespaceInvalid),
|
||||||
|
allowBlank: true
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
name="name_prefix"
|
||||||
|
component={HorizontalInput}
|
||||||
|
id="name_prefix"
|
||||||
|
label={
|
||||||
|
<Fragment>
|
||||||
|
<FormattedMessage {...messages.namePrefixLabel} />
|
||||||
|
<FieldLevelHelp
|
||||||
|
placement="right"
|
||||||
|
style={{ maxWidth: 400 }}
|
||||||
|
content={formatMessage(
|
||||||
|
messages.namePrefixDescription
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
labelColumns={4}
|
||||||
|
inputColumns={4}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
name="name_suffix"
|
||||||
|
component={HorizontalInput}
|
||||||
|
id="name_suffix"
|
||||||
|
label={
|
||||||
|
<Fragment>
|
||||||
|
<FormattedMessage {...messages.nameSuffixLabel} />
|
||||||
|
<FieldLevelHelp
|
||||||
|
placement="right"
|
||||||
|
style={{ maxWidth: 400 }}
|
||||||
|
content={formatMessage(
|
||||||
|
messages.nameSuffixDescription
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
labelColumns={4}
|
||||||
|
inputColumns={4}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
name="tag"
|
||||||
|
component={HorizontalInput}
|
||||||
|
id="tag"
|
||||||
|
label={
|
||||||
|
<Fragment>
|
||||||
|
<FormattedMessage {...messages.tagLabel} />
|
||||||
|
<FieldLevelHelp
|
||||||
|
placement="right"
|
||||||
|
style={{ maxWidth: 400 }}
|
||||||
|
content={formatMessage(messages.tagDescription)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
validate={format({
|
||||||
|
with: DOCKER_TAG_REGEX,
|
||||||
|
allowBlank: true
|
||||||
|
})}
|
||||||
|
labelColumns={4}
|
||||||
|
inputColumns={4}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<Field
|
||||||
|
name="push_destination"
|
||||||
|
component={PushDestinationInput}
|
||||||
|
id="push_destination"
|
||||||
|
label={
|
||||||
|
<Fragment>
|
||||||
|
<FormattedMessage {...messages.pushDestinationLabel} />
|
||||||
|
<FieldLevelHelp
|
||||||
|
placement="right"
|
||||||
|
style={{ maxWidth: 400 }}
|
||||||
|
content={formatMessage(
|
||||||
|
messages.pushDestinationDescription
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
labelColumns={4}
|
||||||
|
validate={this.validatePushDestination}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
name="tag_from_label"
|
||||||
|
component={HorizontalInput}
|
||||||
|
id="tag_from_label"
|
||||||
|
label={
|
||||||
|
<Fragment>
|
||||||
|
<FormattedMessage {...messages.tagFromLabelLabel} />
|
||||||
|
<FieldLevelHelp
|
||||||
|
placement="right"
|
||||||
|
style={{ maxWidth: 400 }}
|
||||||
|
content={formatMessage(
|
||||||
|
messages.tagFromLabelDescription,
|
||||||
|
{
|
||||||
|
labelExample: '{version}-{release}'
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
labelColumns={4}
|
||||||
|
inputColumns={4}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</Wizard.Contents>
|
||||||
|
</Wizard.Main>
|
||||||
|
</Wizard.Row>
|
||||||
|
</OverlayLoader>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContainerImagesPrepareForm.propTypes = {
|
||||||
|
error: PropTypes.object,
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
initialValues: PropTypes.object.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
resetToDefaults: PropTypes.func.isRequired,
|
||||||
|
submitting: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeValues = values => {
|
||||||
|
const generateIfEmpty = ['namespace', 'tag', 'push_destination'];
|
||||||
|
Object.keys(values).map(key => {
|
||||||
|
if (generateIfEmpty.includes(key)) {
|
||||||
|
!values[key] && delete values[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = reduxForm({
|
||||||
|
form: 'containerImagesPrepareForm',
|
||||||
|
onSubmit: (values, dispatch, props) => {
|
||||||
|
normalizeValues(values);
|
||||||
|
dispatch(startContainerImagesPrepare(values));
|
||||||
|
},
|
||||||
|
touchOnChange: true,
|
||||||
|
enableReinitialize: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export default injectIntl(form(ContainerImagesPrepareForm));
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Icon, Button } from 'patternfly-react';
|
||||||
|
import { submit, isSubmitting, isPristine, isInvalid } from 'redux-form';
|
||||||
|
|
||||||
|
import { startContainerImagesPrepare } from '../../actions/ContainerImagesActions';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
next: {
|
||||||
|
id: 'ContainerImagesWizard.next',
|
||||||
|
defaultMessage: 'Next'
|
||||||
|
},
|
||||||
|
reset: {
|
||||||
|
id: 'ContainerImagesPrepareFormActions.reset',
|
||||||
|
defaultMessage: 'Reset to Defaults'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ContainerImagesPrepareFormActions = ({
|
||||||
|
isSubmitting,
|
||||||
|
isInvalid,
|
||||||
|
isPristine,
|
||||||
|
resetToDefaults,
|
||||||
|
submitImagesPrepareForm
|
||||||
|
}) => (
|
||||||
|
<Fragment>
|
||||||
|
<Button onClick={resetToDefaults} disabled={isSubmitting}>
|
||||||
|
<FormattedMessage {...messages.reset} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
bsStyle="primary"
|
||||||
|
onClick={submitImagesPrepareForm}
|
||||||
|
disabled={isSubmitting || isPristine || isInvalid}
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.next} />
|
||||||
|
<Icon type="fa" name="angle-right" />
|
||||||
|
</Button>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
ContainerImagesPrepareFormActions.propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
isFetchingParameters: PropTypes.bool.isRequired,
|
||||||
|
isInvalid: PropTypes.bool.isRequired,
|
||||||
|
isPristine: PropTypes.bool.isRequired,
|
||||||
|
isSubmitting: PropTypes.bool.isRequired,
|
||||||
|
resetToDefaults: PropTypes.func.isRequired,
|
||||||
|
submitImagesPrepareForm: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
isFetchingParameters: state.parameters.isFetching,
|
||||||
|
isSubmitting: isSubmitting('containerImagesPrepareForm')(state),
|
||||||
|
isPristine: isPristine('containerImagesPrepareForm')(state),
|
||||||
|
isInvalid: isInvalid('containerImagesPrepareForm')(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
resetToDefaults: () => dispatch(startContainerImagesPrepare({})),
|
||||||
|
submitImagesPrepareForm: () => dispatch(submit('containerImagesPrepareForm'))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default injectIntl(
|
||||||
|
connect(mapStateToProps, mapDispatchToProps)(
|
||||||
|
ContainerImagesPrepareFormActions
|
||||||
|
)
|
||||||
|
);
|
|
@ -0,0 +1,213 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Wizard, Modal, Icon, Button } from 'patternfly-react';
|
||||||
|
|
||||||
|
import { checkRunningDeployment } from '../utils/checkRunningDeploymentHOC';
|
||||||
|
import { getCurrentPlanName } from '../../selectors/plans';
|
||||||
|
import { Loader } from '../ui/Loader';
|
||||||
|
import { fetchParameters } from '../../actions/ParametersActions';
|
||||||
|
import { startContainerImagesPrepare } from '../../actions/ContainerImagesActions';
|
||||||
|
import {
|
||||||
|
RoutedWizard,
|
||||||
|
CloseModalXButton,
|
||||||
|
CloseModalButton
|
||||||
|
} from '../ui/Modals';
|
||||||
|
import ContainerImagesPrepareForm from './ContainerImagesPrepareForm';
|
||||||
|
import { getContainerImagePrepareParameterSeed } from '../../selectors/parameters';
|
||||||
|
import ContainerImagesPrepareFormActions from './ContainerImagesPrepareFormActions';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: {
|
||||||
|
id: 'ContainerImagesWizard.close',
|
||||||
|
defaultMessage: 'Close'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
id: 'ContainerImagesWizard.cancel',
|
||||||
|
defaultMessage: 'Cancel'
|
||||||
|
},
|
||||||
|
back: {
|
||||||
|
id: 'ContainerImagesWizard.back',
|
||||||
|
defaultMessage: 'Back'
|
||||||
|
},
|
||||||
|
save: {
|
||||||
|
id: 'ContainerImagesWizard.save',
|
||||||
|
defaultMessage: 'Save Changes'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
id: 'ContainerImagesWizard.title',
|
||||||
|
defaultMessage: 'Prepare Container Images'
|
||||||
|
},
|
||||||
|
loadingData: {
|
||||||
|
id: 'ContainerImagesWizard.loadingData',
|
||||||
|
defaultMessage: 'Loading configuration...'
|
||||||
|
},
|
||||||
|
configureImages: {
|
||||||
|
id: 'ContainerImagesWizard.configureImages',
|
||||||
|
defaultMessage: 'Configure Images'
|
||||||
|
},
|
||||||
|
review: {
|
||||||
|
id: 'ContainerImagesWizard.review',
|
||||||
|
defaultMessage: 'Review Configuration'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class ContainerImagesWizard extends Component {
|
||||||
|
state = {
|
||||||
|
activeStepIndex: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {
|
||||||
|
currentPlanName,
|
||||||
|
fetchParameters,
|
||||||
|
isFetchingParameters
|
||||||
|
} = this.props;
|
||||||
|
!isFetchingParameters && fetchParameters(currentPlanName);
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveStepIndex(index) {
|
||||||
|
this.setState({ activeStepIndex: index });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
currentPlanName,
|
||||||
|
intl: { formatMessage },
|
||||||
|
isFetchingParameters,
|
||||||
|
containerImagePrepareParameterSeed,
|
||||||
|
resetToDefaults
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { activeStepIndex } = this.state;
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
step: 1,
|
||||||
|
label: '1',
|
||||||
|
title: formatMessage(messages.configureImages)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
label: '2',
|
||||||
|
title: formatMessage(messages.review)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RoutedWizard
|
||||||
|
id="ContainerImagesWizard__Wizard"
|
||||||
|
redirectPath={`/plans/${currentPlanName}`}
|
||||||
|
>
|
||||||
|
<Modal.Header>
|
||||||
|
<CloseModalXButton />
|
||||||
|
<Modal.Title>
|
||||||
|
<FormattedMessage {...messages.title} />
|
||||||
|
</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Wizard.Body>
|
||||||
|
<Wizard.Steps
|
||||||
|
steps={steps.map(({ step, label, title }, index) => (
|
||||||
|
<Wizard.Step
|
||||||
|
key={step}
|
||||||
|
stepIndex={index}
|
||||||
|
step={step}
|
||||||
|
label={label}
|
||||||
|
title={title}
|
||||||
|
activeStep={steps[activeStepIndex].step}
|
||||||
|
onClick={() => this.setActiveStepIndex(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
/>
|
||||||
|
<Loader
|
||||||
|
height={60}
|
||||||
|
loaded={!isFetchingParameters}
|
||||||
|
content={formatMessage(messages.loadingData)}
|
||||||
|
>
|
||||||
|
{activeStepIndex === 0 ? (
|
||||||
|
<ContainerImagesPrepareForm
|
||||||
|
onSubmit={this.handleContainerImagesPrepareFormSubmit}
|
||||||
|
initialValues={{
|
||||||
|
...containerImagePrepareParameterSeed,
|
||||||
|
push_destination:
|
||||||
|
containerImagePrepareParameterSeed.push_destination || false
|
||||||
|
}}
|
||||||
|
resetToDefaults={resetToDefaults}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>parameter form here</p>
|
||||||
|
)}
|
||||||
|
</Loader>
|
||||||
|
</Wizard.Body>
|
||||||
|
<Wizard.Footer>
|
||||||
|
<CloseModalButton>
|
||||||
|
<FormattedMessage {...messages.cancel} />
|
||||||
|
</CloseModalButton>
|
||||||
|
{activeStepIndex === 0 && <ContainerImagesPrepareFormActions />}
|
||||||
|
{activeStepIndex === 1 && (
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
bsStyle="default"
|
||||||
|
onClick={() => this.setActiveStepIndex(0)}
|
||||||
|
>
|
||||||
|
<Icon type="fa" name="angle-left" />
|
||||||
|
<FormattedMessage {...messages.back} />
|
||||||
|
</Button>
|
||||||
|
<Button bsStyle="primary" onClick={this.onNextButtonClick}>
|
||||||
|
<FormattedMessage {...messages.save} />
|
||||||
|
</Button>
|
||||||
|
<CloseModalButton>
|
||||||
|
<FormattedMessage {...messages.close} />
|
||||||
|
</CloseModalButton>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</Wizard.Footer>
|
||||||
|
</RoutedWizard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContainerImagesWizard.propTypes = {
|
||||||
|
containerImagePrepareParameterSeed: PropTypes.object.isRequired,
|
||||||
|
currentPlanName: PropTypes.string.isRequired,
|
||||||
|
fetchParameters: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
isFetchingParameters: PropTypes.bool.isRequired,
|
||||||
|
resetToDefaults: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
containerImagePrepareParameterSeed: getContainerImagePrepareParameterSeed(
|
||||||
|
state
|
||||||
|
),
|
||||||
|
currentPlanName: getCurrentPlanName(state),
|
||||||
|
isFetchingParameters: state.parameters.isFetching
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
fetchParameters: currentPlanName =>
|
||||||
|
dispatch(fetchParameters(currentPlanName)),
|
||||||
|
resetToDefaults: () => dispatch(startContainerImagesPrepare({}))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default checkRunningDeployment(
|
||||||
|
injectIntl(
|
||||||
|
connect(mapStateToProps, mapDispatchToProps)(ContainerImagesWizard)
|
||||||
|
)
|
||||||
|
);
|
|
@ -0,0 +1,129 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2017 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { createRef } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
Radio,
|
||||||
|
FormGroup,
|
||||||
|
ControlLabel,
|
||||||
|
Col
|
||||||
|
} from 'patternfly-react';
|
||||||
|
import cx from 'classnames';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getValidationState,
|
||||||
|
InputDescription,
|
||||||
|
InputMessage
|
||||||
|
} from '../ui/reduxForm/utils';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
pushDestinationOptionFalse: {
|
||||||
|
id: 'PushDestinationInput.pushDestinationOptionFalse',
|
||||||
|
defaultMessage: `Don't push images`
|
||||||
|
},
|
||||||
|
pushDestinationOptionTrue: {
|
||||||
|
id: 'PushDestinationInput.pushDestinationOptionTrue',
|
||||||
|
defaultMessage: 'Push to Undercloud registry'
|
||||||
|
},
|
||||||
|
pushDestinationOptionCustom: {
|
||||||
|
id: 'PushDestinationInput.pushDestinationOptionCustom',
|
||||||
|
defaultMessage: 'Specify custom registry'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class PushDestinationInput extends React.Component {
|
||||||
|
customRegistryInputRef = createRef();
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
inputColumns,
|
||||||
|
labelColumns,
|
||||||
|
meta,
|
||||||
|
label,
|
||||||
|
input: { value, onChange, onBlur, ...inputRest },
|
||||||
|
description,
|
||||||
|
required
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<FormGroup controlId={id} validationState={getValidationState(meta)}>
|
||||||
|
<Col
|
||||||
|
componentClass={ControlLabel}
|
||||||
|
sm={labelColumns}
|
||||||
|
className={cx({ 'required-pf': required })}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Col>
|
||||||
|
<Col sm={inputColumns}>
|
||||||
|
<Radio
|
||||||
|
{...inputRest}
|
||||||
|
checked={value === false}
|
||||||
|
onChange={e => onChange(false)}
|
||||||
|
onBlur={e => onBlur(false)}
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.pushDestinationOptionFalse} />
|
||||||
|
</Radio>
|
||||||
|
<Radio
|
||||||
|
{...inputRest}
|
||||||
|
checked={value === true}
|
||||||
|
onChange={e => onChange(true)}
|
||||||
|
onBlur={e => onBlur(true)}
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.pushDestinationOptionTrue} />
|
||||||
|
</Radio>
|
||||||
|
<Radio
|
||||||
|
{...inputRest}
|
||||||
|
checked={value === this.customRegistryInputRef.value}
|
||||||
|
onChange={e => onChange(this.customRegistryInputRef.value)}
|
||||||
|
onBlur={e => onBlur(this.customRegistryInputRef.value)}
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.pushDestinationOptionCustom} />
|
||||||
|
</Radio>
|
||||||
|
<FormControl
|
||||||
|
{...inputRest}
|
||||||
|
defaultValue="192.168.24.1:8787"
|
||||||
|
inputRef={ref => (this.customRegistryInputRef = ref)}
|
||||||
|
style={{ marginTop: 4 }}
|
||||||
|
type="text"
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
onBlur={e => onBlur(e.target.value)}
|
||||||
|
disabled={value !== this.customRegistryInputRef.value}
|
||||||
|
/>
|
||||||
|
<InputMessage {...meta} />
|
||||||
|
<InputDescription description={description} />
|
||||||
|
</Col>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PushDestinationInput.propTypes = {
|
||||||
|
description: PropTypes.node,
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
input: PropTypes.object.isRequired,
|
||||||
|
inputColumns: PropTypes.number.isRequired,
|
||||||
|
label: PropTypes.node,
|
||||||
|
labelColumns: PropTypes.number.isRequired,
|
||||||
|
meta: PropTypes.object.isRequired,
|
||||||
|
required: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
PushDestinationInput.defaultProps = {
|
||||||
|
labelColumns: 4,
|
||||||
|
inputColumns: 7,
|
||||||
|
required: false
|
||||||
|
};
|
|
@ -43,6 +43,7 @@ import { fetchEnvironmentConfiguration } from '../../actions/EnvironmentConfigur
|
||||||
import HardwareStep from './HardwareStep';
|
import HardwareStep from './HardwareStep';
|
||||||
import { Loader } from '../ui/Loader';
|
import { Loader } from '../ui/Loader';
|
||||||
import NetworkConfiguration from '../networkConfiguration/NetworkConfiguration';
|
import NetworkConfiguration from '../networkConfiguration/NetworkConfiguration';
|
||||||
|
import ContainerImagesWizard from '../containerImages/ContainerImagesWizard';
|
||||||
import { fetchParameters } from '../../actions/ParametersActions';
|
import { fetchParameters } from '../../actions/ParametersActions';
|
||||||
import RoleDetail from '../roles/RoleDetail';
|
import RoleDetail from '../roles/RoleDetail';
|
||||||
import RolesStep from './RolesStep';
|
import RolesStep from './RolesStep';
|
||||||
|
@ -245,6 +246,10 @@ class CurrentPlan extends React.Component {
|
||||||
path="/plans/:planName/network-configuration"
|
path="/plans/:planName/network-configuration"
|
||||||
component={NetworkConfiguration}
|
component={NetworkConfiguration}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/plans/:planName/container-images"
|
||||||
|
component={ContainerImagesWizard}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/plans/:planName/deployment-confirmation"
|
path="/plans/:planName/deployment-confirmation"
|
||||||
render={() => (
|
render={() => (
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import RoutedModal from './RoutedModal';
|
||||||
|
|
||||||
|
const RoutedWizard = props => (
|
||||||
|
<RoutedModal {...props} dialogClassName="modal-lg wizard-pf" />
|
||||||
|
);
|
||||||
|
|
||||||
|
export default RoutedWizard;
|
|
@ -19,4 +19,5 @@ export ConfirmationModal from './ConfirmationModal';
|
||||||
export Modal from './Modal';
|
export Modal from './Modal';
|
||||||
export ModalPanel from './ModalPanel';
|
export ModalPanel from './ModalPanel';
|
||||||
export RoutedModal from './RoutedModal';
|
export RoutedModal from './RoutedModal';
|
||||||
|
export RoutedWizard from './RoutedWizard';
|
||||||
export RoutedModalPanel from './RoutedModalPanel';
|
export RoutedModalPanel from './RoutedModalPanel';
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const CONTAINER_IMAGES_PREPARE_SUCCESS =
|
||||||
|
'CONTAINER_IMAGES_PREPARE_SUCCESS';
|
|
@ -27,6 +27,8 @@ export default {
|
||||||
CAPABILITIES_UPDATE: 'tripleo.heat_capabilities.update',
|
CAPABILITIES_UPDATE: 'tripleo.heat_capabilities.update',
|
||||||
CREATE_CONTAINER: 'tripleo.plan.create_container',
|
CREATE_CONTAINER: 'tripleo.plan.create_container',
|
||||||
CONFIG_DOWNLOAD_DEPLOY: 'tripleo.deployment.v1.config_download_deploy',
|
CONFIG_DOWNLOAD_DEPLOY: 'tripleo.deployment.v1.config_download_deploy',
|
||||||
|
CONTAINER_IMAGES_PREPARE_DEFAULT:
|
||||||
|
'tripleo.container_images.v1.container_image_prepare_default',
|
||||||
DEPLOYMENT_DEPLOY_PLAN: 'tripleo.deployment.v1.deploy_plan',
|
DEPLOYMENT_DEPLOY_PLAN: 'tripleo.deployment.v1.deploy_plan',
|
||||||
UNDEPLOY_PLAN: 'tripleo.deployment.v1.undeploy_plan',
|
UNDEPLOY_PLAN: 'tripleo.deployment.v1.undeploy_plan',
|
||||||
RECOVER_DEPLOYMENT_STATUS: 'tripleo.deployment.v1.recover_deployment_status',
|
RECOVER_DEPLOYMENT_STATUS: 'tripleo.deployment.v1.recover_deployment_status',
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
Resource,
|
Resource,
|
||||||
Parameter
|
Parameter
|
||||||
} from '../immutableRecords/parameters';
|
} from '../immutableRecords/parameters';
|
||||||
|
import { CONTAINER_IMAGES_PREPARE_SUCCESS } from '../constants/ContainerImagesConstants';
|
||||||
|
|
||||||
const initialState = new ParametersDefaultState();
|
const initialState = new ParametersDefaultState();
|
||||||
|
|
||||||
|
@ -55,6 +56,12 @@ export default function parametersReducer(state = initialState, action) {
|
||||||
case EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_SUCCESS:
|
case EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_SUCCESS:
|
||||||
return state.set('loaded', false);
|
return state.set('loaded', false);
|
||||||
|
|
||||||
|
case CONTAINER_IMAGES_PREPARE_SUCCESS:
|
||||||
|
return state.updateIn(
|
||||||
|
['parameters', 'ContainerImagePrepare'],
|
||||||
|
p => p && p.set('default', action.payload)
|
||||||
|
);
|
||||||
|
|
||||||
case PlansConstants.PLAN_CHOSEN:
|
case PlansConstants.PLAN_CHOSEN:
|
||||||
return initialState;
|
return initialState;
|
||||||
|
|
||||||
|
|
|
@ -33,3 +33,15 @@ export const FQDN_REGEX = new RegExp(
|
||||||
export const PORT_REGEX = new RegExp(
|
export const PORT_REGEX = new RegExp(
|
||||||
/^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
|
/^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const DOCKER_TAG_REGEX = new RegExp(/^[\w][\w.-]{0,127}$/);
|
||||||
|
|
||||||
|
// FQDN or IPV4:PORT / DOCKER_TAG (Docker tag format is the same as image name format)
|
||||||
|
export const DOCKER_REGISTRY_NAMESPACE_REGEX = new RegExp(
|
||||||
|
/^(((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))\/[\w][\w.-]{0,127}$/i
|
||||||
|
);
|
||||||
|
|
||||||
|
// IPV4:PORT
|
||||||
|
export const IPV4_WITH_PORT_REGEX = new RegExp(
|
||||||
|
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in New Issue