Skip to content

Commit 2e2cd7f

Browse files
authored
Implement project pulling from Azure DevOps using Service Principals (ansible#14628)
* Credential Lookup with multiple types Allow looking up a credential with one of multiple type IDs. * Allow Azure cred for SCM Allow selecting an Azure Resource Manager credential for Git-based SCMs. This is in order to enable using Azure Service Principals for project updates. * Implement Azure Service Principal Git This adds support for using an Azure Service Principal for project updates. --------- Signed-off-by: Patrick Uiterwijk <[email protected]>
1 parent 727278a commit 2e2cd7f

File tree

7 files changed

+49
-9
lines changed

7 files changed

+49
-9
lines changed

awx/main/models/projects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ def clean_credential(self):
160160
if self.scm_type == 'insights':
161161
if cred.kind != 'insights':
162162
raise ValidationError(_("Credential kind must be 'insights'."))
163-
elif cred.kind != 'scm':
164-
raise ValidationError(_("Credential kind must be 'scm'."))
163+
elif cred.kind != 'scm' and cred.kind != 'azure_rm':
164+
raise ValidationError(_("Credential kind must be 'scm' or 'azure_rm'." % cred.kind))
165165
try:
166166
if self.scm_type == 'insights':
167167
self.scm_url = settings.INSIGHTS_URL_BASE

awx/main/tasks/jobs.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,9 @@ def _build_scm_url_extra_vars(self, project_update):
12391239

12401240
return scm_url, extra_vars
12411241

1242+
def build_credentials_list(self, instance):
1243+
return [instance.credential]
1244+
12421245
def build_inventory(self, instance, private_data_dir):
12431246
return 'localhost,'
12441247

awx/playbooks/project_update.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,26 @@
3838
tags:
3939
- update_git
4040
block:
41+
- name: Get Azure access token
42+
when: "lookup('ansible.builtin.env', 'AZURE_CLIENT_ID') != ''"
43+
register: azure_token
44+
no_log: True
45+
check_mode: false
46+
azure.azcollection.azure_rm_accesstoken_info:
47+
scopes:
48+
# This is the audience for Azure DevOps, as per
49+
# https://learn.microsoft.com/en-us/rest/api/azure/devops/tokens/
50+
- 499b84ac-1321-427f-aa17-267ca6975798/.default
51+
52+
- name: Define git environment variables
53+
when: "azure_token is not skipped"
54+
no_log: True
55+
ansible.builtin.set_fact:
56+
git_environment:
57+
GIT_CONFIG_COUNT: 1
58+
GIT_CONFIG_KEY_0: http.extraHeader
59+
GIT_CONFIG_VALUE_0: "Authorization: Bearer {{ azure_token.access_token }}"
60+
4161
- name: Update project using git
4262
ansible.builtin.git:
4363
dest: "{{ project_path | quote }}"
@@ -47,6 +67,7 @@
4767
force: "{{ scm_clean }}"
4868
track_submodules: "{{ scm_track_submodules | default(omit) }}"
4969
accept_hostkey: "{{ scm_accept_hostkey | default(omit) }}"
70+
environment: "{{ git_environment | default({}) }}"
5071
register: git_result
5172

5273
- name: Set the git repository version

awx/ui/src/components/Lookup/CredentialLookup.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const QS_CONFIG = getQSConfig('credentials', {
3232
function CredentialLookup({
3333
autoPopulate,
3434
credentialTypeId,
35+
credentialTypeIds,
3536
credentialTypeKind,
3637
credentialTypeNamespace,
3738
fieldName,
@@ -61,6 +62,9 @@ function CredentialLookup({
6162
const typeIdParams = credentialTypeId
6263
? { credential_type: credentialTypeId }
6364
: {};
65+
const typeIdsParams = credentialTypeIds
66+
? { credential_type__in: credentialTypeIds.join() }
67+
: {};
6468
const typeKindParams = credentialTypeKind
6569
? { credential_type__kind: credentialTypeKind }
6670
: {};
@@ -72,6 +76,7 @@ function CredentialLookup({
7276
CredentialsAPI.read(
7377
mergeParams(params, {
7478
...typeIdParams,
79+
...typeIdsParams,
7580
...typeKindParams,
7681
...typeNamespaceParams,
7782
})
@@ -101,6 +106,7 @@ function CredentialLookup({
101106
autoPopulate,
102107
autoPopulateLookup,
103108
credentialTypeId,
109+
credentialTypeIds,
104110
credentialTypeKind,
105111
credentialTypeNamespace,
106112
history.location.search,

awx/ui/src/screens/Project/shared/ProjectForm.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ const fetchCredentials = async (credential) => {
3333
results: [scmCredentialType],
3434
},
3535
},
36+
{
37+
data: {
38+
results: [azurermCredentialType],
39+
},
40+
},
3641
{
3742
data: {
3843
results: [insightsCredentialType],
@@ -45,13 +50,14 @@ const fetchCredentials = async (credential) => {
4550
},
4651
] = await Promise.all([
4752
CredentialTypesAPI.read({ kind: 'scm' }),
53+
CredentialTypesAPI.read({ namespace: 'azure_rm' }),
4854
CredentialTypesAPI.read({ name: 'Insights' }),
4955
CredentialTypesAPI.read({ kind: 'cryptography' }),
5056
]);
5157

5258
if (!credential) {
5359
return {
54-
scm: { typeId: scmCredentialType.id },
60+
scm: { typeIds: [scmCredentialType.id, azurermCredentialType.id] },
5561
insights: { typeId: insightsCredentialType.id },
5662
cryptography: { typeId: cryptographyCredentialType.id },
5763
};
@@ -60,8 +66,12 @@ const fetchCredentials = async (credential) => {
6066
const { credential_type_id } = credential;
6167
return {
6268
scm: {
63-
typeId: scmCredentialType.id,
64-
value: credential_type_id === scmCredentialType.id ? credential : null,
69+
typeIds: [scmCredentialType.id, azurermCredentialType.id],
70+
value:
71+
credential_type_id === scmCredentialType.id ||
72+
credential_type_id === azurermCredentialType.id
73+
? credential
74+
: null,
6575
},
6676
insights: {
6777
typeId: insightsCredentialType.id,
@@ -367,13 +377,13 @@ function ProjectForm({ project, submitError, ...props }) {
367377
});
368378
const [scmTypeOptions, setScmTypeOptions] = useState(null);
369379
const [credentials, setCredentials] = useState({
370-
scm: { typeId: null, value: null },
380+
scm: { typeIds: null, value: null },
371381
insights: { typeId: null, value: null },
372382
cryptography: { typeId: null, value: null },
373383
});
374384
const [signatureValidationCredentials, setSignatureValidationCredentials] =
375385
useState({
376-
scm: { typeId: null, value: null },
386+
scm: { typeIds: null, value: null },
377387
insights: { typeId: null, value: null },
378388
cryptography: { typeId: null, value: null },
379389
});

awx/ui/src/screens/Project/shared/ProjectSubForms/SharedFields.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const ScmCredentialFormField = ({
5252

5353
return (
5454
<CredentialLookup
55-
credentialTypeId={credential.typeId}
55+
credentialTypeIds={credential.typeIds}
5656
label={t`Source Control Credential`}
5757
value={credential.value}
5858
onChange={onCredentialChange}

awxkit/awxkit/api/pages/projects.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def payload(self, organization, scm_type='git', **kwargs):
5050
def create_payload(self, name='', description='', scm_type='git', scm_url='', scm_branch='', organization=Organization, credential=None, **kwargs):
5151
if credential:
5252
if isinstance(credential, Credential):
53-
if credential.ds.credential_type.namespace not in ('scm', 'insights'):
53+
if credential.ds.credential_type.namespace not in ('scm', 'insights', 'azure_rm'):
5454
credential = None # ignore incompatible credential from HasCreate dependency injection
5555
elif credential in (Credential,):
5656
credential = (Credential, dict(credential_type=(True, dict(kind='scm'))))

0 commit comments

Comments
 (0)