mirror of
https://github.com/ansible/awx.git
synced 2026-02-17 19:20:05 -03:30
Compare commits
2 Commits
AAP-58577
...
poc-inv-cr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21d7ccc3ea | ||
|
|
75b9df3f33 |
@@ -15,7 +15,7 @@ from jinja2 import sandbox
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _, gettext_noop
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
@@ -47,7 +47,7 @@ from awx.main.models.rbac import (
|
|||||||
)
|
)
|
||||||
from awx.main.models import Team, Organization
|
from awx.main.models import Team, Organization
|
||||||
from awx.main.utils import encrypt_field
|
from awx.main.utils import encrypt_field
|
||||||
from . import injectors as builtin_injectors
|
from awx_plugins.credentials import injectors as builtin_injectors
|
||||||
|
|
||||||
__all__ = ['Credential', 'CredentialType', 'CredentialInputSource', 'build_safe_env']
|
__all__ = ['Credential', 'CredentialType', 'CredentialInputSource', 'build_safe_env']
|
||||||
|
|
||||||
@@ -601,666 +601,6 @@ class ManagedCredentialType(SimpleNamespace):
|
|||||||
return CredentialType(**self.get_creation_params())
|
return CredentialType(**self.get_creation_params())
|
||||||
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='ssh',
|
|
||||||
kind='ssh',
|
|
||||||
name=gettext_noop('Machine'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
|
||||||
{
|
|
||||||
'id': 'ssh_public_key_data',
|
|
||||||
'label': gettext_noop('Signed SSH Certificate'),
|
|
||||||
'type': 'string',
|
|
||||||
'multiline': True,
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
{
|
|
||||||
'id': 'become_method',
|
|
||||||
'label': gettext_noop('Privilege Escalation Method'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'become_username',
|
|
||||||
'label': gettext_noop('Privilege Escalation Username'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{'id': 'become_password', 'label': gettext_noop('Privilege Escalation Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='scm',
|
|
||||||
kind='scm',
|
|
||||||
name=gettext_noop('Source Control'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
|
||||||
{'id': 'ssh_key_data', 'label': gettext_noop('SCM Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
|
||||||
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='vault',
|
|
||||||
kind='vault',
|
|
||||||
name=gettext_noop('Vault'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'vault_password', 'label': gettext_noop('Vault Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
{
|
|
||||||
'id': 'vault_id',
|
|
||||||
'label': gettext_noop('Vault Identifier'),
|
|
||||||
'type': 'string',
|
|
||||||
'format': 'vault_id',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'Specify an (optional) Vault ID. This is '
|
|
||||||
'equivalent to specifying the --vault-id '
|
|
||||||
'Ansible parameter for providing multiple Vault '
|
|
||||||
'passwords. Note: this feature only works in '
|
|
||||||
'Ansible 2.4+.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['vault_password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='net',
|
|
||||||
kind='net',
|
|
||||||
name=gettext_noop('Network'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
|
||||||
{
|
|
||||||
'id': 'ssh_key_unlock',
|
|
||||||
'label': gettext_noop('Private Key Passphrase'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'authorize',
|
|
||||||
'label': gettext_noop('Authorize'),
|
|
||||||
'type': 'boolean',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'authorize_password',
|
|
||||||
'label': gettext_noop('Authorize Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'dependencies': {
|
|
||||||
'authorize_password': ['authorize'],
|
|
||||||
},
|
|
||||||
'required': ['username'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='aws',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Amazon Web Services'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Access Key'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Secret Key'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'security_token',
|
|
||||||
'label': gettext_noop('STS Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'Security Token Service (STS) is a web service '
|
|
||||||
'that enables you to request temporary, '
|
|
||||||
'limited-privilege credentials for AWS Identity '
|
|
||||||
'and Access Management (IAM) users.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['username', 'password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='openstack',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('OpenStack'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password (API Key)'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Host (Authentication URL)'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The host to authenticate with. For example, https://openstack.business.com/v2.0/'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'project',
|
|
||||||
'label': gettext_noop('Project (Tenant Name)'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'project_domain_name',
|
|
||||||
'label': gettext_noop('Project (Domain Name)'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'domain',
|
|
||||||
'label': gettext_noop('Domain Name'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'OpenStack domains define administrative boundaries. '
|
|
||||||
'It is only needed for Keystone v3 authentication '
|
|
||||||
'URLs. Refer to the documentation for '
|
|
||||||
'common scenarios.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'region',
|
|
||||||
'label': gettext_noop('Region Name'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('For some cloud providers, like OVH, region must be specified'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'verify_ssl',
|
|
||||||
'label': gettext_noop('Verify SSL'),
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['username', 'password', 'host', 'project'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='vmware',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('VMware vCenter'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('VCenter Host'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Enter the hostname or IP address that corresponds to your VMware vCenter.'),
|
|
||||||
},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'username', 'password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='satellite6',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Red Hat Satellite 6'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Satellite 6 URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Enter the URL that corresponds to your Red Hat Satellite 6 server. For example, https://satellite.example.org'),
|
|
||||||
},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'username', 'password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='gce',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Google Compute Engine'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'username',
|
|
||||||
'label': gettext_noop('Service Account Email Address'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The email address assigned to the Google Compute Engine service account.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'project',
|
|
||||||
'label': 'Project',
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'The Project ID is the GCE assigned identification. '
|
|
||||||
'It is often constructed as three words or two words '
|
|
||||||
'followed by a three-digit number. Examples: project-id-000 '
|
|
||||||
'and another-project-id'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'ssh_key_data',
|
|
||||||
'label': gettext_noop('RSA Private Key'),
|
|
||||||
'type': 'string',
|
|
||||||
'format': 'ssh_private_key',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('Paste the contents of the PEM file associated with the service account email.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['username', 'ssh_key_data'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='azure_rm',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Microsoft Azure Resource Manager'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'subscription',
|
|
||||||
'label': gettext_noop('Subscription ID'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Subscription ID is an Azure construct, which is mapped to a username.'),
|
|
||||||
},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'client', 'label': gettext_noop('Client ID'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'secret',
|
|
||||||
'label': gettext_noop('Client Secret'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'tenant', 'label': gettext_noop('Tenant ID'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'cloud_environment',
|
|
||||||
'label': gettext_noop('Azure Cloud Environment'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or Azure stack.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['subscription'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='github_token',
|
|
||||||
kind='token',
|
|
||||||
name=gettext_noop('GitHub Personal Access Token'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('This token needs to come from your profile settings in GitHub'),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'required': ['token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='gitlab_token',
|
|
||||||
kind='token',
|
|
||||||
name=gettext_noop('GitLab Personal Access Token'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('This token needs to come from your profile settings in GitLab'),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'required': ['token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='bitbucket_dc_token',
|
|
||||||
kind='token',
|
|
||||||
name=gettext_noop('Bitbucket Data Center HTTP Access Token'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('This token needs to come from your user settings in Bitbucket'),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'required': ['token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='insights',
|
|
||||||
kind='insights',
|
|
||||||
name=gettext_noop('Insights'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
|
||||||
],
|
|
||||||
'required': ['username', 'password'],
|
|
||||||
},
|
|
||||||
injectors={
|
|
||||||
'extra_vars': {
|
|
||||||
"scm_username": "{{username}}",
|
|
||||||
"scm_password": "{{password}}",
|
|
||||||
},
|
|
||||||
'env': {
|
|
||||||
'INSIGHTS_USER': '{{username}}',
|
|
||||||
'INSIGHTS_PASSWORD': '{{password}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='rhv',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Red Hat Virtualization'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'host', 'label': gettext_noop('Host (Authentication URL)'), 'type': 'string', 'help_text': gettext_noop('The host to authenticate with.')},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'ca_file',
|
|
||||||
'label': gettext_noop('CA File'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Absolute file path to the CA file to use (optional)'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'username', 'password'],
|
|
||||||
},
|
|
||||||
injectors={
|
|
||||||
# The duplication here is intentional; the ovirt4 inventory plugin
|
|
||||||
# writes a .ini file for authentication, while the ansible modules for
|
|
||||||
# ovirt4 use a separate authentication process that support
|
|
||||||
# environment variables; by injecting both, we support both
|
|
||||||
'file': {
|
|
||||||
'template': '\n'.join(
|
|
||||||
[
|
|
||||||
'[ovirt]',
|
|
||||||
'ovirt_url={{host}}',
|
|
||||||
'ovirt_username={{username}}',
|
|
||||||
'ovirt_password={{password}}',
|
|
||||||
'{% if ca_file %}ovirt_ca_file={{ca_file}}{% endif %}',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'env': {'OVIRT_INI_PATH': '{{tower.filename}}', 'OVIRT_URL': '{{host}}', 'OVIRT_USERNAME': '{{username}}', 'OVIRT_PASSWORD': '{{password}}'},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='controller',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Red Hat Ansible Automation Platform'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Red Hat Ansible Automation Platform'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Red Hat Ansible Automation Platform base URL to authenticate with.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'username',
|
|
||||||
'label': gettext_noop('Username'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'Red Hat Ansible Automation Platform username id to authenticate as.This should not be set if an OAuth token is being used.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'oauth_token',
|
|
||||||
'label': gettext_noop('OAuth Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('An OAuth token to use to authenticate with.This should not be set if username/password are being used.'),
|
|
||||||
},
|
|
||||||
{'id': 'verify_ssl', 'label': gettext_noop('Verify SSL'), 'type': 'boolean', 'secret': False},
|
|
||||||
],
|
|
||||||
'required': ['host'],
|
|
||||||
},
|
|
||||||
injectors={
|
|
||||||
'env': {
|
|
||||||
'TOWER_HOST': '{{host}}',
|
|
||||||
'TOWER_USERNAME': '{{username}}',
|
|
||||||
'TOWER_PASSWORD': '{{password}}',
|
|
||||||
'TOWER_VERIFY_SSL': '{{verify_ssl}}',
|
|
||||||
'TOWER_OAUTH_TOKEN': '{{oauth_token}}',
|
|
||||||
'CONTROLLER_HOST': '{{host}}',
|
|
||||||
'CONTROLLER_USERNAME': '{{username}}',
|
|
||||||
'CONTROLLER_PASSWORD': '{{password}}',
|
|
||||||
'CONTROLLER_VERIFY_SSL': '{{verify_ssl}}',
|
|
||||||
'CONTROLLER_OAUTH_TOKEN': '{{oauth_token}}',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='kubernetes_bearer_token',
|
|
||||||
kind='kubernetes',
|
|
||||||
name=gettext_noop('OpenShift or Kubernetes API Bearer Token'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('OpenShift or Kubernetes API Endpoint'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The OpenShift or Kubernetes API Endpoint to authenticate with.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'bearer_token',
|
|
||||||
'label': gettext_noop('API authentication bearer token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'verify_ssl',
|
|
||||||
'label': gettext_noop('Verify SSL'),
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'ssl_ca_cert',
|
|
||||||
'label': gettext_noop('Certificate Authority data'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'bearer_token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='registry',
|
|
||||||
kind='registry',
|
|
||||||
name=gettext_noop('Container Registry'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Authentication URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Authentication endpoint for the container registry.'),
|
|
||||||
'default': 'quay.io',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'username',
|
|
||||||
'label': gettext_noop('Username'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password or Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('A password or token used to authenticate with'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'verify_ssl',
|
|
||||||
'label': gettext_noop('Verify SSL'),
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='galaxy_api_token',
|
|
||||||
kind='galaxy',
|
|
||||||
name=gettext_noop('Ansible Galaxy/Automation Hub API Token'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'url',
|
|
||||||
'label': gettext_noop('Galaxy Server URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The URL of the Galaxy instance to connect to.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'auth_url',
|
|
||||||
'label': gettext_noop('Auth Server URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The URL of a Keycloak server token_endpoint, if using SSO auth.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('API Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('A token to use for authentication against the Galaxy instance.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['url'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='gpg_public_key',
|
|
||||||
kind='cryptography',
|
|
||||||
name=gettext_noop('GPG Public Key'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'gpg_public_key',
|
|
||||||
'label': gettext_noop('GPG Public Key'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('GPG Public Key used to validate content signatures.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['gpg_public_key'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='terraform',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Terraform backend configuration'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'configuration',
|
|
||||||
'label': gettext_noop('Backend configuration'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('Terraform backend config as Hashicorp configuration language.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'gce_credentials',
|
|
||||||
'label': gettext_noop('Google Cloud Platform account credentials'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('Google Cloud Platform account credentials in JSON format.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['configuration'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialInputSource(PrimordialModel):
|
class CredentialInputSource(PrimordialModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
@@ -1324,6 +664,7 @@ class CredentialInputSource(PrimordialModel):
|
|||||||
view_name = 'api:credential_input_source_detail'
|
view_name = 'api:credential_input_source_detail'
|
||||||
return reverse(view_name, kwargs={'pk': self.pk}, request=request)
|
return reverse(view_name, kwargs={'pk': self.pk}, request=request)
|
||||||
|
|
||||||
|
from awx_plugins.credentials.plugins import *
|
||||||
|
|
||||||
for ns, plugin in credential_plugins.items():
|
for ns, plugin in credential_plugins.items():
|
||||||
CredentialType.load_plugin(ns, plugin)
|
CredentialType.load_plugin(ns, plugin)
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ import copy
|
|||||||
import os.path
|
import os.path
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import yaml
|
|
||||||
import tempfile
|
|
||||||
import stat
|
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models, connection
|
from django.db import models, connection
|
||||||
@@ -28,6 +24,7 @@ from django.db.models import Q
|
|||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
|
|
||||||
from ansible_base.lib.utils.models import prevent_search
|
from ansible_base.lib.utils.models import prevent_search
|
||||||
|
from awx_plugins.inventory.plugins import PluginFileInjector
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
@@ -52,11 +49,9 @@ from awx.main.models.notifications import (
|
|||||||
NotificationTemplate,
|
NotificationTemplate,
|
||||||
JobNotificationMixin,
|
JobNotificationMixin,
|
||||||
)
|
)
|
||||||
from awx.main.models.credential.injectors import _openstack_data
|
|
||||||
from awx.main.utils import _inventory_updates
|
from awx.main.utils import _inventory_updates
|
||||||
from awx.main.utils.safe_yaml import sanitize_jinja
|
from awx.main.utils.safe_yaml import sanitize_jinja
|
||||||
from awx.main.utils.execution_environments import to_container_path, get_control_plane_execution_environment
|
from awx.main.utils.execution_environments import get_control_plane_execution_environment
|
||||||
from awx.main.utils.licensing import server_product_name
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'SmartInventoryMembership', 'HostMetric', 'HostMetricSummaryMonthly']
|
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'SmartInventoryMembership', 'HostMetric', 'HostMetricSummaryMonthly']
|
||||||
@@ -1427,297 +1422,5 @@ class CustomInventoryScript(CommonModelNameNotUnique):
|
|||||||
return reverse('api:inventory_script_detail', kwargs={'pk': self.pk}, request=request)
|
return reverse('api:inventory_script_detail', kwargs={'pk': self.pk}, request=request)
|
||||||
|
|
||||||
|
|
||||||
class PluginFileInjector(object):
|
|
||||||
plugin_name = None # Ansible core name used to reference plugin
|
|
||||||
# base injector should be one of None, "managed", or "template"
|
|
||||||
# this dictates which logic to borrow from playbook injectors
|
|
||||||
base_injector = None
|
|
||||||
# every source should have collection, these are for the collection name
|
|
||||||
namespace = None
|
|
||||||
collection = None
|
|
||||||
collection_migration = '2.9' # Starting with this version, we use collections
|
|
||||||
use_fqcn = False # plugin: name versus plugin: namespace.collection.name
|
|
||||||
|
|
||||||
# TODO: delete this method and update unit tests
|
|
||||||
@classmethod
|
|
||||||
def get_proper_name(cls):
|
|
||||||
if cls.plugin_name is None:
|
|
||||||
return None
|
|
||||||
return f'{cls.namespace}.{cls.collection}.{cls.plugin_name}'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self):
|
|
||||||
"""Inventory filename for using the inventory plugin
|
|
||||||
This is created dynamically, but the auto plugin requires this exact naming
|
|
||||||
"""
|
|
||||||
return '{0}.yml'.format(self.plugin_name)
|
|
||||||
|
|
||||||
def inventory_contents(self, inventory_update, private_data_dir):
|
|
||||||
"""Returns a string that is the content for the inventory file for the inventory plugin"""
|
|
||||||
return yaml.safe_dump(self.inventory_as_dict(inventory_update, private_data_dir), default_flow_style=False, width=1000)
|
|
||||||
|
|
||||||
def inventory_as_dict(self, inventory_update, private_data_dir):
|
|
||||||
source_vars = dict(inventory_update.source_vars_dict) # make a copy
|
|
||||||
'''
|
|
||||||
None conveys that we should use the user-provided plugin.
|
|
||||||
Note that a plugin value of '' should still be overridden.
|
|
||||||
'''
|
|
||||||
if self.plugin_name is not None:
|
|
||||||
if hasattr(self, 'downstream_namespace') and server_product_name() != 'AWX':
|
|
||||||
source_vars['plugin'] = f'{self.downstream_namespace}.{self.downstream_collection}.{self.plugin_name}'
|
|
||||||
elif self.use_fqcn:
|
|
||||||
source_vars['plugin'] = f'{self.namespace}.{self.collection}.{self.plugin_name}'
|
|
||||||
else:
|
|
||||||
source_vars['plugin'] = self.plugin_name
|
|
||||||
return source_vars
|
|
||||||
|
|
||||||
def build_env(self, inventory_update, env, private_data_dir, private_data_files):
|
|
||||||
injector_env = self.get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
env.update(injector_env)
|
|
||||||
# All CLOUD_PROVIDERS sources implement as inventory plugin from collection
|
|
||||||
env['ANSIBLE_INVENTORY_ENABLED'] = 'auto'
|
|
||||||
return env
|
|
||||||
|
|
||||||
def _get_shared_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
"""By default, we will apply the standard managed injectors"""
|
|
||||||
injected_env = {}
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
# some sources may have no credential, specifically ec2
|
|
||||||
if credential is None:
|
|
||||||
return injected_env
|
|
||||||
if self.base_injector in ('managed', 'template'):
|
|
||||||
injected_env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk) # so injector knows this is inventory
|
|
||||||
if self.base_injector == 'managed':
|
|
||||||
from awx.main.models.credential import injectors as builtin_injectors
|
|
||||||
|
|
||||||
cred_kind = inventory_update.source.replace('ec2', 'aws')
|
|
||||||
if cred_kind in dir(builtin_injectors):
|
|
||||||
getattr(builtin_injectors, cred_kind)(credential, injected_env, private_data_dir)
|
|
||||||
elif self.base_injector == 'template':
|
|
||||||
safe_env = injected_env.copy()
|
|
||||||
args = []
|
|
||||||
credential.credential_type.inject_credential(credential, injected_env, safe_env, args, private_data_dir)
|
|
||||||
# NOTE: safe_env is handled externally to injector class by build_safe_env static method
|
|
||||||
# that means that managed injectors must only inject detectable env keys
|
|
||||||
# enforcement of this is accomplished by tests
|
|
||||||
return injected_env
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
env = self._get_shared_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
return env
|
|
||||||
|
|
||||||
def build_private_data(self, inventory_update, private_data_dir):
|
|
||||||
return self.build_plugin_private_data(inventory_update, private_data_dir)
|
|
||||||
|
|
||||||
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class azure_rm(PluginFileInjector):
|
|
||||||
plugin_name = 'azure_rm'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'azure'
|
|
||||||
collection = 'azcollection'
|
|
||||||
|
|
||||||
def get_plugin_env(self, *args, **kwargs):
|
|
||||||
ret = super(azure_rm, self).get_plugin_env(*args, **kwargs)
|
|
||||||
# We need native jinja2 types so that tags can give JSON null value
|
|
||||||
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class ec2(PluginFileInjector):
|
|
||||||
plugin_name = 'aws_ec2'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'amazon'
|
|
||||||
collection = 'aws'
|
|
||||||
|
|
||||||
def get_plugin_env(self, *args, **kwargs):
|
|
||||||
ret = super(ec2, self).get_plugin_env(*args, **kwargs)
|
|
||||||
# We need native jinja2 types so that ec2_state_code will give integer
|
|
||||||
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class gce(PluginFileInjector):
|
|
||||||
plugin_name = 'gcp_compute'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'google'
|
|
||||||
collection = 'cloud'
|
|
||||||
|
|
||||||
def get_plugin_env(self, *args, **kwargs):
|
|
||||||
ret = super(gce, self).get_plugin_env(*args, **kwargs)
|
|
||||||
# We need native jinja2 types so that ip addresses can give JSON null value
|
|
||||||
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def inventory_as_dict(self, inventory_update, private_data_dir):
|
|
||||||
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
# InventorySource.source_vars take precedence over ENV vars
|
|
||||||
if 'projects' not in ret:
|
|
||||||
ret['projects'] = [credential.get_input('project', default='')]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class vmware(PluginFileInjector):
|
|
||||||
plugin_name = 'vmware_vm_inventory'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'community'
|
|
||||||
collection = 'vmware'
|
|
||||||
|
|
||||||
|
|
||||||
class openstack(PluginFileInjector):
|
|
||||||
plugin_name = 'openstack'
|
|
||||||
namespace = 'openstack'
|
|
||||||
collection = 'cloud'
|
|
||||||
|
|
||||||
def _get_clouds_dict(self, inventory_update, cred, private_data_dir):
|
|
||||||
openstack_data = _openstack_data(cred)
|
|
||||||
|
|
||||||
openstack_data['clouds']['devstack']['private'] = inventory_update.source_vars_dict.get('private', True)
|
|
||||||
ansible_variables = {
|
|
||||||
'use_hostnames': True,
|
|
||||||
'expand_hostvars': False,
|
|
||||||
'fail_on_errors': True,
|
|
||||||
}
|
|
||||||
provided_count = 0
|
|
||||||
for var_name in ansible_variables:
|
|
||||||
if var_name in inventory_update.source_vars_dict:
|
|
||||||
ansible_variables[var_name] = inventory_update.source_vars_dict[var_name]
|
|
||||||
provided_count += 1
|
|
||||||
if provided_count:
|
|
||||||
# Must we provide all 3 because the user provides any 1 of these??
|
|
||||||
# this probably results in some incorrect mangling of the defaults
|
|
||||||
openstack_data['ansible'] = ansible_variables
|
|
||||||
return openstack_data
|
|
||||||
|
|
||||||
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
private_data = {'credentials': {}}
|
|
||||||
|
|
||||||
openstack_data = self._get_clouds_dict(inventory_update, credential, private_data_dir)
|
|
||||||
private_data['credentials'][credential] = yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True)
|
|
||||||
return private_data
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
env = super(openstack, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
cred_data = private_data_files['credentials']
|
|
||||||
env['OS_CLIENT_CONFIG_FILE'] = to_container_path(cred_data[credential], private_data_dir)
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
class rhv(PluginFileInjector):
|
|
||||||
"""ovirt uses the custom credential templating, and that is all"""
|
|
||||||
|
|
||||||
plugin_name = 'ovirt'
|
|
||||||
base_injector = 'template'
|
|
||||||
initial_version = '2.9'
|
|
||||||
namespace = 'ovirt'
|
|
||||||
collection = 'ovirt'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'rhv'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
|
|
||||||
class satellite6(PluginFileInjector):
|
|
||||||
plugin_name = 'foreman'
|
|
||||||
namespace = 'theforeman'
|
|
||||||
collection = 'foreman'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'satellite'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
# this assumes that this is merged
|
|
||||||
# https://github.com/ansible/ansible/pull/52693
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
ret = super(satellite6, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
if credential:
|
|
||||||
ret['FOREMAN_SERVER'] = credential.get_input('host', default='')
|
|
||||||
ret['FOREMAN_USER'] = credential.get_input('username', default='')
|
|
||||||
ret['FOREMAN_PASSWORD'] = credential.get_input('password', default='')
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class terraform(PluginFileInjector):
|
|
||||||
plugin_name = 'terraform_state'
|
|
||||||
namespace = 'cloud'
|
|
||||||
collection = 'terraform'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
def inventory_as_dict(self, inventory_update, private_data_dir):
|
|
||||||
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
config_cred = credential.get_input('configuration')
|
|
||||||
if config_cred:
|
|
||||||
handle, path = tempfile.mkstemp(dir=os.path.join(private_data_dir, 'env'))
|
|
||||||
with os.fdopen(handle, 'w') as f:
|
|
||||||
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
|
||||||
f.write(config_cred)
|
|
||||||
ret['backend_config_files'] = to_container_path(path, private_data_dir)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
|
|
||||||
private_data = {'credentials': {}}
|
|
||||||
gce_cred = credential.get_input('gce_credentials', default=None)
|
|
||||||
if gce_cred:
|
|
||||||
private_data['credentials'][credential] = gce_cred
|
|
||||||
return private_data
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
env = super(terraform, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
cred_data = private_data_files['credentials']
|
|
||||||
if credential in cred_data:
|
|
||||||
env['GOOGLE_BACKEND_CREDENTIALS'] = to_container_path(cred_data[credential], private_data_dir)
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
class controller(PluginFileInjector):
|
|
||||||
plugin_name = 'tower' # TODO: relying on routing for now, update after EEs pick up revised collection
|
|
||||||
base_injector = 'template'
|
|
||||||
namespace = 'awx'
|
|
||||||
collection = 'awx'
|
|
||||||
downstream_namespace = 'ansible'
|
|
||||||
downstream_collection = 'controller'
|
|
||||||
|
|
||||||
|
|
||||||
class insights(PluginFileInjector):
|
|
||||||
plugin_name = 'insights'
|
|
||||||
base_injector = 'template'
|
|
||||||
namespace = 'redhatinsights'
|
|
||||||
collection = 'insights'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'insights'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
|
|
||||||
class openshift_virtualization(PluginFileInjector):
|
|
||||||
plugin_name = 'kubevirt'
|
|
||||||
base_injector = 'template'
|
|
||||||
namespace = 'kubevirt'
|
|
||||||
collection = 'core'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'openshift_virtualization'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
|
|
||||||
class constructed(PluginFileInjector):
|
|
||||||
plugin_name = 'constructed'
|
|
||||||
namespace = 'ansible'
|
|
||||||
collection = 'builtin'
|
|
||||||
|
|
||||||
def build_env(self, *args, **kwargs):
|
|
||||||
env = super().build_env(*args, **kwargs)
|
|
||||||
# Enable script inventory plugin so we pick up the script files from source inventories
|
|
||||||
env['ANSIBLE_INVENTORY_ENABLED'] += ',script'
|
|
||||||
env['ANSIBLE_INVENTORY_ANY_UNPARSED_IS_FAILED'] = 'True'
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
for cls in PluginFileInjector.__subclasses__():
|
for cls in PluginFileInjector.__subclasses__():
|
||||||
InventorySourceOptions.injectors[cls.__name__] = cls
|
InventorySourceOptions.injectors[cls.__name__] = cls
|
||||||
|
|||||||
0
awx_plugins/__init__.py
Normal file
0
awx_plugins/__init__.py
Normal file
0
awx_plugins/credentials/__init__.py
Normal file
0
awx_plugins/credentials/__init__.py
Normal file
666
awx_plugins/credentials/plugins.py
Normal file
666
awx_plugins/credentials/plugins.py
Normal file
@@ -0,0 +1,666 @@
|
|||||||
|
|
||||||
|
# Django
|
||||||
|
from django.utils.translation import gettext_noop
|
||||||
|
|
||||||
|
# AWX
|
||||||
|
from awx.main.models.credential import ManagedCredentialType
|
||||||
|
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='ssh',
|
||||||
|
kind='ssh',
|
||||||
|
name=gettext_noop('Machine'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
||||||
|
{
|
||||||
|
'id': 'ssh_public_key_data',
|
||||||
|
'label': gettext_noop('Signed SSH Certificate'),
|
||||||
|
'type': 'string',
|
||||||
|
'multiline': True,
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
{
|
||||||
|
'id': 'become_method',
|
||||||
|
'label': gettext_noop('Privilege Escalation Method'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'become_username',
|
||||||
|
'label': gettext_noop('Privilege Escalation Username'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{'id': 'become_password', 'label': gettext_noop('Privilege Escalation Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='scm',
|
||||||
|
kind='scm',
|
||||||
|
name=gettext_noop('Source Control'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
||||||
|
{'id': 'ssh_key_data', 'label': gettext_noop('SCM Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
||||||
|
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='vault',
|
||||||
|
kind='vault',
|
||||||
|
name=gettext_noop('Vault'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'vault_password', 'label': gettext_noop('Vault Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
{
|
||||||
|
'id': 'vault_id',
|
||||||
|
'label': gettext_noop('Vault Identifier'),
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'vault_id',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'Specify an (optional) Vault ID. This is '
|
||||||
|
'equivalent to specifying the --vault-id '
|
||||||
|
'Ansible parameter for providing multiple Vault '
|
||||||
|
'passwords. Note: this feature only works in '
|
||||||
|
'Ansible 2.4+.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['vault_password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='net',
|
||||||
|
kind='net',
|
||||||
|
name=gettext_noop('Network'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
||||||
|
{
|
||||||
|
'id': 'ssh_key_unlock',
|
||||||
|
'label': gettext_noop('Private Key Passphrase'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'authorize',
|
||||||
|
'label': gettext_noop('Authorize'),
|
||||||
|
'type': 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'authorize_password',
|
||||||
|
'label': gettext_noop('Authorize Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'dependencies': {
|
||||||
|
'authorize_password': ['authorize'],
|
||||||
|
},
|
||||||
|
'required': ['username'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='aws',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Amazon Web Services'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Access Key'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Secret Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'security_token',
|
||||||
|
'label': gettext_noop('STS Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'Security Token Service (STS) is a web service '
|
||||||
|
'that enables you to request temporary, '
|
||||||
|
'limited-privilege credentials for AWS Identity '
|
||||||
|
'and Access Management (IAM) users.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['username', 'password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='openstack',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('OpenStack'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password (API Key)'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Host (Authentication URL)'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The host to authenticate with. For example, https://openstack.business.com/v2.0/'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'project',
|
||||||
|
'label': gettext_noop('Project (Tenant Name)'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'project_domain_name',
|
||||||
|
'label': gettext_noop('Project (Domain Name)'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'domain',
|
||||||
|
'label': gettext_noop('Domain Name'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'OpenStack domains define administrative boundaries. '
|
||||||
|
'It is only needed for Keystone v3 authentication '
|
||||||
|
'URLs. Refer to the documentation for '
|
||||||
|
'common scenarios.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'region',
|
||||||
|
'label': gettext_noop('Region Name'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('For some cloud providers, like OVH, region must be specified'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'verify_ssl',
|
||||||
|
'label': gettext_noop('Verify SSL'),
|
||||||
|
'type': 'boolean',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['username', 'password', 'host', 'project'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='vmware',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('VMware vCenter'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('VCenter Host'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Enter the hostname or IP address that corresponds to your VMware vCenter.'),
|
||||||
|
},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'username', 'password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='satellite6',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Red Hat Satellite 6'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Satellite 6 URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Enter the URL that corresponds to your Red Hat Satellite 6 server. For example, https://satellite.example.org'),
|
||||||
|
},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'username', 'password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='gce',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Google Compute Engine'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'username',
|
||||||
|
'label': gettext_noop('Service Account Email Address'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The email address assigned to the Google Compute Engine service account.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'project',
|
||||||
|
'label': 'Project',
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'The Project ID is the GCE assigned identification. '
|
||||||
|
'It is often constructed as three words or two words '
|
||||||
|
'followed by a three-digit number. Examples: project-id-000 '
|
||||||
|
'and another-project-id'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'ssh_key_data',
|
||||||
|
'label': gettext_noop('RSA Private Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'ssh_private_key',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('Paste the contents of the PEM file associated with the service account email.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['username', 'ssh_key_data'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='azure_rm',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Microsoft Azure Resource Manager'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'subscription',
|
||||||
|
'label': gettext_noop('Subscription ID'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Subscription ID is an Azure construct, which is mapped to a username.'),
|
||||||
|
},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'client', 'label': gettext_noop('Client ID'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'secret',
|
||||||
|
'label': gettext_noop('Client Secret'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'tenant', 'label': gettext_noop('Tenant ID'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'cloud_environment',
|
||||||
|
'label': gettext_noop('Azure Cloud Environment'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or Azure stack.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['subscription'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='github_token',
|
||||||
|
kind='token',
|
||||||
|
name=gettext_noop('GitHub Personal Access Token'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('This token needs to come from your profile settings in GitHub'),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'required': ['token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='gitlab_token',
|
||||||
|
kind='token',
|
||||||
|
name=gettext_noop('GitLab Personal Access Token'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('This token needs to come from your profile settings in GitLab'),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'required': ['token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='bitbucket_dc_token',
|
||||||
|
kind='token',
|
||||||
|
name=gettext_noop('Bitbucket Data Center HTTP Access Token'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('This token needs to come from your user settings in Bitbucket'),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'required': ['token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='insights',
|
||||||
|
kind='insights',
|
||||||
|
name=gettext_noop('Insights'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
||||||
|
],
|
||||||
|
'required': ['username', 'password'],
|
||||||
|
},
|
||||||
|
injectors={
|
||||||
|
'extra_vars': {
|
||||||
|
"scm_username": "{{username}}",
|
||||||
|
"scm_password": "{{password}}",
|
||||||
|
},
|
||||||
|
'env': {
|
||||||
|
'INSIGHTS_USER': '{{username}}',
|
||||||
|
'INSIGHTS_PASSWORD': '{{password}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='rhv',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Red Hat Virtualization'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'host', 'label': gettext_noop('Host (Authentication URL)'), 'type': 'string', 'help_text': gettext_noop('The host to authenticate with.')},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'ca_file',
|
||||||
|
'label': gettext_noop('CA File'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Absolute file path to the CA file to use (optional)'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'username', 'password'],
|
||||||
|
},
|
||||||
|
injectors={
|
||||||
|
# The duplication here is intentional; the ovirt4 inventory plugin
|
||||||
|
# writes a .ini file for authentication, while the ansible modules for
|
||||||
|
# ovirt4 use a separate authentication process that support
|
||||||
|
# environment variables; by injecting both, we support both
|
||||||
|
'file': {
|
||||||
|
'template': '\n'.join(
|
||||||
|
[
|
||||||
|
'[ovirt]',
|
||||||
|
'ovirt_url={{host}}',
|
||||||
|
'ovirt_username={{username}}',
|
||||||
|
'ovirt_password={{password}}',
|
||||||
|
'{% if ca_file %}ovirt_ca_file={{ca_file}}{% endif %}',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'env': {'OVIRT_INI_PATH': '{{tower.filename}}', 'OVIRT_URL': '{{host}}', 'OVIRT_USERNAME': '{{username}}', 'OVIRT_PASSWORD': '{{password}}'},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='controller',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Red Hat Ansible Automation Platform'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Red Hat Ansible Automation Platform'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Red Hat Ansible Automation Platform base URL to authenticate with.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'username',
|
||||||
|
'label': gettext_noop('Username'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'Red Hat Ansible Automation Platform username id to authenticate as.This should not be set if an OAuth token is being used.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'oauth_token',
|
||||||
|
'label': gettext_noop('OAuth Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('An OAuth token to use to authenticate with.This should not be set if username/password are being used.'),
|
||||||
|
},
|
||||||
|
{'id': 'verify_ssl', 'label': gettext_noop('Verify SSL'), 'type': 'boolean', 'secret': False},
|
||||||
|
],
|
||||||
|
'required': ['host'],
|
||||||
|
},
|
||||||
|
injectors={
|
||||||
|
'env': {
|
||||||
|
'TOWER_HOST': '{{host}}',
|
||||||
|
'TOWER_USERNAME': '{{username}}',
|
||||||
|
'TOWER_PASSWORD': '{{password}}',
|
||||||
|
'TOWER_VERIFY_SSL': '{{verify_ssl}}',
|
||||||
|
'TOWER_OAUTH_TOKEN': '{{oauth_token}}',
|
||||||
|
'CONTROLLER_HOST': '{{host}}',
|
||||||
|
'CONTROLLER_USERNAME': '{{username}}',
|
||||||
|
'CONTROLLER_PASSWORD': '{{password}}',
|
||||||
|
'CONTROLLER_VERIFY_SSL': '{{verify_ssl}}',
|
||||||
|
'CONTROLLER_OAUTH_TOKEN': '{{oauth_token}}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='kubernetes_bearer_token',
|
||||||
|
kind='kubernetes',
|
||||||
|
name=gettext_noop('OpenShift or Kubernetes API Bearer Token'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('OpenShift or Kubernetes API Endpoint'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The OpenShift or Kubernetes API Endpoint to authenticate with.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'bearer_token',
|
||||||
|
'label': gettext_noop('API authentication bearer token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'verify_ssl',
|
||||||
|
'label': gettext_noop('Verify SSL'),
|
||||||
|
'type': 'boolean',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'ssl_ca_cert',
|
||||||
|
'label': gettext_noop('Certificate Authority data'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'bearer_token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='registry',
|
||||||
|
kind='registry',
|
||||||
|
name=gettext_noop('Container Registry'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Authentication URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Authentication endpoint for the container registry.'),
|
||||||
|
'default': 'quay.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'username',
|
||||||
|
'label': gettext_noop('Username'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password or Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('A password or token used to authenticate with'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'verify_ssl',
|
||||||
|
'label': gettext_noop('Verify SSL'),
|
||||||
|
'type': 'boolean',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='galaxy_api_token',
|
||||||
|
kind='galaxy',
|
||||||
|
name=gettext_noop('Ansible Galaxy/Automation Hub API Token'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'url',
|
||||||
|
'label': gettext_noop('Galaxy Server URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The URL of the Galaxy instance to connect to.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'auth_url',
|
||||||
|
'label': gettext_noop('Auth Server URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The URL of a Keycloak server token_endpoint, if using SSO auth.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('API Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('A token to use for authentication against the Galaxy instance.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['url'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='gpg_public_key',
|
||||||
|
kind='cryptography',
|
||||||
|
name=gettext_noop('GPG Public Key'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'gpg_public_key',
|
||||||
|
'label': gettext_noop('GPG Public Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('GPG Public Key used to validate content signatures.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['gpg_public_key'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='terraform',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Terraform backend configuration'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'configuration',
|
||||||
|
'label': gettext_noop('Backend configuration'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('Terraform backend config as Hashicorp configuration language.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'gce_credentials',
|
||||||
|
'label': gettext_noop('Google Cloud Platform account credentials'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('Google Cloud Platform account credentials in JSON format.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['configuration'],
|
||||||
|
},
|
||||||
|
)
|
||||||
0
awx_plugins/inventory/__init__.py
Normal file
0
awx_plugins/inventory/__init__.py
Normal file
303
awx_plugins/inventory/plugins.py
Normal file
303
awx_plugins/inventory/plugins.py
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
import yaml
|
||||||
|
import stat
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from awx_plugins.credentials.injectors import _openstack_data
|
||||||
|
from awx.main.utils.execution_environments import to_container_path
|
||||||
|
|
||||||
|
from awx.main.utils.licensing import server_product_name
|
||||||
|
|
||||||
|
|
||||||
|
class PluginFileInjector(object):
|
||||||
|
plugin_name = None # Ansible core name used to reference plugin
|
||||||
|
# base injector should be one of None, "managed", or "template"
|
||||||
|
# this dictates which logic to borrow from playbook injectors
|
||||||
|
base_injector = None
|
||||||
|
# every source should have collection, these are for the collection name
|
||||||
|
namespace = None
|
||||||
|
collection = None
|
||||||
|
collection_migration = '2.9' # Starting with this version, we use collections
|
||||||
|
use_fqcn = False # plugin: name versus plugin: namespace.collection.name
|
||||||
|
|
||||||
|
# TODO: delete this method and update unit tests
|
||||||
|
@classmethod
|
||||||
|
def get_proper_name(cls):
|
||||||
|
if cls.plugin_name is None:
|
||||||
|
return None
|
||||||
|
return f'{cls.namespace}.{cls.collection}.{cls.plugin_name}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filename(self):
|
||||||
|
"""Inventory filename for using the inventory plugin
|
||||||
|
This is created dynamically, but the auto plugin requires this exact naming
|
||||||
|
"""
|
||||||
|
return '{0}.yml'.format(self.plugin_name)
|
||||||
|
|
||||||
|
def inventory_contents(self, inventory_update, private_data_dir):
|
||||||
|
"""Returns a string that is the content for the inventory file for the inventory plugin"""
|
||||||
|
return yaml.safe_dump(self.inventory_as_dict(inventory_update, private_data_dir), default_flow_style=False, width=1000)
|
||||||
|
|
||||||
|
def inventory_as_dict(self, inventory_update, private_data_dir):
|
||||||
|
source_vars = dict(inventory_update.source_vars_dict) # make a copy
|
||||||
|
'''
|
||||||
|
None conveys that we should use the user-provided plugin.
|
||||||
|
Note that a plugin value of '' should still be overridden.
|
||||||
|
'''
|
||||||
|
if self.plugin_name is not None:
|
||||||
|
if hasattr(self, 'downstream_namespace') and server_product_name() != 'AWX':
|
||||||
|
source_vars['plugin'] = f'{self.downstream_namespace}.{self.downstream_collection}.{self.plugin_name}'
|
||||||
|
elif self.use_fqcn:
|
||||||
|
source_vars['plugin'] = f'{self.namespace}.{self.collection}.{self.plugin_name}'
|
||||||
|
else:
|
||||||
|
source_vars['plugin'] = self.plugin_name
|
||||||
|
return source_vars
|
||||||
|
|
||||||
|
def build_env(self, inventory_update, env, private_data_dir, private_data_files):
|
||||||
|
injector_env = self.get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
env.update(injector_env)
|
||||||
|
# All CLOUD_PROVIDERS sources implement as inventory plugin from collection
|
||||||
|
env['ANSIBLE_INVENTORY_ENABLED'] = 'auto'
|
||||||
|
return env
|
||||||
|
|
||||||
|
def _get_shared_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
"""By default, we will apply the standard managed injectors"""
|
||||||
|
injected_env = {}
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
# some sources may have no credential, specifically ec2
|
||||||
|
if credential is None:
|
||||||
|
return injected_env
|
||||||
|
if self.base_injector in ('managed', 'template'):
|
||||||
|
injected_env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk) # so injector knows this is inventory
|
||||||
|
if self.base_injector == 'managed':
|
||||||
|
from awx_plugins.credentials import injectors as builtin_injectors
|
||||||
|
|
||||||
|
cred_kind = inventory_update.source.replace('ec2', 'aws')
|
||||||
|
if cred_kind in dir(builtin_injectors):
|
||||||
|
getattr(builtin_injectors, cred_kind)(credential, injected_env, private_data_dir)
|
||||||
|
elif self.base_injector == 'template':
|
||||||
|
safe_env = injected_env.copy()
|
||||||
|
args = []
|
||||||
|
credential.credential_type.inject_credential(credential, injected_env, safe_env, args, private_data_dir)
|
||||||
|
# NOTE: safe_env is handled externally to injector class by build_safe_env static method
|
||||||
|
# that means that managed injectors must only inject detectable env keys
|
||||||
|
# enforcement of this is accomplished by tests
|
||||||
|
return injected_env
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
env = self._get_shared_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
return env
|
||||||
|
|
||||||
|
def build_private_data(self, inventory_update, private_data_dir):
|
||||||
|
return self.build_plugin_private_data(inventory_update, private_data_dir)
|
||||||
|
|
||||||
|
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class azure_rm(PluginFileInjector):
|
||||||
|
plugin_name = 'azure_rm'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'azure'
|
||||||
|
collection = 'azcollection'
|
||||||
|
|
||||||
|
def get_plugin_env(self, *args, **kwargs):
|
||||||
|
ret = super(azure_rm, self).get_plugin_env(*args, **kwargs)
|
||||||
|
# We need native jinja2 types so that tags can give JSON null value
|
||||||
|
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class ec2(PluginFileInjector):
|
||||||
|
plugin_name = 'aws_ec2'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'amazon'
|
||||||
|
collection = 'aws'
|
||||||
|
|
||||||
|
def get_plugin_env(self, *args, **kwargs):
|
||||||
|
ret = super(ec2, self).get_plugin_env(*args, **kwargs)
|
||||||
|
# We need native jinja2 types so that ec2_state_code will give integer
|
||||||
|
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class gce(PluginFileInjector):
|
||||||
|
plugin_name = 'gcp_compute'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'google'
|
||||||
|
collection = 'cloud'
|
||||||
|
|
||||||
|
def get_plugin_env(self, *args, **kwargs):
|
||||||
|
ret = super(gce, self).get_plugin_env(*args, **kwargs)
|
||||||
|
# We need native jinja2 types so that ip addresses can give JSON null value
|
||||||
|
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def inventory_as_dict(self, inventory_update, private_data_dir):
|
||||||
|
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
# InventorySource.source_vars take precedence over ENV vars
|
||||||
|
if 'projects' not in ret:
|
||||||
|
ret['projects'] = [credential.get_input('project', default='')]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class vmware(PluginFileInjector):
|
||||||
|
plugin_name = 'vmware_vm_inventory'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'community'
|
||||||
|
collection = 'vmware'
|
||||||
|
|
||||||
|
|
||||||
|
class openstack(PluginFileInjector):
|
||||||
|
plugin_name = 'openstack'
|
||||||
|
namespace = 'openstack'
|
||||||
|
collection = 'cloud'
|
||||||
|
|
||||||
|
def _get_clouds_dict(self, inventory_update, cred, private_data_dir):
|
||||||
|
openstack_data = _openstack_data(cred)
|
||||||
|
|
||||||
|
openstack_data['clouds']['devstack']['private'] = inventory_update.source_vars_dict.get('private', True)
|
||||||
|
ansible_variables = {
|
||||||
|
'use_hostnames': True,
|
||||||
|
'expand_hostvars': False,
|
||||||
|
'fail_on_errors': True,
|
||||||
|
}
|
||||||
|
provided_count = 0
|
||||||
|
for var_name in ansible_variables:
|
||||||
|
if var_name in inventory_update.source_vars_dict:
|
||||||
|
ansible_variables[var_name] = inventory_update.source_vars_dict[var_name]
|
||||||
|
provided_count += 1
|
||||||
|
if provided_count:
|
||||||
|
# Must we provide all 3 because the user provides any 1 of these??
|
||||||
|
# this probably results in some incorrect mangling of the defaults
|
||||||
|
openstack_data['ansible'] = ansible_variables
|
||||||
|
return openstack_data
|
||||||
|
|
||||||
|
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
private_data = {'credentials': {}}
|
||||||
|
|
||||||
|
openstack_data = self._get_clouds_dict(inventory_update, credential, private_data_dir)
|
||||||
|
private_data['credentials'][credential] = yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True)
|
||||||
|
return private_data
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
env = super(openstack, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
cred_data = private_data_files['credentials']
|
||||||
|
env['OS_CLIENT_CONFIG_FILE'] = to_container_path(cred_data[credential], private_data_dir)
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
class rhv(PluginFileInjector):
|
||||||
|
"""ovirt uses the custom credential templating, and that is all"""
|
||||||
|
|
||||||
|
plugin_name = 'ovirt'
|
||||||
|
base_injector = 'template'
|
||||||
|
initial_version = '2.9'
|
||||||
|
namespace = 'ovirt'
|
||||||
|
collection = 'ovirt'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'rhv'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
|
||||||
|
class satellite6(PluginFileInjector):
|
||||||
|
plugin_name = 'foreman'
|
||||||
|
namespace = 'theforeman'
|
||||||
|
collection = 'foreman'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'satellite'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
# this assumes that this is merged
|
||||||
|
# https://github.com/ansible/ansible/pull/52693
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
ret = super(satellite6, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
if credential:
|
||||||
|
ret['FOREMAN_SERVER'] = credential.get_input('host', default='')
|
||||||
|
ret['FOREMAN_USER'] = credential.get_input('username', default='')
|
||||||
|
ret['FOREMAN_PASSWORD'] = credential.get_input('password', default='')
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class terraform(PluginFileInjector):
|
||||||
|
plugin_name = 'terraform_state'
|
||||||
|
namespace = 'cloud'
|
||||||
|
collection = 'terraform'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
def inventory_as_dict(self, inventory_update, private_data_dir):
|
||||||
|
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
config_cred = credential.get_input('configuration')
|
||||||
|
if config_cred:
|
||||||
|
handle, path = tempfile.mkstemp(dir=os.path.join(private_data_dir, 'env'))
|
||||||
|
with os.fdopen(handle, 'w') as f:
|
||||||
|
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
||||||
|
f.write(config_cred)
|
||||||
|
ret['backend_config_files'] = to_container_path(path, private_data_dir)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
|
||||||
|
private_data = {'credentials': {}}
|
||||||
|
gce_cred = credential.get_input('gce_credentials', default=None)
|
||||||
|
if gce_cred:
|
||||||
|
private_data['credentials'][credential] = gce_cred
|
||||||
|
return private_data
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
env = super(terraform, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
cred_data = private_data_files['credentials']
|
||||||
|
if credential in cred_data:
|
||||||
|
env['GOOGLE_BACKEND_CREDENTIALS'] = to_container_path(cred_data[credential], private_data_dir)
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
class controller(PluginFileInjector):
|
||||||
|
plugin_name = 'tower' # TODO: relying on routing for now, update after EEs pick up revised collection
|
||||||
|
base_injector = 'template'
|
||||||
|
namespace = 'awx'
|
||||||
|
collection = 'awx'
|
||||||
|
downstream_namespace = 'ansible'
|
||||||
|
downstream_collection = 'controller'
|
||||||
|
|
||||||
|
|
||||||
|
class insights(PluginFileInjector):
|
||||||
|
plugin_name = 'insights'
|
||||||
|
base_injector = 'template'
|
||||||
|
namespace = 'redhatinsights'
|
||||||
|
collection = 'insights'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'insights'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
|
||||||
|
class openshift_virtualization(PluginFileInjector):
|
||||||
|
plugin_name = 'kubevirt'
|
||||||
|
base_injector = 'template'
|
||||||
|
namespace = 'kubevirt'
|
||||||
|
collection = 'core'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'openshift_virtualization'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
|
||||||
|
class constructed(PluginFileInjector):
|
||||||
|
plugin_name = 'constructed'
|
||||||
|
namespace = 'ansible'
|
||||||
|
collection = 'builtin'
|
||||||
|
|
||||||
|
def build_env(self, *args, **kwargs):
|
||||||
|
env = super().build_env(*args, **kwargs)
|
||||||
|
# Enable script inventory plugin so we pick up the script files from source inventories
|
||||||
|
env['ANSIBLE_INVENTORY_ENABLED'] += ',script'
|
||||||
|
env['ANSIBLE_INVENTORY_ANY_UNPARSED_IS_FAILED'] = 'True'
|
||||||
|
return env
|
||||||
|
|
||||||
Reference in New Issue
Block a user