mirror of
https://github.com/ansible/awx.git
synced 2026-01-20 06:01:25 -03:30
Adds support for GCE credentials in credential form(s)
This commit is contained in:
parent
49edaab861
commit
4a9d39c3fa
@ -7,6 +7,7 @@ import FieldTooltip from './FieldTooltip';
|
||||
function FormField(props) {
|
||||
const {
|
||||
id,
|
||||
helperText,
|
||||
name,
|
||||
label,
|
||||
tooltip,
|
||||
@ -25,6 +26,7 @@ function FormField(props) {
|
||||
{(type === 'textarea' && (
|
||||
<FormGroup
|
||||
fieldId={id}
|
||||
helperText={helperText}
|
||||
helperTextInvalid={meta.error}
|
||||
isRequired={isRequired}
|
||||
isValid={isValid}
|
||||
@ -46,6 +48,7 @@ function FormField(props) {
|
||||
)) || (
|
||||
<FormGroup
|
||||
fieldId={id}
|
||||
helperText={helperText}
|
||||
helperTextInvalid={meta.error}
|
||||
isRequired={isRequired}
|
||||
isValid={isValid}
|
||||
@ -70,6 +73,7 @@ function FormField(props) {
|
||||
}
|
||||
|
||||
FormField.propTypes = {
|
||||
helperText: PropTypes.string,
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
|
||||
@ -81,6 +85,7 @@ FormField.propTypes = {
|
||||
};
|
||||
|
||||
FormField.defaultProps = {
|
||||
helperText: '',
|
||||
type: 'text',
|
||||
validate: () => {},
|
||||
isRequired: false,
|
||||
|
||||
@ -20,7 +20,9 @@ function CredentialAdd({ me }) {
|
||||
try {
|
||||
const {
|
||||
data: { results: loadedCredentialTypes },
|
||||
} = await CredentialTypesAPI.read({ or__kind: ['scm', 'ssh'] });
|
||||
} = await CredentialTypesAPI.read({
|
||||
or__namespace: ['gce', 'scm', 'ssh'],
|
||||
});
|
||||
setCredentialTypes(loadedCredentialTypes);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
@ -65,7 +67,15 @@ function CredentialAdd({ me }) {
|
||||
);
|
||||
}
|
||||
if (isLoading) {
|
||||
return <ContentLoading />;
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<ContentLoading />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PageSection>
|
||||
|
||||
@ -20,7 +20,9 @@ function CredentialEdit({ credential, me }) {
|
||||
try {
|
||||
const {
|
||||
data: { results: loadedCredentialTypes },
|
||||
} = await CredentialTypesAPI.read({ or__kind: ['scm', 'ssh'] });
|
||||
} = await CredentialTypesAPI.read({
|
||||
or__namespace: ['gce', 'scm', 'ssh'],
|
||||
});
|
||||
setCredentialTypes(loadedCredentialTypes);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
|
||||
@ -13,12 +13,17 @@ import {
|
||||
FormColumnLayout,
|
||||
SubFormLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
import { ManualSubForm, SourceControlSubForm } from './CredentialSubForms';
|
||||
import {
|
||||
GoogleComputeEngineSubForm,
|
||||
ManualSubForm,
|
||||
SourceControlSubForm,
|
||||
} from './CredentialSubForms';
|
||||
|
||||
function CredentialFormFields({
|
||||
i18n,
|
||||
credentialTypes,
|
||||
formik,
|
||||
gceCredentialTypeId,
|
||||
initialValues,
|
||||
scmCredentialTypeId,
|
||||
sshCredentialTypeId,
|
||||
@ -106,6 +111,7 @@ function CredentialFormFields({
|
||||
<Title size="md">{i18n._(t`Type Details`)}</Title>
|
||||
{
|
||||
{
|
||||
[gceCredentialTypeId]: <GoogleComputeEngineSubForm />,
|
||||
[sshCredentialTypeId]: <ManualSubForm />,
|
||||
[scmCredentialTypeId]: <SourceControlSubForm />,
|
||||
}[formik.values.credential_type]
|
||||
@ -130,22 +136,26 @@ function CredentialForm({
|
||||
organization: credential?.summary_fields?.organization || null,
|
||||
credential_type: credential.credential_type || '',
|
||||
inputs: {
|
||||
username: credential?.inputs?.username || '',
|
||||
password: credential?.inputs?.password || '',
|
||||
ssh_key_data: credential?.inputs?.ssh_key_data || '',
|
||||
ssh_public_key_data: credential?.inputs?.ssh_public_key_data || '',
|
||||
ssh_key_unlock: credential?.inputs?.ssh_key_unlock || '',
|
||||
become_method: credential?.inputs?.become_method || '',
|
||||
become_username: credential?.inputs?.become_username || '',
|
||||
become_password: credential?.inputs?.become_password || '',
|
||||
become_username: credential?.inputs?.become_username || '',
|
||||
password: credential?.inputs?.password || '',
|
||||
project: credential?.inputs?.project || '',
|
||||
ssh_key_data: credential?.inputs?.ssh_key_data || '',
|
||||
ssh_key_unlock: credential?.inputs?.ssh_key_unlock || '',
|
||||
ssh_public_key_data: credential?.inputs?.ssh_public_key_data || '',
|
||||
username: credential?.inputs?.username || '',
|
||||
},
|
||||
};
|
||||
|
||||
const scmCredentialTypeId = Object.keys(credentialTypes)
|
||||
.filter(key => credentialTypes[key].kind === 'scm')
|
||||
.filter(key => credentialTypes[key].namespace === 'scm')
|
||||
.map(key => credentialTypes[key].id)[0];
|
||||
const sshCredentialTypeId = Object.keys(credentialTypes)
|
||||
.filter(key => credentialTypes[key].kind === 'ssh')
|
||||
.filter(key => credentialTypes[key].namespace === 'ssh')
|
||||
.map(key => credentialTypes[key].id)[0];
|
||||
const gceCredentialTypeId = Object.keys(credentialTypes)
|
||||
.filter(key => credentialTypes[key].namespace === 'gce')
|
||||
.map(key => credentialTypes[key].id)[0];
|
||||
|
||||
return (
|
||||
@ -168,6 +178,7 @@ function CredentialForm({
|
||||
'become_username',
|
||||
'become_password',
|
||||
];
|
||||
const gceKeys = ['username', 'ssh_key_data', 'project'];
|
||||
if (parseInt(values.credential_type, 10) === scmCredentialTypeId) {
|
||||
Object.keys(values.inputs).forEach(key => {
|
||||
if (scmKeys.indexOf(key) < 0) {
|
||||
@ -182,6 +193,14 @@ function CredentialForm({
|
||||
delete values.inputs[key];
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
parseInt(values.credential_type, 10) === gceCredentialTypeId
|
||||
) {
|
||||
Object.keys(values.inputs).forEach(key => {
|
||||
if (gceKeys.indexOf(key) < 0) {
|
||||
delete values.inputs[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
onSubmit(values);
|
||||
}}
|
||||
@ -193,6 +212,7 @@ function CredentialForm({
|
||||
formik={formik}
|
||||
initialValues={initialValues}
|
||||
credentialTypes={credentialTypes}
|
||||
gceCredentialTypeId={gceCredentialTypeId}
|
||||
scmCredentialTypeId={scmCredentialTypeId}
|
||||
sshCredentialTypeId={sshCredentialTypeId}
|
||||
{...rest}
|
||||
|
||||
@ -4,6 +4,8 @@ import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
|
||||
import CredentialForm from './CredentialForm';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
const machineCredential = {
|
||||
id: 3,
|
||||
type: 'credential',
|
||||
@ -180,6 +182,104 @@ const sourceControlCredential = {
|
||||
kubernetes: false,
|
||||
};
|
||||
|
||||
const gceCredential = {
|
||||
id: 9,
|
||||
type: 'credential',
|
||||
url: '/api/v2/credentials/9/',
|
||||
related: {
|
||||
named_url:
|
||||
'/api/v2/credentials/a gce cred++Google Compute Engine+cloud++Default/',
|
||||
created_by: '/api/v2/users/1/',
|
||||
modified_by: '/api/v2/users/1/',
|
||||
organization: '/api/v2/organizations/4/',
|
||||
activity_stream: '/api/v2/credentials/9/activity_stream/',
|
||||
access_list: '/api/v2/credentials/9/access_list/',
|
||||
object_roles: '/api/v2/credentials/9/object_roles/',
|
||||
owner_users: '/api/v2/credentials/9/owner_users/',
|
||||
owner_teams: '/api/v2/credentials/9/owner_teams/',
|
||||
copy: '/api/v2/credentials/9/copy/',
|
||||
input_sources: '/api/v2/credentials/9/input_sources/',
|
||||
credential_type: '/api/v2/credential_types/10/',
|
||||
},
|
||||
summary_fields: {
|
||||
organization: {
|
||||
id: 4,
|
||||
name: 'Default',
|
||||
description: '',
|
||||
},
|
||||
credential_type: {
|
||||
id: 10,
|
||||
name: 'Google Compute Engine',
|
||||
description: '',
|
||||
},
|
||||
created_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
modified_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
object_roles: {
|
||||
admin_role: {
|
||||
description: 'Can manage all aspects of the credential',
|
||||
name: 'Admin',
|
||||
id: 287,
|
||||
},
|
||||
use_role: {
|
||||
description: 'Can use the credential in a job template',
|
||||
name: 'Use',
|
||||
id: 288,
|
||||
},
|
||||
read_role: {
|
||||
description: 'May view settings for the credential',
|
||||
name: 'Read',
|
||||
id: 289,
|
||||
},
|
||||
},
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
delete: true,
|
||||
copy: true,
|
||||
use: true,
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'user',
|
||||
name: 'admin',
|
||||
description: ' ',
|
||||
url: '/api/v2/users/1/',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'organization',
|
||||
name: 'Default',
|
||||
description: '',
|
||||
url: '/api/v2/organizations/4/',
|
||||
},
|
||||
],
|
||||
},
|
||||
created: '2020-04-13T17:33:27.625773Z',
|
||||
modified: '2020-04-13T17:33:27.625882Z',
|
||||
name: 'a gce cred',
|
||||
description: '',
|
||||
organization: 4,
|
||||
credential_type: 10,
|
||||
inputs: {
|
||||
project: 'test123',
|
||||
username: 'test123.iam.gserviceaccount.com',
|
||||
ssh_key_data: '$encrypted$',
|
||||
},
|
||||
kind: 'gce',
|
||||
cloud: true,
|
||||
kubernetes: false,
|
||||
};
|
||||
|
||||
const credentialTypes = [
|
||||
{
|
||||
id: 2,
|
||||
@ -313,36 +413,71 @@ const credentialTypes = [
|
||||
},
|
||||
injectors: {},
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
type: 'credential_type',
|
||||
url: '/api/v2/credential_types/10/',
|
||||
related: {
|
||||
credentials: '/api/v2/credential_types/10/credentials/',
|
||||
activity_stream: '/api/v2/credential_types/10/activity_stream/',
|
||||
},
|
||||
summary_fields: {
|
||||
user_capabilities: {
|
||||
edit: false,
|
||||
delete: false,
|
||||
},
|
||||
},
|
||||
created: '2020-04-09T19:20:27.090665Z',
|
||||
modified: '2020-04-09T19:21:11.575214Z',
|
||||
name: 'Google Compute Engine',
|
||||
description: '',
|
||||
kind: 'cloud',
|
||||
namespace: 'gce',
|
||||
managed_by_tower: true,
|
||||
inputs: {
|
||||
fields: [
|
||||
{
|
||||
id: 'username',
|
||||
label: 'Service Account Email Address',
|
||||
type: 'string',
|
||||
help_text:
|
||||
'The email address assigned to the Google Compute Engine service account.',
|
||||
},
|
||||
{
|
||||
id: 'project',
|
||||
label: 'Project',
|
||||
type: 'string',
|
||||
help_text:
|
||||
'The Project ID is the GCE assigned identification. It is often constructed as three words or two words followed by a three-digit number. Examples: project-id-000 and another-project-id',
|
||||
},
|
||||
{
|
||||
id: 'ssh_key_data',
|
||||
label: 'RSA Private Key',
|
||||
type: 'string',
|
||||
format: 'ssh_private_key',
|
||||
secret: true,
|
||||
multiline: true,
|
||||
help_text:
|
||||
'Paste the contents of the PEM file associated with the service account email.',
|
||||
},
|
||||
],
|
||||
required: ['username', 'ssh_key_data'],
|
||||
},
|
||||
injectors: {},
|
||||
},
|
||||
];
|
||||
|
||||
describe('<CredentialForm />', () => {
|
||||
let wrapper;
|
||||
let onCancel;
|
||||
let onSubmit;
|
||||
const onCancel = jest.fn();
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
const addFieldExpects = () => {
|
||||
expect(wrapper.find('FormGroup').length).toBe(4);
|
||||
expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Username"]').length).toBe(0);
|
||||
expect(wrapper.find('FormGroup[label="Password"]').length).toBe(0);
|
||||
expect(wrapper.find('FormGroup[label="SSH Private Key"]').length).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Signed SSH Certificate"]').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Private Key Passphrase"]').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Privelege Escalation Method"]').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Privilege Escalation Username"]').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Privilege Escalation Password"]').length
|
||||
).toBe(0);
|
||||
};
|
||||
|
||||
const machineFieldExpects = () => {
|
||||
@ -371,6 +506,7 @@ describe('<CredentialForm />', () => {
|
||||
};
|
||||
|
||||
const sourceFieldExpects = () => {
|
||||
expect(wrapper.find('FormGroup').length).toBe(8);
|
||||
expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
|
||||
@ -378,139 +514,218 @@ describe('<CredentialForm />', () => {
|
||||
expect(wrapper.find('FormGroup[label="Username"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Password"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="SSH Private Key"]').length).toBe(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Signed SSH Certificate"]').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Private Key Passphrase"]').length
|
||||
).toBe(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Privelege Escalation Method"]').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Privilege Escalation Username"]').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Privilege Escalation Password"]').length
|
||||
).toBe(0);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
onCancel = jest.fn();
|
||||
onSubmit = jest.fn();
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credential={machineCredential}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const gceFieldExpects = () => {
|
||||
expect(wrapper.find('FormGroup').length).toBe(8);
|
||||
expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Organization"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Credential Type"]').length).toBe(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Service account JSON file"]').length
|
||||
).toBe(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Service account email address"]').length
|
||||
).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Project"]').length).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="RSA private key"]').length).toBe(1);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('Initially renders successfully', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
});
|
||||
|
||||
test('should display form fields on add properly', () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
addFieldExpects();
|
||||
});
|
||||
|
||||
test('should display form fields for machine credential properly', () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credential={machineCredential}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
machineFieldExpects();
|
||||
});
|
||||
|
||||
test('should display form fields for source control credential properly', () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credential={sourceControlCredential}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
sourceFieldExpects();
|
||||
});
|
||||
|
||||
test('should update form values', async () => {
|
||||
// name and description change
|
||||
act(() => {
|
||||
wrapper.find('input#credential-name').simulate('change', {
|
||||
target: { value: 'new Foo', name: 'name' },
|
||||
});
|
||||
wrapper.find('input#credential-description').simulate('change', {
|
||||
target: { value: 'new Bar', name: 'description' },
|
||||
describe('Add', () => {
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input#credential-name').prop('value')).toEqual(
|
||||
'new Foo'
|
||||
);
|
||||
expect(wrapper.find('input#credential-description').prop('value')).toEqual(
|
||||
'new Bar'
|
||||
);
|
||||
// organization change
|
||||
act(() => {
|
||||
wrapper.find('OrganizationLookup').invoke('onBlur')();
|
||||
wrapper.find('OrganizationLookup').invoke('onChange')({
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
test('should display form fields on add properly', async () => {
|
||||
addFieldExpects();
|
||||
});
|
||||
test('should update form values', async () => {
|
||||
// name and description change
|
||||
await act(async () => {
|
||||
wrapper.find('input#credential-name').simulate('change', {
|
||||
target: { value: 'new Foo', name: 'name' },
|
||||
});
|
||||
wrapper.find('input#credential-description').simulate('change', {
|
||||
target: { value: 'new Bar', name: 'description' },
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input#credential-name').prop('value')).toEqual(
|
||||
'new Foo'
|
||||
);
|
||||
expect(
|
||||
wrapper.find('input#credential-description').prop('value')
|
||||
).toEqual('new Bar');
|
||||
// organization change
|
||||
await act(async () => {
|
||||
wrapper.find('OrganizationLookup').invoke('onBlur')();
|
||||
wrapper.find('OrganizationLookup').invoke('onChange')({
|
||||
id: 3,
|
||||
name: 'organization',
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
|
||||
id: 3,
|
||||
name: 'organization',
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
|
||||
id: 3,
|
||||
name: 'organization',
|
||||
test('should display cred type subform when scm type select has a value', async () => {
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
.find('AnsibleSelect[id="credential_type"]')
|
||||
.invoke('onChange')(null, 1);
|
||||
});
|
||||
wrapper.update();
|
||||
machineFieldExpects();
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
.find('AnsibleSelect[id="credential_type"]')
|
||||
.invoke('onChange')(null, 2);
|
||||
});
|
||||
wrapper.update();
|
||||
sourceFieldExpects();
|
||||
});
|
||||
test('should update expected fields when gce service account json file uploaded', async () => {
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
.find('AnsibleSelect[id="credential_type"]')
|
||||
.invoke('onChange')(null, 10);
|
||||
});
|
||||
wrapper.update();
|
||||
gceFieldExpects();
|
||||
expect(wrapper.find('input#credential-username').prop('value')).toBe('');
|
||||
expect(wrapper.find('input#credential-project').prop('value')).toBe('');
|
||||
expect(wrapper.find('textarea#credential-sshKeyData').prop('value')).toBe(
|
||||
''
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('FileUpload').invoke('onChange')({
|
||||
name: 'foo.json',
|
||||
text: () =>
|
||||
'{"client_email":"testemail@ansible.com","project_id":"test123","private_key":"-----BEGIN PRIVATE KEY-----\\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\n-----END PRIVATE KEY-----\\n"}',
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input#credential-username').prop('value')).toBe(
|
||||
'testemail@ansible.com'
|
||||
);
|
||||
expect(wrapper.find('input#credential-project').prop('value')).toBe(
|
||||
'test123'
|
||||
);
|
||||
expect(wrapper.find('textarea#credential-sshKeyData').prop('value')).toBe(
|
||||
'-----BEGIN PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n-----END PRIVATE KEY-----\n'
|
||||
);
|
||||
});
|
||||
test('should clear expected fields when file clear button clicked', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('FileUploadField').invoke('onClearButtonClick')();
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('input#credential-username').prop('value')).toBe('');
|
||||
expect(wrapper.find('input#credential-project').prop('value')).toBe('');
|
||||
expect(wrapper.find('textarea#credential-sshKeyData').prop('value')).toBe(
|
||||
''
|
||||
);
|
||||
});
|
||||
test('should show error when error thrown parsing JSON', async () => {
|
||||
expect(wrapper.find('#credential-gce-file-helper').text()).toBe(
|
||||
'Select a JSON formatted service account key to autopopulate the following fields.'
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('FileUpload').invoke('onChange')({
|
||||
name: 'foo.json',
|
||||
text: () => '{not good json}',
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('#credential-gce-file-helper').text()).toBe(
|
||||
'There was an error parsing the file. Please check the file formatting and try again.'
|
||||
);
|
||||
});
|
||||
test('should call handleCancel when Cancel button is clicked', async () => {
|
||||
expect(onCancel).not.toHaveBeenCalled();
|
||||
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||
expect(onCancel).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test('should display cred type subform when scm type select has a value', async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
addFieldExpects();
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
.find('AnsibleSelect[id="credential_type"]')
|
||||
.invoke('onChange')(null, 1);
|
||||
describe('Edit', () => {
|
||||
afterEach(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
wrapper.update();
|
||||
machineFieldExpects();
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
.find('AnsibleSelect[id="credential_type"]')
|
||||
.invoke('onChange')(null, 2);
|
||||
});
|
||||
wrapper.update();
|
||||
sourceFieldExpects();
|
||||
});
|
||||
test('Initially renders successfully', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credential={machineCredential}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
test('should call handleCancel when Cancel button is clicked', async () => {
|
||||
expect(onCancel).not.toHaveBeenCalled();
|
||||
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||
expect(onCancel).toBeCalled();
|
||||
expect(wrapper.length).toBe(1);
|
||||
});
|
||||
|
||||
test('should display form fields for machine credential properly', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credential={machineCredential}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
machineFieldExpects();
|
||||
});
|
||||
|
||||
test('should display form fields for source control credential properly', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credential={sourceControlCredential}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
sourceFieldExpects();
|
||||
});
|
||||
|
||||
test('should display form fields for gce credential properly', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialForm
|
||||
onCancel={onCancel}
|
||||
onSubmit={onSubmit}
|
||||
credential={gceCredential}
|
||||
credentialTypes={credentialTypes}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
gceFieldExpects();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
import React, { useState } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
import { FileUpload, FormGroup } from '@patternfly/react-core';
|
||||
import FormField from '@components/FormField';
|
||||
import { FormColumnLayout, FormFullWidthLayout } from '@components/FormLayout';
|
||||
import { required } from '@util/validators';
|
||||
|
||||
const GoogleComputeEngineSubForm = ({ i18n }) => {
|
||||
const [fileError, setFileError] = useState(null);
|
||||
const [filename, setFilename] = useState('');
|
||||
const [file, setFile] = useState('');
|
||||
const inputsUsernameHelpers = useField({
|
||||
name: 'inputs.username',
|
||||
})[2];
|
||||
const inputsProjectHelpers = useField({
|
||||
name: 'inputs.project',
|
||||
})[2];
|
||||
const inputsSSHKeyDataHelpers = useField({
|
||||
name: 'inputs.ssh_key_data',
|
||||
})[2];
|
||||
|
||||
return (
|
||||
<FormColumnLayout>
|
||||
<FormGroup
|
||||
fieldId="credential-gce-file"
|
||||
isValid={!fileError}
|
||||
label={i18n._(t`Service account JSON file`)}
|
||||
helperText={i18n._(
|
||||
t`Select a JSON formatted service account key to autopopulate the following fields.`
|
||||
)}
|
||||
helperTextInvalid={fileError}
|
||||
>
|
||||
<FileUpload
|
||||
id="credential-gce-file"
|
||||
value={file}
|
||||
filename={filename}
|
||||
filenamePlaceholder={i18n._(t`Choose a .json file`)}
|
||||
onChange={async value => {
|
||||
if (value) {
|
||||
try {
|
||||
setFile(value);
|
||||
setFilename(value.name);
|
||||
const fileText = await value.text();
|
||||
const fileJSON = JSON.parse(fileText);
|
||||
if (
|
||||
!fileJSON.client_email &&
|
||||
!fileJSON.project_id &&
|
||||
!fileJSON.private_key
|
||||
) {
|
||||
setFileError(
|
||||
i18n._(
|
||||
t`Expected at least one of client_email, project_id or private_key to be present in the file.`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
inputsUsernameHelpers.setValue(fileJSON.client_email || '');
|
||||
inputsProjectHelpers.setValue(fileJSON.project_id || '');
|
||||
inputsSSHKeyDataHelpers.setValue(fileJSON.private_key || '');
|
||||
setFileError(null);
|
||||
}
|
||||
} catch {
|
||||
setFileError(
|
||||
i18n._(
|
||||
t`There was an error parsing the file. Please check the file formatting and try again.`
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setFile('');
|
||||
setFilename('');
|
||||
inputsUsernameHelpers.setValue('');
|
||||
inputsProjectHelpers.setValue('');
|
||||
inputsSSHKeyDataHelpers.setValue('');
|
||||
setFileError(null);
|
||||
}
|
||||
}}
|
||||
dropzoneProps={{
|
||||
accept: '.json',
|
||||
onDropRejected: () => {
|
||||
setFileError(
|
||||
i18n._(
|
||||
t`File upload rejected. Please select a single .json file.`
|
||||
)
|
||||
);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormField
|
||||
id="credential-username"
|
||||
label={i18n._(t`Service account email address`)}
|
||||
name="inputs.username"
|
||||
type="email"
|
||||
validate={required(null, i18n)}
|
||||
isRequired
|
||||
/>
|
||||
<FormField
|
||||
id="credential-project"
|
||||
label={i18n._(t`Project`)}
|
||||
name="inputs.project"
|
||||
type="text"
|
||||
/>
|
||||
<FormFullWidthLayout>
|
||||
<FormField
|
||||
id="credential-sshKeyData"
|
||||
label={i18n._(t`RSA private key`)}
|
||||
name="inputs.ssh_key_data"
|
||||
type="textarea"
|
||||
rows={6}
|
||||
validate={required(null, i18n)}
|
||||
isRequired
|
||||
/>
|
||||
</FormFullWidthLayout>
|
||||
</FormColumnLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default withI18n()(GoogleComputeEngineSubForm);
|
||||
@ -26,6 +26,7 @@ export const SSHKeyDataField = withI18n()(({ i18n }) => (
|
||||
label={i18n._(t`SSH Private Key`)}
|
||||
name="inputs.ssh_key_data"
|
||||
type="textarea"
|
||||
rows={6}
|
||||
/>
|
||||
));
|
||||
|
||||
|
||||
@ -12,16 +12,14 @@ import {
|
||||
} from './SharedFields';
|
||||
|
||||
const SourceControlSubForm = () => (
|
||||
<>
|
||||
<FormColumnLayout>
|
||||
<UsernameFormField />
|
||||
<PasswordFormField />
|
||||
<SSHKeyUnlockField />
|
||||
</FormColumnLayout>
|
||||
<FormColumnLayout>
|
||||
<UsernameFormField />
|
||||
<PasswordFormField />
|
||||
<SSHKeyUnlockField />
|
||||
<FormFullWidthLayout>
|
||||
<SSHKeyDataField />
|
||||
</FormFullWidthLayout>
|
||||
</>
|
||||
</FormColumnLayout>
|
||||
);
|
||||
|
||||
export default withI18n()(SourceControlSubForm);
|
||||
|
||||
@ -1,2 +1,5 @@
|
||||
export {
|
||||
default as GoogleComputeEngineSubForm,
|
||||
} from './GoogleComputeEngineSubForm';
|
||||
export { default as ManualSubForm } from './ManualSubForm';
|
||||
export { default as SourceControlSubForm } from './SourceControlSubForm';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user