mirror of
https://github.com/ansible/awx.git
synced 2026-03-07 19:51:08 -03:30
Add Content Signature Validation Credential field to Projects Form page and Projects Detail page
This commit is contained in:
@@ -7,7 +7,15 @@ class CredentialTypes extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadAllTypes(
|
async loadAllTypes(
|
||||||
acceptableKinds = ['machine', 'cloud', 'net', 'ssh', 'vault', 'kubernetes']
|
acceptableKinds = [
|
||||||
|
'machine',
|
||||||
|
'cloud',
|
||||||
|
'net',
|
||||||
|
'ssh',
|
||||||
|
'vault',
|
||||||
|
'kubernetes',
|
||||||
|
'cryptography',
|
||||||
|
]
|
||||||
) {
|
) {
|
||||||
const pageSize = 200;
|
const pageSize = 200;
|
||||||
// The number of credential types a user can have is unlimited. In practice, it is unlikely for
|
// The number of credential types a user can have is unlimited. In practice, it is unlikely for
|
||||||
|
|||||||
@@ -125,6 +125,21 @@ function PromptProjectDetail({ resource }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{summary_fields?.signature_validation_credential?.id && (
|
||||||
|
<Detail
|
||||||
|
label={t`Content Signature Validation Credential`}
|
||||||
|
dataCy={`${prefixCy}-content-signature-validation-credential`}
|
||||||
|
value={
|
||||||
|
<CredentialChip
|
||||||
|
key={resource.summary_fields.signature_validation_credential.id}
|
||||||
|
credential={
|
||||||
|
resource.summary_fields.signature_validation_credential
|
||||||
|
}
|
||||||
|
isReadOnly
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{optionsList && (
|
{optionsList && (
|
||||||
<Detail
|
<Detail
|
||||||
label={t`Enabled Options`}
|
label={t`Enabled Options`}
|
||||||
|
|||||||
@@ -18,10 +18,20 @@ function ProjectAdd() {
|
|||||||
// the API might throw an unexpected error if our creation request
|
// the API might throw an unexpected error if our creation request
|
||||||
// has a zero-length string as its credential field. As a work-around,
|
// has a zero-length string as its credential field. As a work-around,
|
||||||
// normalize falsey credential fields by deleting them.
|
// normalize falsey credential fields by deleting them.
|
||||||
delete values.credential;
|
values.credential = null;
|
||||||
} else {
|
} else if (typeof values.credential.id === 'number') {
|
||||||
values.credential = values.credential.id;
|
values.credential = values.credential.id;
|
||||||
}
|
}
|
||||||
|
if (values.scm_type === 'git') {
|
||||||
|
if (!values.signature_validation_credential) {
|
||||||
|
values.signature_validation_credential = null;
|
||||||
|
} else if (
|
||||||
|
typeof values.signature_validation_credential.id === 'number'
|
||||||
|
) {
|
||||||
|
values.signature_validation_credential =
|
||||||
|
values.signature_validation_credential.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
setFormSubmitError(null);
|
setFormSubmitError(null);
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ describe('<ProjectAdd />', () => {
|
|||||||
scm_clean: true,
|
scm_clean: true,
|
||||||
scm_track_submodules: false,
|
scm_track_submodules: false,
|
||||||
credential: 100,
|
credential: 100,
|
||||||
|
signature_validation_credential: 200,
|
||||||
local_path: '',
|
local_path: '',
|
||||||
organization: { id: 2, name: 'Bar' },
|
organization: { id: 2, name: 'Bar' },
|
||||||
scm_update_on_launch: true,
|
scm_update_on_launch: true,
|
||||||
@@ -73,16 +74,32 @@ describe('<ProjectAdd />', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cryptographyCredentialResolve = {
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'GPG Public Key',
|
||||||
|
kind: 'cryptography',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await ProjectsAPI.readOptions.mockImplementation(
|
await ProjectsAPI.readOptions.mockImplementation(
|
||||||
() => projectOptionsResolve
|
() => projectOptionsResolve
|
||||||
);
|
);
|
||||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
() => scmCredentialResolve
|
() => scmCredentialResolve
|
||||||
);
|
);
|
||||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
() => insightsCredentialResolve
|
() => insightsCredentialResolve
|
||||||
);
|
);
|
||||||
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
|
() => cryptographyCredentialResolve
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -110,6 +127,7 @@ describe('<ProjectAdd />', () => {
|
|||||||
...projectData,
|
...projectData,
|
||||||
organization: 2,
|
organization: 2,
|
||||||
default_environment: 1,
|
default_environment: 1,
|
||||||
|
signature_validation_credential: 200,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ function ProjectDetail({ project }) {
|
|||||||
</TextList>
|
</TextList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateLastJobTooltip = (job) => (
|
const generateLastJobTooltip = (job) => (
|
||||||
<>
|
<>
|
||||||
<div>{t`MOST RECENT SYNC`}</div>
|
<div>{t`MOST RECENT SYNC`}</div>
|
||||||
@@ -149,6 +148,7 @@ function ProjectDetail({ project }) {
|
|||||||
} else if (summary_fields?.last_job) {
|
} else if (summary_fields?.last_job) {
|
||||||
job = summary_fields.last_job;
|
job = summary_fields.last_job;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSourceControlUrlHelpText = () =>
|
const getSourceControlUrlHelpText = () =>
|
||||||
scm_type === 'git'
|
scm_type === 'git'
|
||||||
? projectHelpText.githubSourceControlUrl
|
? projectHelpText.githubSourceControlUrl
|
||||||
@@ -234,6 +234,22 @@ function ProjectDetail({ project }) {
|
|||||||
label={t`Source Control Refspec`}
|
label={t`Source Control Refspec`}
|
||||||
value={scm_refspec}
|
value={scm_refspec}
|
||||||
/>
|
/>
|
||||||
|
{summary_fields.signature_validation_credential && (
|
||||||
|
<Detail
|
||||||
|
label={t`Content Signature Validation Credential`}
|
||||||
|
helpText={projectHelpText.signatureValidation}
|
||||||
|
value={
|
||||||
|
<CredentialChip
|
||||||
|
key={summary_fields.signature_validation_credential.id}
|
||||||
|
credential={summary_fields.signature_validation_credential}
|
||||||
|
isReadOnly
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
isEmpty={
|
||||||
|
summary_fields.signature_validation_credential.length === 0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{summary_fields.credential && (
|
{summary_fields.credential && (
|
||||||
<Detail
|
<Detail
|
||||||
label={t`Source Control Credential`}
|
label={t`Source Control Credential`}
|
||||||
@@ -244,6 +260,7 @@ function ProjectDetail({ project }) {
|
|||||||
isReadOnly
|
isReadOnly
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
isEmpty={summary_fields.credential.length === 0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Detail
|
<Detail
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ describe('<ProjectDetail />', () => {
|
|||||||
name: 'qux',
|
name: 'qux',
|
||||||
kind: 'scm',
|
kind: 'scm',
|
||||||
},
|
},
|
||||||
|
signature_validation_credential: {
|
||||||
|
id: 2000,
|
||||||
|
name: 'svc',
|
||||||
|
kind: 'cryptography',
|
||||||
|
},
|
||||||
last_job: {
|
last_job: {
|
||||||
id: 9000,
|
id: 9000,
|
||||||
status: 'successful',
|
status: 'successful',
|
||||||
@@ -78,6 +83,7 @@ describe('<ProjectDetail />', () => {
|
|||||||
scm_delete_on_update: true,
|
scm_delete_on_update: true,
|
||||||
scm_track_submodules: true,
|
scm_track_submodules: true,
|
||||||
credential: 100,
|
credential: 100,
|
||||||
|
signature_validation_credential: 200,
|
||||||
status: 'successful',
|
status: 'successful',
|
||||||
organization: 10,
|
organization: 10,
|
||||||
scm_update_on_launch: true,
|
scm_update_on_launch: true,
|
||||||
@@ -108,6 +114,10 @@ describe('<ProjectDetail />', () => {
|
|||||||
'Source Control Credential',
|
'Source Control Credential',
|
||||||
`Scm: ${mockProject.summary_fields.credential.name}`
|
`Scm: ${mockProject.summary_fields.credential.name}`
|
||||||
);
|
);
|
||||||
|
assertDetail(
|
||||||
|
'Content Signature Validation Credential',
|
||||||
|
`Cryptography: ${mockProject.summary_fields.signature_validation_credential.name}`
|
||||||
|
);
|
||||||
assertDetail(
|
assertDetail(
|
||||||
'Cache Timeout',
|
'Cache Timeout',
|
||||||
`${mockProject.scm_update_cache_timeout} Seconds`
|
`${mockProject.scm_update_cache_timeout} Seconds`
|
||||||
|
|||||||
@@ -18,10 +18,21 @@ function ProjectEdit({ project }) {
|
|||||||
// the API might throw an unexpected error if our creation request
|
// the API might throw an unexpected error if our creation request
|
||||||
// has a zero-length string as its credential field. As a work-around,
|
// has a zero-length string as its credential field. As a work-around,
|
||||||
// normalize falsey credential fields by deleting them.
|
// normalize falsey credential fields by deleting them.
|
||||||
delete values.credential;
|
values.credential = null;
|
||||||
} else {
|
} else if (typeof values.credential.id === 'number') {
|
||||||
values.credential = values.credential.id;
|
values.credential = values.credential.id;
|
||||||
}
|
}
|
||||||
|
if (values.scm_type === 'git') {
|
||||||
|
if (!values.signature_validation_credential) {
|
||||||
|
values.signature_validation_credential = null;
|
||||||
|
} else if (
|
||||||
|
typeof values.signature_validation_credential.id === 'number'
|
||||||
|
) {
|
||||||
|
values.signature_validation_credential =
|
||||||
|
values.signature_validation_credential.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { id },
|
data: { id },
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ describe('<ProjectEdit />', () => {
|
|||||||
scm_clean: true,
|
scm_clean: true,
|
||||||
scm_track_submodules: false,
|
scm_track_submodules: false,
|
||||||
credential: 100,
|
credential: 100,
|
||||||
|
signature_validation_credential: 200,
|
||||||
local_path: 'bar',
|
local_path: 'bar',
|
||||||
organization: 2,
|
organization: 2,
|
||||||
scm_update_on_launch: true,
|
scm_update_on_launch: true,
|
||||||
@@ -33,6 +34,12 @@ describe('<ProjectEdit />', () => {
|
|||||||
credential_type_id: 5,
|
credential_type_id: 5,
|
||||||
kind: 'insights',
|
kind: 'insights',
|
||||||
},
|
},
|
||||||
|
signature_validation_credential: {
|
||||||
|
id: 200,
|
||||||
|
credential_type_id: 6,
|
||||||
|
kind: 'cryptography',
|
||||||
|
name: 'foo',
|
||||||
|
},
|
||||||
organization: {
|
organization: {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
@@ -60,6 +67,7 @@ describe('<ProjectEdit />', () => {
|
|||||||
|
|
||||||
const scmCredentialResolve = {
|
const scmCredentialResolve = {
|
||||||
data: {
|
data: {
|
||||||
|
count: 1,
|
||||||
results: [
|
results: [
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
@@ -72,6 +80,7 @@ describe('<ProjectEdit />', () => {
|
|||||||
|
|
||||||
const insightsCredentialResolve = {
|
const insightsCredentialResolve = {
|
||||||
data: {
|
data: {
|
||||||
|
count: 1,
|
||||||
results: [
|
results: [
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
@@ -82,6 +91,19 @@ describe('<ProjectEdit />', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cryptographyCredentialResolve = {
|
||||||
|
data: {
|
||||||
|
count: 1,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'GPG Public Key',
|
||||||
|
kind: 'cryptography',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
RootAPI.readAssetVariables.mockResolvedValue({
|
RootAPI.readAssetVariables.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
@@ -91,12 +113,15 @@ describe('<ProjectEdit />', () => {
|
|||||||
await ProjectsAPI.readOptions.mockImplementation(
|
await ProjectsAPI.readOptions.mockImplementation(
|
||||||
() => projectOptionsResolve
|
() => projectOptionsResolve
|
||||||
);
|
);
|
||||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
() => scmCredentialResolve
|
() => scmCredentialResolve
|
||||||
);
|
);
|
||||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
() => insightsCredentialResolve
|
() => insightsCredentialResolve
|
||||||
);
|
);
|
||||||
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
|
() => cryptographyCredentialResolve
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ const projectHelpTextStrings = {
|
|||||||
you can input tags, commit hashes, and arbitrary refs. Some
|
you can input tags, commit hashes, and arbitrary refs. Some
|
||||||
commit hashes and refs may not be available unless you also
|
commit hashes and refs may not be available unless you also
|
||||||
provide a custom refspec.`,
|
provide a custom refspec.`,
|
||||||
|
signatureValidation: t`Enable content signing to verify that the content
|
||||||
|
has remained secure when a project is synced.
|
||||||
|
If the content has been tampered with, the
|
||||||
|
job will not run.`,
|
||||||
options: {
|
options: {
|
||||||
clean: t`Remove any local modifications prior to performing an update.`,
|
clean: t`Remove any local modifications prior to performing an update.`,
|
||||||
delete: t`Delete the local repository in its entirety prior to
|
delete: t`Delete the local repository in its entirety prior to
|
||||||
|
|||||||
@@ -37,15 +37,22 @@ const fetchCredentials = async (credential) => {
|
|||||||
results: [insightsCredentialType],
|
results: [insightsCredentialType],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
results: [cryptographyCredentialType],
|
||||||
|
},
|
||||||
|
},
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
CredentialTypesAPI.read({ kind: 'scm' }),
|
CredentialTypesAPI.read({ kind: 'scm' }),
|
||||||
CredentialTypesAPI.read({ name: 'Insights' }),
|
CredentialTypesAPI.read({ name: 'Insights' }),
|
||||||
|
CredentialTypesAPI.read({ kind: 'cryptography' }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
return {
|
return {
|
||||||
scm: { typeId: scmCredentialType.id },
|
scm: { typeId: scmCredentialType.id },
|
||||||
insights: { typeId: insightsCredentialType.id },
|
insights: { typeId: insightsCredentialType.id },
|
||||||
|
cryptography: { typeId: cryptographyCredentialType.id },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +67,13 @@ const fetchCredentials = async (credential) => {
|
|||||||
value:
|
value:
|
||||||
credential_type_id === insightsCredentialType.id ? credential : null,
|
credential_type_id === insightsCredentialType.id ? credential : null,
|
||||||
},
|
},
|
||||||
|
cryptography: {
|
||||||
|
typeId: cryptographyCredentialType.id,
|
||||||
|
value:
|
||||||
|
credential_type_id === cryptographyCredentialType.id
|
||||||
|
? credential
|
||||||
|
: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,7 +83,9 @@ function ProjectFormFields({
|
|||||||
project_local_paths,
|
project_local_paths,
|
||||||
formik,
|
formik,
|
||||||
setCredentials,
|
setCredentials,
|
||||||
|
setSignatureValidationCredentials,
|
||||||
credentials,
|
credentials,
|
||||||
|
signatureValidationCredentials,
|
||||||
scmTypeOptions,
|
scmTypeOptions,
|
||||||
setScmSubFormState,
|
setScmSubFormState,
|
||||||
scmSubFormState,
|
scmSubFormState,
|
||||||
@@ -79,6 +95,7 @@ function ProjectFormFields({
|
|||||||
scm_branch: '',
|
scm_branch: '',
|
||||||
scm_refspec: '',
|
scm_refspec: '',
|
||||||
credential: '',
|
credential: '',
|
||||||
|
signature_validation_credential: '',
|
||||||
scm_clean: false,
|
scm_clean: false,
|
||||||
scm_delete_on_update: false,
|
scm_delete_on_update: false,
|
||||||
scm_track_submodules: false,
|
scm_track_submodules: false,
|
||||||
@@ -86,7 +103,6 @@ function ProjectFormFields({
|
|||||||
allow_override: false,
|
allow_override: false,
|
||||||
scm_update_cache_timeout: 0,
|
scm_update_cache_timeout: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { setFieldValue, setFieldTouched } = useFormikContext();
|
const { setFieldValue, setFieldTouched } = useFormikContext();
|
||||||
|
|
||||||
const [scmTypeField, scmTypeMeta, scmTypeHelpers] = useField({
|
const [scmTypeField, scmTypeMeta, scmTypeHelpers] = useField({
|
||||||
@@ -147,6 +163,19 @@ function ProjectFormFields({
|
|||||||
[credentials, setCredentials]
|
[credentials, setCredentials]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSignatureValidationCredentialSelection = useCallback(
|
||||||
|
(type, value) => {
|
||||||
|
setSignatureValidationCredentials({
|
||||||
|
...signatureValidationCredentials,
|
||||||
|
[type]: {
|
||||||
|
...signatureValidationCredentials[type],
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[signatureValidationCredentials, setSignatureValidationCredentials]
|
||||||
|
);
|
||||||
|
|
||||||
const handleOrganizationUpdate = useCallback(
|
const handleOrganizationUpdate = useCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
setFieldValue('organization', value);
|
setFieldValue('organization', value);
|
||||||
@@ -259,7 +288,13 @@ function ProjectFormFields({
|
|||||||
git: (
|
git: (
|
||||||
<GitSubForm
|
<GitSubForm
|
||||||
credential={credentials.scm}
|
credential={credentials.scm}
|
||||||
|
signature_validation_credential={
|
||||||
|
signatureValidationCredentials.cryptography
|
||||||
|
}
|
||||||
onCredentialSelection={handleCredentialSelection}
|
onCredentialSelection={handleCredentialSelection}
|
||||||
|
onSignatureValidationCredentialSelection={
|
||||||
|
handleSignatureValidationCredentialSelection
|
||||||
|
}
|
||||||
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -295,7 +330,6 @@ function ProjectFormFields({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProjectForm({ project, submitError, ...props }) {
|
function ProjectForm({ project, submitError, ...props }) {
|
||||||
const { handleCancel, handleSubmit } = props;
|
const { handleCancel, handleSubmit } = props;
|
||||||
const { summary_fields = {} } = project;
|
const { summary_fields = {} } = project;
|
||||||
@@ -307,6 +341,7 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
scm_branch: '',
|
scm_branch: '',
|
||||||
scm_refspec: '',
|
scm_refspec: '',
|
||||||
credential: '',
|
credential: '',
|
||||||
|
signature_validation_credential: '',
|
||||||
scm_clean: false,
|
scm_clean: false,
|
||||||
scm_delete_on_update: false,
|
scm_delete_on_update: false,
|
||||||
scm_track_submodules: false,
|
scm_track_submodules: false,
|
||||||
@@ -318,12 +353,22 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
const [credentials, setCredentials] = useState({
|
const [credentials, setCredentials] = useState({
|
||||||
scm: { typeId: null, value: null },
|
scm: { typeId: null, value: null },
|
||||||
insights: { typeId: null, value: null },
|
insights: { typeId: null, value: null },
|
||||||
|
cryptography: { typeId: null, value: null },
|
||||||
});
|
});
|
||||||
|
const [signatureValidationCredentials, setSignatureValidationCredentials] =
|
||||||
|
useState({
|
||||||
|
scm: { typeId: null, value: null },
|
||||||
|
insights: { typeId: null, value: null },
|
||||||
|
cryptography: { typeId: null, value: null },
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
const credentialResponse = fetchCredentials(summary_fields.credential);
|
const credentialResponse = fetchCredentials(summary_fields.credential);
|
||||||
|
const signatureValidationCredentialResponse = fetchCredentials(
|
||||||
|
summary_fields.signature_validation_credential
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
data: {
|
data: {
|
||||||
actions: {
|
actions: {
|
||||||
@@ -335,6 +380,9 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
} = await ProjectsAPI.readOptions();
|
} = await ProjectsAPI.readOptions();
|
||||||
|
|
||||||
setCredentials(await credentialResponse);
|
setCredentials(await credentialResponse);
|
||||||
|
setSignatureValidationCredentials(
|
||||||
|
await signatureValidationCredentialResponse
|
||||||
|
);
|
||||||
setScmTypeOptions(choices);
|
setScmTypeOptions(choices);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setContentError(error);
|
setContentError(error);
|
||||||
@@ -344,7 +392,10 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [summary_fields.credential]);
|
}, [
|
||||||
|
summary_fields.credential,
|
||||||
|
summary_fields.signature_validation_credential,
|
||||||
|
]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
@@ -378,6 +429,8 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
scm_update_cache_timeout: project.scm_update_cache_timeout || 0,
|
scm_update_cache_timeout: project.scm_update_cache_timeout || 0,
|
||||||
scm_update_on_launch: project.scm_update_on_launch || false,
|
scm_update_on_launch: project.scm_update_on_launch || false,
|
||||||
scm_url: project.scm_url || '',
|
scm_url: project.scm_url || '',
|
||||||
|
signature_validation_credential:
|
||||||
|
project.signature_validation_credential || '',
|
||||||
default_environment:
|
default_environment:
|
||||||
project.summary_fields?.default_environment || null,
|
project.summary_fields?.default_environment || null,
|
||||||
}}
|
}}
|
||||||
@@ -392,7 +445,11 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
project_local_paths={project_local_paths}
|
project_local_paths={project_local_paths}
|
||||||
formik={formik}
|
formik={formik}
|
||||||
setCredentials={setCredentials}
|
setCredentials={setCredentials}
|
||||||
|
setSignatureValidationCredentials={
|
||||||
|
setSignatureValidationCredentials
|
||||||
|
}
|
||||||
credentials={credentials}
|
credentials={credentials}
|
||||||
|
signatureValidationCredentials={signatureValidationCredentials}
|
||||||
scmTypeOptions={scmTypeOptions}
|
scmTypeOptions={scmTypeOptions}
|
||||||
setScmSubFormState={setScmSubFormState}
|
setScmSubFormState={setScmSubFormState}
|
||||||
scmSubFormState={scmSubFormState}
|
scmSubFormState={scmSubFormState}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ describe('<ProjectForm />', () => {
|
|||||||
scm_clean: true,
|
scm_clean: true,
|
||||||
scm_track_submodules: false,
|
scm_track_submodules: false,
|
||||||
credential: 100,
|
credential: 100,
|
||||||
|
signature_validation_credential: 200,
|
||||||
organization: 2,
|
organization: 2,
|
||||||
scm_update_on_launch: true,
|
scm_update_on_launch: true,
|
||||||
scm_update_cache_timeout: 3,
|
scm_update_cache_timeout: 3,
|
||||||
@@ -35,6 +36,12 @@ describe('<ProjectForm />', () => {
|
|||||||
id: 2,
|
id: 2,
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
},
|
},
|
||||||
|
signature_validation_credential: {
|
||||||
|
id: 200,
|
||||||
|
credential_type_id: 6,
|
||||||
|
kind: 'cryptography',
|
||||||
|
name: 'Svc',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,6 +65,7 @@ describe('<ProjectForm />', () => {
|
|||||||
|
|
||||||
const scmCredentialResolve = {
|
const scmCredentialResolve = {
|
||||||
data: {
|
data: {
|
||||||
|
count: 1,
|
||||||
results: [
|
results: [
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
@@ -70,6 +78,7 @@ describe('<ProjectForm />', () => {
|
|||||||
|
|
||||||
const insightsCredentialResolve = {
|
const insightsCredentialResolve = {
|
||||||
data: {
|
data: {
|
||||||
|
count: 1,
|
||||||
results: [
|
results: [
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
@@ -80,6 +89,19 @@ describe('<ProjectForm />', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cryptographyCredentialResolve = {
|
||||||
|
data: {
|
||||||
|
count: 1,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'GPG Public Key',
|
||||||
|
kind: 'cryptography',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
RootAPI.readAssetVariables.mockResolvedValue({
|
RootAPI.readAssetVariables.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
@@ -89,12 +111,15 @@ describe('<ProjectForm />', () => {
|
|||||||
await ProjectsAPI.readOptions.mockImplementation(
|
await ProjectsAPI.readOptions.mockImplementation(
|
||||||
() => projectOptionsResolve
|
() => projectOptionsResolve
|
||||||
);
|
);
|
||||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
() => scmCredentialResolve
|
() => scmCredentialResolve
|
||||||
);
|
);
|
||||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
() => insightsCredentialResolve
|
() => insightsCredentialResolve
|
||||||
);
|
);
|
||||||
|
await CredentialTypesAPI.read.mockImplementation(
|
||||||
|
() => cryptographyCredentialResolve
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -153,9 +178,17 @@ describe('<ProjectForm />', () => {
|
|||||||
expect(
|
expect(
|
||||||
wrapper.find('FormGroup[label="Source Control Refspec"]').length
|
wrapper.find('FormGroup[label="Source Control Refspec"]').length
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Content Signature Validation Credential"]')
|
||||||
|
.length
|
||||||
|
).toBe(1);
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('FormGroup[label="Source Control Credential"]').length
|
wrapper.find('FormGroup[label="Source Control Credential"]').length
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Content Signature Validation Credential"]')
|
||||||
|
.length
|
||||||
|
).toBe(1);
|
||||||
expect(wrapper.find('FormGroup[label="Options"]').length).toBe(1);
|
expect(wrapper.find('FormGroup[label="Options"]').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -177,21 +210,52 @@ describe('<ProjectForm />', () => {
|
|||||||
id: 1,
|
id: 1,
|
||||||
name: 'organization',
|
name: 'organization',
|
||||||
});
|
});
|
||||||
wrapper.find('CredentialLookup').invoke('onBlur')();
|
wrapper
|
||||||
wrapper.find('CredentialLookup').invoke('onChange')({
|
.find('CredentialLookup[label="Source Control Credential"]')
|
||||||
|
.invoke('onBlur')();
|
||||||
|
wrapper
|
||||||
|
.find('CredentialLookup[label="Source Control Credential"]')
|
||||||
|
.invoke('onChange')({
|
||||||
id: 10,
|
id: 10,
|
||||||
name: 'credential',
|
name: 'credential',
|
||||||
});
|
});
|
||||||
|
wrapper
|
||||||
|
.find(
|
||||||
|
'CredentialLookup[label="Content Signature Validation Credential"]'
|
||||||
|
)
|
||||||
|
.invoke('onBlur')();
|
||||||
|
wrapper
|
||||||
|
.find(
|
||||||
|
'CredentialLookup[label="Content Signature Validation Credential"]'
|
||||||
|
)
|
||||||
|
.invoke('onChange')({
|
||||||
|
id: 20,
|
||||||
|
name: 'signature_validation_credential',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
|
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'organization',
|
name: 'organization',
|
||||||
});
|
});
|
||||||
expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('CredentialLookup[label="Source Control Credential"]')
|
||||||
|
.prop('value')
|
||||||
|
).toEqual({
|
||||||
id: 10,
|
id: 10,
|
||||||
name: 'credential',
|
name: 'credential',
|
||||||
});
|
});
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(
|
||||||
|
'CredentialLookup[label="Content Signature Validation Credential"]'
|
||||||
|
)
|
||||||
|
.prop('value')
|
||||||
|
).toEqual({
|
||||||
|
id: 20,
|
||||||
|
name: 'signature_validation_credential',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should display insights credential lookup when source control type is "insights"', async () => {
|
test('should display insights credential lookup when source control type is "insights"', async () => {
|
||||||
@@ -358,7 +422,9 @@ describe('<ProjectForm />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should display ContentError on throw', async () => {
|
test('should display ContentError on throw', async () => {
|
||||||
CredentialTypesAPI.read = () => Promise.reject(new Error());
|
CredentialTypesAPI.read.mockImplementationOnce(() =>
|
||||||
|
Promise.reject(new Error())
|
||||||
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<ProjectForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
|
<ProjectForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'styled-components/macro';
|
import 'styled-components/macro';
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import CredentialLookup from 'components/Lookup/CredentialLookup';
|
||||||
import FormField from 'components/FormField';
|
import FormField from 'components/FormField';
|
||||||
import getDocsBaseUrl from 'util/getDocsBaseUrl';
|
import getDocsBaseUrl from 'util/getDocsBaseUrl';
|
||||||
import { useConfig } from 'contexts/Config';
|
import { useConfig } from 'contexts/Config';
|
||||||
@@ -16,9 +18,22 @@ import projectHelpStrings from '../Project.helptext';
|
|||||||
|
|
||||||
const GitSubForm = ({
|
const GitSubForm = ({
|
||||||
credential,
|
credential,
|
||||||
|
signature_validation_credential,
|
||||||
onCredentialSelection,
|
onCredentialSelection,
|
||||||
|
onSignatureValidationCredentialSelection,
|
||||||
scmUpdateOnLaunch,
|
scmUpdateOnLaunch,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { setFieldValue, setFieldTouched } = useFormikContext();
|
||||||
|
|
||||||
|
const onCredentialChange = useCallback(
|
||||||
|
(value) => {
|
||||||
|
onSignatureValidationCredentialSelection('cryptography', value);
|
||||||
|
setFieldValue('signature_validation_credential', value);
|
||||||
|
setFieldTouched('signature_validation_credential', true, false);
|
||||||
|
},
|
||||||
|
[onSignatureValidationCredentialSelection, setFieldValue, setFieldTouched]
|
||||||
|
);
|
||||||
|
|
||||||
const docsURL = `${getDocsBaseUrl(
|
const docsURL = `${getDocsBaseUrl(
|
||||||
useConfig()
|
useConfig()
|
||||||
)}/html/userguide/projects.html#manage-playbooks-using-source-control`;
|
)}/html/userguide/projects.html#manage-playbooks-using-source-control`;
|
||||||
@@ -35,6 +50,13 @@ const GitSubForm = ({
|
|||||||
tooltipMaxWidth="400px"
|
tooltipMaxWidth="400px"
|
||||||
tooltip={projectHelpStrings.sourceControlRefspec(docsURL)}
|
tooltip={projectHelpStrings.sourceControlRefspec(docsURL)}
|
||||||
/>
|
/>
|
||||||
|
<CredentialLookup
|
||||||
|
credentialTypeId={signature_validation_credential.typeId}
|
||||||
|
label={t`Content Signature Validation Credential`}
|
||||||
|
onChange={onCredentialChange}
|
||||||
|
value={signature_validation_credential.value}
|
||||||
|
tooltip={projectHelpStrings.signatureValidation}
|
||||||
|
/>
|
||||||
<ScmCredentialFormField
|
<ScmCredentialFormField
|
||||||
credential={credential}
|
credential={credential}
|
||||||
onCredentialSelection={onCredentialSelection}
|
onCredentialSelection={onCredentialSelection}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export const Project = shape({
|
|||||||
summary_fields: shape({
|
summary_fields: shape({
|
||||||
organization: Organization,
|
organization: Organization,
|
||||||
credential: Credential,
|
credential: Credential,
|
||||||
|
signature_validation_credential: Credential,
|
||||||
last_job: shape({}),
|
last_job: shape({}),
|
||||||
last_update: shape({}),
|
last_update: shape({}),
|
||||||
created_by: shape({}),
|
created_by: shape({}),
|
||||||
@@ -163,6 +164,7 @@ export const Project = shape({
|
|||||||
scm_delete_on_update: bool,
|
scm_delete_on_update: bool,
|
||||||
scm_track_submodules: bool,
|
scm_track_submodules: bool,
|
||||||
credential: number,
|
credential: number,
|
||||||
|
signature_validation_credential: number,
|
||||||
status: oneOf([
|
status: oneOf([
|
||||||
'new',
|
'new',
|
||||||
'pending',
|
'pending',
|
||||||
|
|||||||
Reference in New Issue
Block a user