mirror of
https://github.com/ansible/awx.git
synced 2026-05-17 14:27:42 -02:30
add credential form and add edit routes
This commit is contained in:
@@ -4,6 +4,14 @@ class Credentials extends Base {
|
|||||||
constructor(http) {
|
constructor(http) {
|
||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = '/api/v2/credentials/';
|
this.baseUrl = '/api/v2/credentials/';
|
||||||
|
|
||||||
|
this.readAccessList = this.readAccessList.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
readAccessList(id, params) {
|
||||||
|
return this.http.get(`${this.baseUrl}${id}/access_list/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { ResourceAccessList } from '@components/ResourceAccessList';
|
|||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
import CredentialDetail from './CredentialDetail';
|
import CredentialDetail from './CredentialDetail';
|
||||||
|
import CredentialEdit from './CredentialEdit';
|
||||||
import { CredentialsAPI } from '@api';
|
import { CredentialsAPI } from '@api';
|
||||||
|
|
||||||
function Credential({ i18n, setBreadcrumb }) {
|
function Credential({ i18n, setBreadcrumb }) {
|
||||||
@@ -100,6 +101,11 @@ function Credential({ i18n, setBreadcrumb }) {
|
|||||||
path="/credentials/:id/details"
|
path="/credentials/:id/details"
|
||||||
render={() => <CredentialDetail credential={credential} />}
|
render={() => <CredentialDetail credential={credential} />}
|
||||||
/>,
|
/>,
|
||||||
|
<Route
|
||||||
|
key="edit"
|
||||||
|
path="/credentials/:id/edit"
|
||||||
|
render={() => <CredentialEdit credential={credential} />}
|
||||||
|
/>,
|
||||||
credential.organization && (
|
credential.organization && (
|
||||||
<Route
|
<Route
|
||||||
key="access"
|
key="access"
|
||||||
@@ -122,7 +128,7 @@ function Credential({ i18n, setBreadcrumb }) {
|
|||||||
<Link to={`/credentials/${match.params.id}/details`}>
|
<Link to={`/credentials/${match.params.id}/details`}>
|
||||||
{i18n._(`View Credential Details`)}
|
{i18n._(`View Credential Details`)}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</ContentError>
|
</ContentError>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,84 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Card, CardBody, PageSection } from '@patternfly/react-core';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { PageSection, Card } from '@patternfly/react-core';
|
||||||
|
import { CardBody } from '@components/Card';
|
||||||
|
import ContentError from '@components/ContentError';
|
||||||
|
import ContentLoading from '@components/ContentLoading';
|
||||||
|
|
||||||
function CredentialAdd() {
|
import { CredentialTypesAPI, CredentialsAPI } from '@api';
|
||||||
|
import CredentialForm from '../shared/CredentialForm';
|
||||||
|
|
||||||
|
function CredentialAdd({ me }) {
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [credentialTypes, setCredentialTypes] = useState(null);
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { results: loadedCredentialTypes },
|
||||||
|
} = await CredentialTypesAPI.read({ or__kind: ['scm', 'ssh'] });
|
||||||
|
setCredentialTypes(loadedCredentialTypes);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
history.push('/credentials');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async values => {
|
||||||
|
const { organization, ...remainingValues } = values;
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { id: credentialId },
|
||||||
|
} = await CredentialsAPI.create({
|
||||||
|
user: (me && me.id) || null,
|
||||||
|
organization: (organization && organization.id) || null,
|
||||||
|
...remainingValues,
|
||||||
|
});
|
||||||
|
const url = `/credentials/${credentialId}/details`;
|
||||||
|
history.push(`${url}`);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<ContentError error={error} />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isLoading) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<CardBody>Coming soon :)</CardBody>
|
<CardBody>
|
||||||
|
<CredentialForm
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
credentialTypes={credentialTypes}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { CredentialAdd as _CredentialAdd };
|
||||||
export default CredentialAdd;
|
export default CredentialAdd;
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
import { sleep } from '@testUtils/testUtils';
|
||||||
|
|
||||||
|
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||||
|
import CredentialAdd from './CredentialAdd';
|
||||||
|
|
||||||
|
jest.mock('@api');
|
||||||
|
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: 'credential_type',
|
||||||
|
url: '/api/v2/credential_types/2/',
|
||||||
|
related: {
|
||||||
|
credentials: '/api/v2/credential_types/2/credentials/',
|
||||||
|
activity_stream: '/api/v2/credential_types/2/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
delete: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-02-12T19:42:43.551238Z',
|
||||||
|
modified: '2020-02-12T19:43:03.164800Z',
|
||||||
|
name: 'Source Control',
|
||||||
|
description: '',
|
||||||
|
kind: 'scm',
|
||||||
|
namespace: 'scm',
|
||||||
|
managed_by_tower: true,
|
||||||
|
inputs: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_data',
|
||||||
|
label: 'SCM Private Key',
|
||||||
|
type: 'string',
|
||||||
|
format: 'ssh_private_key',
|
||||||
|
secret: true,
|
||||||
|
multiline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_unlock',
|
||||||
|
label: 'Private Key Passphrase',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
injectors: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'credential_type',
|
||||||
|
url: '/api/v2/credential_types/1/',
|
||||||
|
related: {
|
||||||
|
credentials: '/api/v2/credential_types/1/credentials/',
|
||||||
|
activity_stream: '/api/v2/credential_types/1/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
delete: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-02-12T19:42:43.539626Z',
|
||||||
|
modified: '2020-02-12T19:43:03.159739Z',
|
||||||
|
name: 'Machine',
|
||||||
|
description: '',
|
||||||
|
kind: 'ssh',
|
||||||
|
namespace: 'ssh',
|
||||||
|
managed_by_tower: true,
|
||||||
|
inputs: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_data',
|
||||||
|
label: 'SSH Private Key',
|
||||||
|
type: 'string',
|
||||||
|
format: 'ssh_private_key',
|
||||||
|
secret: true,
|
||||||
|
multiline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_public_key_data',
|
||||||
|
label: 'Signed SSH Certificate',
|
||||||
|
type: 'string',
|
||||||
|
multiline: true,
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_unlock',
|
||||||
|
label: 'Private Key Passphrase',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_method',
|
||||||
|
label: 'Privilege Escalation Method',
|
||||||
|
type: 'string',
|
||||||
|
help_text:
|
||||||
|
'Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_username',
|
||||||
|
label: 'Privilege Escalation Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_password',
|
||||||
|
label: 'Privilege Escalation Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
injectors: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
CredentialsAPI.create.mockResolvedValue({ data: { id: 13 } });
|
||||||
|
|
||||||
|
describe('<CredentialAdd />', () => {
|
||||||
|
let wrapper;
|
||||||
|
let history;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
history = createMemoryHistory({ initialEntries: ['/credentials'] });
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<CredentialAdd />, {
|
||||||
|
context: { router: { history } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Initially renders successfully', () => {
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
});
|
||||||
|
test('handleSubmit should call the api and redirect to details page', async () => {
|
||||||
|
await waitForElement(wrapper, 'isLoading', el => el.length === 0);
|
||||||
|
|
||||||
|
wrapper.find('CredentialForm').prop('onSubmit')({
|
||||||
|
user: 1,
|
||||||
|
organization: null,
|
||||||
|
name: 'foo',
|
||||||
|
description: 'bar',
|
||||||
|
credential_type: '2',
|
||||||
|
inputs: {},
|
||||||
|
});
|
||||||
|
await sleep(1);
|
||||||
|
expect(CredentialsAPI.create).toHaveBeenCalledWith({
|
||||||
|
user: 1,
|
||||||
|
organization: null,
|
||||||
|
name: 'foo',
|
||||||
|
description: 'bar',
|
||||||
|
credential_type: '2',
|
||||||
|
inputs: {},
|
||||||
|
});
|
||||||
|
expect(history.location.pathname).toBe('/credentials/13/details');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleCancel should return the user back to the inventories list', async () => {
|
||||||
|
await waitForElement(wrapper, 'isLoading', el => el.length === 0);
|
||||||
|
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
|
||||||
|
expect(history.location.pathname).toEqual('/credentials');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -82,7 +82,7 @@ function CredentialDetail({ i18n, credential }) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (secret === true) {
|
} else if (secret === true) {
|
||||||
detail = <Detail key={id} label={label} value={i18n._(t`Encrypted`)} />;
|
detail = null;
|
||||||
} else {
|
} else {
|
||||||
detail = <Detail key={id} label={label} value={inputs[id]} />;
|
detail = <Detail key={id} label={label} value={inputs[id]} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ describe('<CredentialDetail />', () => {
|
|||||||
mockCredential.summary_fields.credential_type.name
|
mockCredential.summary_fields.credential_type.name
|
||||||
);
|
);
|
||||||
expectDetailToMatch(wrapper, 'Username', mockCredential.inputs.username);
|
expectDetailToMatch(wrapper, 'Username', mockCredential.inputs.username);
|
||||||
expectDetailToMatch(wrapper, 'Password', 'Encrypted');
|
|
||||||
expectDetailToMatch(wrapper, 'SSH Private Key', 'Encrypted');
|
|
||||||
expectDetailToMatch(wrapper, 'Signed SSH Certificate', 'Encrypted');
|
|
||||||
expectDetailToMatch(wrapper, 'Private Key Passphrase', 'Encrypted');
|
|
||||||
expectDetailToMatch(
|
expectDetailToMatch(
|
||||||
wrapper,
|
wrapper,
|
||||||
'Privilege Escalation Method',
|
'Privilege Escalation Method',
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { object } from 'prop-types';
|
||||||
|
|
||||||
|
import { CardBody } from '@components/Card';
|
||||||
|
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||||
|
import ContentError from '@components/ContentError';
|
||||||
|
import ContentLoading from '@components/ContentLoading';
|
||||||
|
import CredentialForm from '../shared/CredentialForm';
|
||||||
|
|
||||||
|
function CredentialEdit({ credential, me }) {
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [credentialTypes, setCredentialTypes] = useState(null);
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { results: loadedCredentialTypes },
|
||||||
|
} = await CredentialTypesAPI.read({ or__kind: ['scm', 'ssh'] });
|
||||||
|
setCredentialTypes(loadedCredentialTypes);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
const url = `/credentials/${credential.id}/details`;
|
||||||
|
|
||||||
|
history.push(`${url}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async values => {
|
||||||
|
const { organization, ...remainingValues } = values;
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { id: credentialId },
|
||||||
|
} = await CredentialsAPI.update(credential.id, {
|
||||||
|
user: (me && me.id) || null,
|
||||||
|
organization: (organization && organization.id) || null,
|
||||||
|
...remainingValues,
|
||||||
|
});
|
||||||
|
const url = `/credentials/${credentialId}/details`;
|
||||||
|
history.push(`${url}`);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ContentError />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardBody>
|
||||||
|
<CredentialForm
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
credential={credential}
|
||||||
|
credentialTypes={credentialTypes}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CredentialEdit.proptype = {
|
||||||
|
inventory: object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CredentialEdit as _CredentialEdit };
|
||||||
|
export default CredentialEdit;
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
import { sleep } from '@testUtils/testUtils';
|
||||||
|
|
||||||
|
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||||
|
import CredentialEdit from './CredentialEdit';
|
||||||
|
|
||||||
|
jest.mock('@api');
|
||||||
|
|
||||||
|
const mockCredential = {
|
||||||
|
id: 3,
|
||||||
|
type: 'credential',
|
||||||
|
url: '/api/v2/credentials/3/',
|
||||||
|
related: {
|
||||||
|
named_url: '/api/v2/credentials/oersdgfasf++Machine+ssh++org/',
|
||||||
|
created_by: '/api/v2/users/1/',
|
||||||
|
modified_by: '/api/v2/users/1/',
|
||||||
|
organization: '/api/v2/organizations/1/',
|
||||||
|
activity_stream: '/api/v2/credentials/3/activity_stream/',
|
||||||
|
access_list: '/api/v2/credentials/3/access_list/',
|
||||||
|
object_roles: '/api/v2/credentials/3/object_roles/',
|
||||||
|
owner_users: '/api/v2/credentials/3/owner_users/',
|
||||||
|
owner_teams: '/api/v2/credentials/3/owner_teams/',
|
||||||
|
copy: '/api/v2/credentials/3/copy/',
|
||||||
|
input_sources: '/api/v2/credentials/3/input_sources/',
|
||||||
|
credential_type: '/api/v2/credential_types/1/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
organization: {
|
||||||
|
id: 1,
|
||||||
|
name: 'org',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
credential_type: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Machine',
|
||||||
|
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: 36,
|
||||||
|
},
|
||||||
|
use_role: {
|
||||||
|
description: 'Can use the credential in a job template',
|
||||||
|
name: 'Use',
|
||||||
|
id: 37,
|
||||||
|
},
|
||||||
|
read_role: {
|
||||||
|
description: 'May view settings for the credential',
|
||||||
|
name: 'Read',
|
||||||
|
id: 38,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
copy: true,
|
||||||
|
use: true,
|
||||||
|
},
|
||||||
|
owners: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'user',
|
||||||
|
name: 'admin',
|
||||||
|
description: ' ',
|
||||||
|
url: '/api/v2/users/1/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'organization',
|
||||||
|
name: 'org',
|
||||||
|
description: '',
|
||||||
|
url: '/api/v2/organizations/1/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
created: '2020-02-18T15:35:04.563928Z',
|
||||||
|
modified: '2020-02-18T15:35:04.563957Z',
|
||||||
|
name: 'oersdgfasf',
|
||||||
|
description: '',
|
||||||
|
organization: 1,
|
||||||
|
credential_type: 1,
|
||||||
|
inputs: {},
|
||||||
|
kind: 'ssh',
|
||||||
|
cloud: false,
|
||||||
|
kubernetes: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: 'credential_type',
|
||||||
|
url: '/api/v2/credential_types/2/',
|
||||||
|
related: {
|
||||||
|
credentials: '/api/v2/credential_types/2/credentials/',
|
||||||
|
activity_stream: '/api/v2/credential_types/2/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
delete: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-02-12T19:42:43.551238Z',
|
||||||
|
modified: '2020-02-12T19:43:03.164800Z',
|
||||||
|
name: 'Source Control',
|
||||||
|
description: '',
|
||||||
|
kind: 'scm',
|
||||||
|
namespace: 'scm',
|
||||||
|
managed_by_tower: true,
|
||||||
|
inputs: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_data',
|
||||||
|
label: 'SCM Private Key',
|
||||||
|
type: 'string',
|
||||||
|
format: 'ssh_private_key',
|
||||||
|
secret: true,
|
||||||
|
multiline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_unlock',
|
||||||
|
label: 'Private Key Passphrase',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
injectors: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'credential_type',
|
||||||
|
url: '/api/v2/credential_types/1/',
|
||||||
|
related: {
|
||||||
|
credentials: '/api/v2/credential_types/1/credentials/',
|
||||||
|
activity_stream: '/api/v2/credential_types/1/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
delete: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-02-12T19:42:43.539626Z',
|
||||||
|
modified: '2020-02-12T19:43:03.159739Z',
|
||||||
|
name: 'Machine',
|
||||||
|
description: '',
|
||||||
|
kind: 'ssh',
|
||||||
|
namespace: 'ssh',
|
||||||
|
managed_by_tower: true,
|
||||||
|
inputs: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_data',
|
||||||
|
label: 'SSH Private Key',
|
||||||
|
type: 'string',
|
||||||
|
format: 'ssh_private_key',
|
||||||
|
secret: true,
|
||||||
|
multiline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_public_key_data',
|
||||||
|
label: 'Signed SSH Certificate',
|
||||||
|
type: 'string',
|
||||||
|
multiline: true,
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_unlock',
|
||||||
|
label: 'Private Key Passphrase',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_method',
|
||||||
|
label: 'Privilege Escalation Method',
|
||||||
|
type: 'string',
|
||||||
|
help_text:
|
||||||
|
'Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_username',
|
||||||
|
label: 'Privilege Escalation Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_password',
|
||||||
|
label: 'Privilege Escalation Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
injectors: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
CredentialsAPI.update.mockResolvedValue({ data: { id: 3 } });
|
||||||
|
|
||||||
|
describe('<CredentialEdit />', () => {
|
||||||
|
let wrapper;
|
||||||
|
let history;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
history = createMemoryHistory({ initialEntries: ['/credentials'] });
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<CredentialEdit credential={mockCredential} />,
|
||||||
|
{
|
||||||
|
context: { router: { history } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initially renders successfully', async () => {
|
||||||
|
expect(wrapper.find('CredentialEdit').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleCancel returns the user to credential detail', async () => {
|
||||||
|
await waitForElement(wrapper, 'isLoading', el => el.length === 0);
|
||||||
|
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
|
||||||
|
expect(history.location.pathname).toEqual('/credentials/3/details');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleSubmit should post to the api', async () => {
|
||||||
|
await waitForElement(wrapper, 'isLoading', el => el.length === 0);
|
||||||
|
|
||||||
|
wrapper.find('CredentialForm').prop('onSubmit')({
|
||||||
|
user: 1,
|
||||||
|
organization: null,
|
||||||
|
name: 'foo',
|
||||||
|
description: 'bar',
|
||||||
|
credential_type: '2',
|
||||||
|
inputs: {},
|
||||||
|
});
|
||||||
|
await sleep(1);
|
||||||
|
expect(CredentialsAPI.update).toHaveBeenCalledWith(3, {
|
||||||
|
user: 1,
|
||||||
|
organization: null,
|
||||||
|
name: 'foo',
|
||||||
|
description: 'bar',
|
||||||
|
credential_type: '2',
|
||||||
|
inputs: {},
|
||||||
|
});
|
||||||
|
expect(history.location.pathname).toBe('/credentials/3/details');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './CredentialEdit';
|
||||||
183
awx/ui_next/src/screens/Credential/shared/CredentialForm.jsx
Normal file
183
awx/ui_next/src/screens/Credential/shared/CredentialForm.jsx
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Formik, useField } from 'formik';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { func, shape } from 'prop-types';
|
||||||
|
import { Form, FormGroup, Title } from '@patternfly/react-core';
|
||||||
|
import FormField from '@components/FormField';
|
||||||
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
|
import { required } from '@util/validators';
|
||||||
|
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
||||||
|
import { FormColumnLayout, SubFormLayout } from '@components/FormLayout';
|
||||||
|
import { ManualSubForm, SourceControlSubForm } from './CredentialSubForms';
|
||||||
|
|
||||||
|
function CredentialFormFields({
|
||||||
|
i18n,
|
||||||
|
credentialTypes,
|
||||||
|
formik,
|
||||||
|
initialValues,
|
||||||
|
}) {
|
||||||
|
const [orgField, orgMeta, orgHelpers] = useField('organization');
|
||||||
|
const [credTypeField, credTypeMeta, credTypeHelpers] = useField({
|
||||||
|
name: 'credential_type',
|
||||||
|
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||||
|
});
|
||||||
|
|
||||||
|
const credentialTypeOptions = Object.keys(credentialTypes).map(key => {
|
||||||
|
return {
|
||||||
|
value: credentialTypes[key].id,
|
||||||
|
key: credentialTypes[key].kind,
|
||||||
|
label: credentialTypes[key].name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const scmCredentialTypeId = Object.keys(credentialTypes)
|
||||||
|
.filter(key => credentialTypes[key].kind === 'scm')
|
||||||
|
.map(key => credentialTypes[key].id)[0];
|
||||||
|
const sshCredentialTypeId = Object.keys(credentialTypes)
|
||||||
|
.filter(key => credentialTypes[key].kind === 'ssh')
|
||||||
|
.map(key => credentialTypes[key].id)[0];
|
||||||
|
|
||||||
|
const resetSubFormFields = (value, form) => {
|
||||||
|
Object.keys(form.initialValues.inputs).forEach(label => {
|
||||||
|
if (parseInt(value, 10) === form.initialValues.credential_type) {
|
||||||
|
form.setFieldValue(`inputs.${label}`, initialValues.inputs[label]);
|
||||||
|
} else {
|
||||||
|
form.setFieldValue(`inputs.${label}`, undefined);
|
||||||
|
}
|
||||||
|
form.setFieldTouched(`inputs.${label}`, false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
id="credential-name"
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
validate={required(null, i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="credential-description"
|
||||||
|
label={i18n._(t`Description`)}
|
||||||
|
name="description"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<OrganizationLookup
|
||||||
|
helperTextInvalid={orgMeta.error}
|
||||||
|
isValid={!orgMeta.touched || !orgMeta.error}
|
||||||
|
onBlur={() => orgHelpers.setTouched()}
|
||||||
|
onChange={value => {
|
||||||
|
orgHelpers.setValue(value);
|
||||||
|
}}
|
||||||
|
value={orgField.value}
|
||||||
|
touched={orgMeta.touched}
|
||||||
|
error={orgMeta.error}
|
||||||
|
/>
|
||||||
|
<FormGroup
|
||||||
|
fieldId="credential-credentialType"
|
||||||
|
helperTextInvalid={credTypeMeta.error}
|
||||||
|
isRequired
|
||||||
|
isValid={!credTypeMeta.touched || !credTypeMeta.error}
|
||||||
|
label={i18n._(t`Credential Type`)}
|
||||||
|
>
|
||||||
|
<AnsibleSelect
|
||||||
|
{...credTypeField}
|
||||||
|
id="credential_type"
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
key: '',
|
||||||
|
label: i18n._(t`Choose a Credential Type`),
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
...credentialTypeOptions,
|
||||||
|
]}
|
||||||
|
onChange={(event, value) => {
|
||||||
|
credTypeHelpers.setValue(value);
|
||||||
|
resetSubFormFields(value, formik);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
{formik.values.credential_type !== undefined &&
|
||||||
|
formik.values.credential_type !== '' && (
|
||||||
|
<SubFormLayout>
|
||||||
|
<Title size="md">{i18n._(t`Type Details`)}</Title>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
[sshCredentialTypeId]: <ManualSubForm />,
|
||||||
|
[scmCredentialTypeId]: <SourceControlSubForm />,
|
||||||
|
}[formik.values.credential_type]
|
||||||
|
}
|
||||||
|
</SubFormLayout>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CredentialForm({ credential = {}, onSubmit, onCancel, ...rest }) {
|
||||||
|
const initialValues = {
|
||||||
|
name: credential.name || undefined,
|
||||||
|
description: credential.description || undefined,
|
||||||
|
organization:
|
||||||
|
(credential.summary_fields && credential.summary_fields.organization) ||
|
||||||
|
null,
|
||||||
|
credential_type: credential.credential_type || undefined,
|
||||||
|
inputs: {
|
||||||
|
username: (credential.inputs && credential.inputs.username) || undefined,
|
||||||
|
password: (credential.inputs && credential.inputs.password) || undefined,
|
||||||
|
ssh_key_data:
|
||||||
|
(credential.inputs && credential.inputs.ssh_key_data) || undefined,
|
||||||
|
ssh_public_key_data:
|
||||||
|
(credential.inputs && credential.inputs.ssh_public_key_data) ||
|
||||||
|
undefined,
|
||||||
|
ssh_key_unlock:
|
||||||
|
(credential.inputs && credential.inputs.ssh_key_unlock) || undefined,
|
||||||
|
become_method:
|
||||||
|
(credential.inputs && credential.inputs.become_method) || undefined,
|
||||||
|
become_username:
|
||||||
|
(credential.inputs && credential.inputs.become_username) || undefined,
|
||||||
|
become_password:
|
||||||
|
(credential.inputs && credential.inputs.become_password) || undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={values => {
|
||||||
|
onSubmit(values);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formik => (
|
||||||
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
|
<FormColumnLayout>
|
||||||
|
<CredentialFormFields
|
||||||
|
formik={formik}
|
||||||
|
initialValues={initialValues}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<FormActionGroup
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
/>
|
||||||
|
</FormColumnLayout>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CredentialForm.proptype = {
|
||||||
|
handleSubmit: func.isRequired,
|
||||||
|
handleCancel: func.isRequired,
|
||||||
|
credential: shape({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
CredentialForm.defaultProps = {
|
||||||
|
credential: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(CredentialForm);
|
||||||
@@ -0,0 +1,516 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
|
|
||||||
|
import CredentialForm from './CredentialForm';
|
||||||
|
|
||||||
|
const machineCredential = {
|
||||||
|
id: 3,
|
||||||
|
type: 'credential',
|
||||||
|
url: '/api/v2/credentials/3/',
|
||||||
|
related: {
|
||||||
|
named_url: '/api/v2/credentials/oersdgfasf++Machine+ssh++org/',
|
||||||
|
created_by: '/api/v2/users/1/',
|
||||||
|
modified_by: '/api/v2/users/1/',
|
||||||
|
organization: '/api/v2/organizations/1/',
|
||||||
|
activity_stream: '/api/v2/credentials/3/activity_stream/',
|
||||||
|
access_list: '/api/v2/credentials/3/access_list/',
|
||||||
|
object_roles: '/api/v2/credentials/3/object_roles/',
|
||||||
|
owner_users: '/api/v2/credentials/3/owner_users/',
|
||||||
|
owner_teams: '/api/v2/credentials/3/owner_teams/',
|
||||||
|
copy: '/api/v2/credentials/3/copy/',
|
||||||
|
input_sources: '/api/v2/credentials/3/input_sources/',
|
||||||
|
credential_type: '/api/v2/credential_types/1/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
organization: {
|
||||||
|
id: 1,
|
||||||
|
name: 'org',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
credential_type: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Machine',
|
||||||
|
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: 36,
|
||||||
|
},
|
||||||
|
use_role: {
|
||||||
|
description: 'Can use the credential in a job template',
|
||||||
|
name: 'Use',
|
||||||
|
id: 37,
|
||||||
|
},
|
||||||
|
read_role: {
|
||||||
|
description: 'May view settings for the credential',
|
||||||
|
name: 'Read',
|
||||||
|
id: 38,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
copy: true,
|
||||||
|
use: true,
|
||||||
|
},
|
||||||
|
owners: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'user',
|
||||||
|
name: 'admin',
|
||||||
|
description: ' ',
|
||||||
|
url: '/api/v2/users/1/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'organization',
|
||||||
|
name: 'org',
|
||||||
|
description: '',
|
||||||
|
url: '/api/v2/organizations/1/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
created: '2020-02-18T15:35:04.563928Z',
|
||||||
|
modified: '2020-02-18T15:35:04.563957Z',
|
||||||
|
name: 'oersdgfasf',
|
||||||
|
description: '',
|
||||||
|
organization: 1,
|
||||||
|
credential_type: 1,
|
||||||
|
inputs: {},
|
||||||
|
kind: 'ssh',
|
||||||
|
cloud: false,
|
||||||
|
kubernetes: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sourceControlCredential = {
|
||||||
|
id: 4,
|
||||||
|
type: 'credential',
|
||||||
|
url: '/api/v2/credentials/4/',
|
||||||
|
related: {
|
||||||
|
named_url: '/api/v2/credentials/joijoij++Source Control+scm++/',
|
||||||
|
created_by: '/api/v2/users/1/',
|
||||||
|
modified_by: '/api/v2/users/1/',
|
||||||
|
activity_stream: '/api/v2/credentials/4/activity_stream/',
|
||||||
|
access_list: '/api/v2/credentials/4/access_list/',
|
||||||
|
object_roles: '/api/v2/credentials/4/object_roles/',
|
||||||
|
owner_users: '/api/v2/credentials/4/owner_users/',
|
||||||
|
owner_teams: '/api/v2/credentials/4/owner_teams/',
|
||||||
|
copy: '/api/v2/credentials/4/copy/',
|
||||||
|
input_sources: '/api/v2/credentials/4/input_sources/',
|
||||||
|
credential_type: '/api/v2/credential_types/2/',
|
||||||
|
user: '/api/v2/users/1/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
credential_type: {
|
||||||
|
id: 2,
|
||||||
|
name: 'Source Control',
|
||||||
|
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: 39,
|
||||||
|
},
|
||||||
|
use_role: {
|
||||||
|
description: 'Can use the credential in a job template',
|
||||||
|
name: 'Use',
|
||||||
|
id: 40,
|
||||||
|
},
|
||||||
|
read_role: {
|
||||||
|
description: 'May view settings for the credential',
|
||||||
|
name: 'Read',
|
||||||
|
id: 41,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
copy: true,
|
||||||
|
use: true,
|
||||||
|
},
|
||||||
|
owners: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'user',
|
||||||
|
name: 'admin',
|
||||||
|
description: ' ',
|
||||||
|
url: '/api/v2/users/1/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
created: '2020-02-18T16:03:01.366287Z',
|
||||||
|
modified: '2020-02-18T16:03:01.366315Z',
|
||||||
|
name: 'joijoij',
|
||||||
|
description: 'ojiojojo',
|
||||||
|
organization: null,
|
||||||
|
credential_type: 2,
|
||||||
|
inputs: {
|
||||||
|
ssh_key_unlock: '$encrypted$',
|
||||||
|
},
|
||||||
|
kind: 'scm',
|
||||||
|
cloud: false,
|
||||||
|
kubernetes: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const credentialTypes = [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: 'credential_type',
|
||||||
|
url: '/api/v2/credential_types/2/',
|
||||||
|
related: {
|
||||||
|
credentials: '/api/v2/credential_types/2/credentials/',
|
||||||
|
activity_stream: '/api/v2/credential_types/2/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
delete: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-02-12T19:42:43.551238Z',
|
||||||
|
modified: '2020-02-12T19:43:03.164800Z',
|
||||||
|
name: 'Source Control',
|
||||||
|
description: '',
|
||||||
|
kind: 'scm',
|
||||||
|
namespace: 'scm',
|
||||||
|
managed_by_tower: true,
|
||||||
|
inputs: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_data',
|
||||||
|
label: 'SCM Private Key',
|
||||||
|
type: 'string',
|
||||||
|
format: 'ssh_private_key',
|
||||||
|
secret: true,
|
||||||
|
multiline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_unlock',
|
||||||
|
label: 'Private Key Passphrase',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
injectors: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'credential_type',
|
||||||
|
url: '/api/v2/credential_types/1/',
|
||||||
|
related: {
|
||||||
|
credentials: '/api/v2/credential_types/1/credentials/',
|
||||||
|
activity_stream: '/api/v2/credential_types/1/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
edit: false,
|
||||||
|
delete: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-02-12T19:42:43.539626Z',
|
||||||
|
modified: '2020-02-12T19:43:03.159739Z',
|
||||||
|
name: 'Machine',
|
||||||
|
description: '',
|
||||||
|
kind: 'ssh',
|
||||||
|
namespace: 'ssh',
|
||||||
|
managed_by_tower: true,
|
||||||
|
inputs: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_data',
|
||||||
|
label: 'SSH Private Key',
|
||||||
|
type: 'string',
|
||||||
|
format: 'ssh_private_key',
|
||||||
|
secret: true,
|
||||||
|
multiline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_public_key_data',
|
||||||
|
label: 'Signed SSH Certificate',
|
||||||
|
type: 'string',
|
||||||
|
multiline: true,
|
||||||
|
secret: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ssh_key_unlock',
|
||||||
|
label: 'Private Key Passphrase',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_method',
|
||||||
|
label: 'Privilege Escalation Method',
|
||||||
|
type: 'string',
|
||||||
|
help_text:
|
||||||
|
'Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_username',
|
||||||
|
label: 'Privilege Escalation Username',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'become_password',
|
||||||
|
label: 'Privilege Escalation Password',
|
||||||
|
type: 'string',
|
||||||
|
secret: true,
|
||||||
|
ask_at_runtime: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
injectors: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('<CredentialForm />', () => {
|
||||||
|
let wrapper;
|
||||||
|
let onCancel;
|
||||||
|
let onSubmit;
|
||||||
|
|
||||||
|
const addFieldExpects = () => {
|
||||||
|
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 = () => {
|
||||||
|
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(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(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Private Key Passphrase"]').length
|
||||||
|
).toBe(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Privelege Escalation Method"]').length
|
||||||
|
).toBe(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Privilege Escalation Username"]').length
|
||||||
|
).toBe(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Privilege Escalation Password"]').length
|
||||||
|
).toBe(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sourceFieldExpects = () => {
|
||||||
|
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(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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
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')({
|
||||||
|
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 () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<CredentialForm
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
credentialTypes={credentialTypes}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
addFieldExpects();
|
||||||
|
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 call handleCancel when Cancel button is clicked', async () => {
|
||||||
|
expect(onCancel).not.toHaveBeenCalled();
|
||||||
|
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||||
|
expect(onCancel).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useField } from 'formik';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import FormField, { PasswordField } from '@components/FormField';
|
||||||
|
import { FormGroup } from '@patternfly/react-core';
|
||||||
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
|
import { FormColumnLayout, FormFullWidthLayout } from '@components/FormLayout';
|
||||||
|
import {
|
||||||
|
UsernameFormField,
|
||||||
|
PasswordFormField,
|
||||||
|
SSHKeyUnlockField,
|
||||||
|
SSHKeyDataField,
|
||||||
|
} from './SharedFields';
|
||||||
|
|
||||||
|
const ManualSubForm = ({ i18n }) => {
|
||||||
|
const becomeMethodOptions = [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
key: '',
|
||||||
|
label: i18n._(t`Choose a Privelege Escalation Method`),
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
...[
|
||||||
|
'sudo',
|
||||||
|
'su',
|
||||||
|
'pbrun',
|
||||||
|
'pfexec',
|
||||||
|
'dzdo',
|
||||||
|
'pmrun',
|
||||||
|
'runas',
|
||||||
|
'enable',
|
||||||
|
'doas',
|
||||||
|
'ksu',
|
||||||
|
'machinectl',
|
||||||
|
'sesu',
|
||||||
|
].map(val => ({ value: val, key: val, label: val })),
|
||||||
|
];
|
||||||
|
|
||||||
|
const becomeMethodFieldArr = useField('inputs.become_method');
|
||||||
|
const becomeMethodField = becomeMethodFieldArr[0];
|
||||||
|
const becomeMethodHelpers = becomeMethodFieldArr[2];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormColumnLayout>
|
||||||
|
<UsernameFormField />
|
||||||
|
<PasswordFormField />
|
||||||
|
<FormFullWidthLayout>
|
||||||
|
<SSHKeyDataField />
|
||||||
|
<FormField
|
||||||
|
id="credential-sshPublicKeyData"
|
||||||
|
label={i18n._(t`Signed SSH Certificate`)}
|
||||||
|
name="inputs.ssh_public_key_data"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</FormFullWidthLayout>
|
||||||
|
<SSHKeyUnlockField />
|
||||||
|
<FormGroup
|
||||||
|
fieldId="credential-becomeMethod"
|
||||||
|
label={i18n._(t`Privelege Escalation Method`)}
|
||||||
|
>
|
||||||
|
<AnsibleSelect
|
||||||
|
{...becomeMethodField}
|
||||||
|
id="credential-becomeMethod"
|
||||||
|
data={becomeMethodOptions}
|
||||||
|
onChange={(event, value) => {
|
||||||
|
becomeMethodHelpers.setValue(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormField
|
||||||
|
id="credential-becomeUsername"
|
||||||
|
label={i18n._(t`Privilege Escalation Username`)}
|
||||||
|
name="inputs.become_username"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<PasswordField
|
||||||
|
id="credential-becomePassword"
|
||||||
|
label={i18n._(t`Privilege Escalation Password`)}
|
||||||
|
name="inputs.become_password"
|
||||||
|
/>
|
||||||
|
</FormColumnLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(ManualSubForm);
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import FormField, { PasswordField } from '@components/FormField';
|
||||||
|
import { Title } from '@patternfly/react-core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const UsernameFormField = withI18n()(({ i18n }) => (
|
||||||
|
<FormField
|
||||||
|
id="credentual-username"
|
||||||
|
label={i18n._(t`Username`)}
|
||||||
|
name="inputs.username"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
export const PasswordFormField = withI18n()(({ i18n }) => (
|
||||||
|
<PasswordField
|
||||||
|
id="credential-password"
|
||||||
|
label={i18n._(t`Password`)}
|
||||||
|
name="inputs.password"
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
export const SSHKeyDataField = withI18n()(({ i18n }) => (
|
||||||
|
<FormField
|
||||||
|
id="credential-sshKeyData"
|
||||||
|
label={i18n._(t`SSH Private Key`)}
|
||||||
|
name="inputs.ssh_key_data"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
export const SSHKeyUnlockField = withI18n()(({ i18n }) => (
|
||||||
|
<PasswordField
|
||||||
|
id="credential-sshKeyUnlock"
|
||||||
|
label={i18n._(t`Private Key Passphrase`)}
|
||||||
|
name="inputs.ssh_key_unlock"
|
||||||
|
/>
|
||||||
|
));
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { FormColumnLayout, FormFullWidthLayout } from '@components/FormLayout';
|
||||||
|
import {
|
||||||
|
UsernameFormField,
|
||||||
|
PasswordFormField,
|
||||||
|
SSHKeyUnlockField,
|
||||||
|
SSHKeyDataField,
|
||||||
|
} from './SharedFields';
|
||||||
|
|
||||||
|
const SourceControlSubForm = () => (
|
||||||
|
<>
|
||||||
|
<FormColumnLayout>
|
||||||
|
<UsernameFormField />
|
||||||
|
<PasswordFormField />
|
||||||
|
<SSHKeyUnlockField />
|
||||||
|
</FormColumnLayout>
|
||||||
|
<FormFullWidthLayout>
|
||||||
|
<SSHKeyDataField />
|
||||||
|
</FormFullWidthLayout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default withI18n()(SourceControlSubForm);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as ManualSubForm } from './ManualSubForm';
|
||||||
|
export { default as SourceControlSubForm } from './SourceControlSubForm';
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": "credential",
|
||||||
|
"url": "/api/v2/credentials/2/",
|
||||||
|
"related": {
|
||||||
|
"named_url": "/api/v2/credentials/jojoijoij++Source Control+scm++/",
|
||||||
|
"created_by": "/api/v2/users/1/",
|
||||||
|
"modified_by": "/api/v2/users/1/",
|
||||||
|
"activity_stream": "/api/v2/credentials/2/activity_stream/",
|
||||||
|
"access_list": "/api/v2/credentials/2/access_list/",
|
||||||
|
"object_roles": "/api/v2/credentials/2/object_roles/",
|
||||||
|
"owner_users": "/api/v2/credentials/2/owner_users/",
|
||||||
|
"owner_teams": "/api/v2/credentials/2/owner_teams/",
|
||||||
|
"copy": "/api/v2/credentials/2/copy/",
|
||||||
|
"input_sources": "/api/v2/credentials/2/input_sources/",
|
||||||
|
"credential_type": "/api/v2/credential_types/2/",
|
||||||
|
"user": "/api/v2/users/1/"
|
||||||
|
},
|
||||||
|
"summary_fields": {
|
||||||
|
"credential_type": {
|
||||||
|
"id": 2,
|
||||||
|
"name": "Source Control",
|
||||||
|
"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": 6
|
||||||
|
},
|
||||||
|
"use_role": {
|
||||||
|
"description": "Can use the credential in a job template",
|
||||||
|
"name": "Use",
|
||||||
|
"id": 7
|
||||||
|
},
|
||||||
|
"read_role": {
|
||||||
|
"description": "May view settings for the credential",
|
||||||
|
"name": "Read",
|
||||||
|
"id": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user_capabilities": {
|
||||||
|
"edit": true,
|
||||||
|
"delete": true,
|
||||||
|
"copy": true,
|
||||||
|
"use": true
|
||||||
|
},
|
||||||
|
"owners": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "user",
|
||||||
|
"name": "admin",
|
||||||
|
"description": " ",
|
||||||
|
"url": "/api/v2/users/1/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"created": "2020-02-12T19:59:11.508933Z",
|
||||||
|
"modified": "2020-02-12T19:59:11.508958Z",
|
||||||
|
"name": "jojoijoij",
|
||||||
|
"description": "",
|
||||||
|
"organization": null,
|
||||||
|
"credential_type": 2,
|
||||||
|
"inputs": {
|
||||||
|
"password": "$encrypted$",
|
||||||
|
"username": "uujoij",
|
||||||
|
"ssh_key_unlock": "$encrypted$"
|
||||||
|
},
|
||||||
|
"kind": "scm",
|
||||||
|
"cloud": false,
|
||||||
|
"kubernetes": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"type": "credential",
|
||||||
|
"url": "/api/v2/credentials/3/",
|
||||||
|
"related": {
|
||||||
|
"named_url": "/api/v2/credentials/oersdgfasf++Machine+ssh++org/",
|
||||||
|
"created_by": "/api/v2/users/1/",
|
||||||
|
"modified_by": "/api/v2/users/1/",
|
||||||
|
"organization": "/api/v2/organizations/1/",
|
||||||
|
"activity_stream": "/api/v2/credentials/3/activity_stream/",
|
||||||
|
"access_list": "/api/v2/credentials/3/access_list/",
|
||||||
|
"object_roles": "/api/v2/credentials/3/object_roles/",
|
||||||
|
"owner_users": "/api/v2/credentials/3/owner_users/",
|
||||||
|
"owner_teams": "/api/v2/credentials/3/owner_teams/",
|
||||||
|
"copy": "/api/v2/credentials/3/copy/",
|
||||||
|
"input_sources": "/api/v2/credentials/3/input_sources/",
|
||||||
|
"credential_type": "/api/v2/credential_types/1/"
|
||||||
|
},
|
||||||
|
"summary_fields": {
|
||||||
|
"organization": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "org",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"credential_type": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Machine",
|
||||||
|
"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": 36
|
||||||
|
},
|
||||||
|
"use_role": {
|
||||||
|
"description": "Can use the credential in a job template",
|
||||||
|
"name": "Use",
|
||||||
|
"id": 37
|
||||||
|
},
|
||||||
|
"read_role": {
|
||||||
|
"description": "May view settings for the credential",
|
||||||
|
"name": "Read",
|
||||||
|
"id": 38
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user_capabilities": {
|
||||||
|
"edit": true,
|
||||||
|
"delete": true,
|
||||||
|
"copy": true,
|
||||||
|
"use": true
|
||||||
|
},
|
||||||
|
"owners": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "user",
|
||||||
|
"name": "admin",
|
||||||
|
"description": " ",
|
||||||
|
"url": "/api/v2/users/1/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "organization",
|
||||||
|
"name": "org",
|
||||||
|
"description": "",
|
||||||
|
"url": "/api/v2/organizations/1/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"created": "2020-02-18T15:35:04.563928Z",
|
||||||
|
"modified": "2020-02-18T15:35:04.563957Z",
|
||||||
|
"name": "oersdgfasf",
|
||||||
|
"description": "",
|
||||||
|
"organization": 1,
|
||||||
|
"credential_type": 1,
|
||||||
|
"inputs": {},
|
||||||
|
"kind": "ssh",
|
||||||
|
"cloud": false,
|
||||||
|
"kubernetes": false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user