Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable support for injecting complex extra vars #13267

Merged
merged 10 commits into from
Feb 2, 2023

Conversation

philipsd6
Copy link
Contributor

@philipsd6 philipsd6 commented Dec 1, 2022

SUMMARY

Until now, it's only been possible to inject flat strings with Jinja templating into extra vars from Custom Credential Types. However, it's quite desirable to be able to pass a data structure containing authentication information for certain collections/modules. For example, the Infoblox collection accepts authentication under the nios_provider key.

This PR enables that data structure to be created by an injector config.

related #1965 #13070

ISSUE TYPE
  • New or Enhanced Feature
COMPONENT NAME
  • Other
AWX VERSION
PYTHON=python3 make VERSION
awx: 21.9.1.dev32+g4af80777dd
ADDITIONAL INFORMATION

This PR also makes it possible to template the keys of an injector configuration, as requested in #13070.

For example, given an input configuration providing values for environment, host, apikey, username, it's possible to use an extra_vars injector configuration of:

"{{ environment }}_auth":
  host: "{{ host }}"
  apikey: "{{ apikey }}"
  username: "{{ username }}"
  verify_ssl: "{{ verify_ssl }}"

resulting in the following extra var available to the playbooks:

prod_auth:
  host: http://production.server
  apikey: 5hfthisj4hisj5hgnotj6hrealgj6
  username: philipsd6
  verify_ssl: True

This would lay the groundwork for allowing multiple credentials of the same credential type, as another credential might supply values for a different environment.

@philipsd6
Copy link
Contributor Author

I'm opening this PR to generate some discussion -- I'll update with tests and documentation if this passes muster.

@AlanCoding
Copy link
Member

Let's get your example documented in docs/credentials/custom_credential_types.md

@philipsd6
Copy link
Contributor Author

philipsd6 commented Dec 2, 2022

Let's get your example documented in docs/credentials/custom_credential_types.md

I'll document the major part of this PR, but not the ability to template the key (yet) as that needs some discussion.

@shanemcd
Copy link
Member

shanemcd commented Dec 3, 2022

This is awesome 😄 Before we merge this we'll need to get some tests added. Check out test_tasks.py - there's a lot of examples of tests calling inject_credential.

@philipsd6
Copy link
Contributor Author

philipsd6 commented Dec 6, 2022

Added two tests for nested and templated extra vars keys, but still TBD: Updating the jsonschema for the CredentialTypeInjectorField model (the tests will fail until this is done.)

@john-westcott-iv
Copy link
Member

@philipsd6 if you are all set with your changes can you convert this from a draft PR to an opened PR?

@philipsd6
Copy link
Contributor Author

@philipsd6 if you are all set with your changes can you convert this from a draft PR to an opened PR?

I don't think it's ready yet -- I'm struggling with the jsonschema that will allow recursively nested injector vars. Currently in my test env, I'm still getting this error when trying to create a nested extra_vars injector:

OrderedDict([('nested_auth', OrderedDict([('username', '{{ username }}'), ('password', '{{ password }}')]))]) is not valid under any of the given schemas

@philipsd6 philipsd6 force-pushed the feature/complex_extra_vars branch 4 times, most recently from d243279 to 34ea161 Compare December 14, 2022 21:40
@philipsd6
Copy link
Contributor Author

I updated awx/main/fields.py:CredentialTypeInjectorField to try to make the extra_vars property recursive, but while the schema is ok with an online validator: https://www.jsonschemavalidator.net/s/Yr7ExLBf it still fails in AWX when trying to update a credential with a nested extra_var injector with:

TypeError: Can't compile non template nodes
10.0.2.100 PATCH /api/v2/credential_types/28/ - HTTP/1.1 500

@philipsd6
Copy link
Contributor Author

I updated awx/main/fields.py:CredentialTypeInjectorField to try to make the extra_vars property recursive, but while the schema is ok with an online validator: https://www.jsonschemavalidator.net/s/Yr7ExLBf it still fails in AWX when trying to update a credential with a nested extra_var injector with:

TypeError: Can't compile non template nodes
10.0.2.100 PATCH /api/v2/credential_types/28/ - HTTP/1.1 500

My bad, the jsonschema update worked fine, it was the validator that was failing on the nested part. Fixed with latest commit.

@philipsd6 philipsd6 marked this pull request as ready for review December 15, 2022 15:13
@@ -790,13 +790,11 @@ def schema(self, model_instance):
'extra_vars': {
'type': 'object',
'patternProperties': {
# http://docs.ansible.com/ansible/playbooks_variables.html#what-makes-a-valid-variable-name
'^[a-zA-Z_]+[a-zA-Z0-9_]*$': {'type': 'string'},
r'^(?:(?:{(?:{|%)[^{}]*?(?:%|})})|(?:[a-zA-Z_]+[a-zA-Z0-9_]*)+)+$': {"anyOf": [{'type': 'string'}, {'$ref': '#/properties/extra_vars'}]}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regex is ridiculous, but the only alternative I can see is to just make it r'^.+$'

raise django_exceptions.ValidationError(_('Encountered unsafe code execution: {}').format(e))
except TemplateSyntaxError as e:
raise django_exceptions.ValidationError(
_('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})').format(sub_key=key, type=type_, error_msg=e),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting a server error due to the fact that key is undefined here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, valid -- and not only that, so is type_. I'll have to revise a little deeper after closer inspection. Thanks for the review.


def validate_extra_vars(key, node):
if isinstance(node, dict):
return {validate_extra_vars(key, k): validate_extra_vars(k, v) for k, v in node.items()}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really don't want to lose context in error messages. The key passed in should always be a string right? So what if you combined k and key for the value here, so instead of validate_extra_vars(k, v) do validate_extra_vars(f'{key}:{k}', v), or whatever you'd prefer for delimiter. That would keep stacking layers of context on a rolling basis.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a '.' (dot) as the dict key delimiter, seemed appropriate for this.

if isinstance(node, dict):
return {validate_extra_vars(key, k): validate_extra_vars(k, v) for k, v in node.items()}
elif isinstance(node, list):
return [validate_extra_vars(key, x) for x in node]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to change for x in node to for i, x in enumerate(node) and then stack onto key, like f'{key}:{i}. It doesn't distinguish between dicts and lists... but it shouldn't matter because the user know what injectors they submitted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the context is fully maintained now in the validation error messages. I used key[i][i] style for the validation error message, as between the dict dot style and this, it should be pseudo jq/jmespath style.

@AlanCoding: running podman exec -it awx-tools_awx_1 make test_unit works passes the new unit tests, But I'm not sure how to actually test those validation error paths.

@philipsd6 philipsd6 force-pushed the feature/complex_extra_vars branch 2 times, most recently from 891fe8d to 3c51d01 Compare January 9, 2023 21:21
params={'value': value},
)
if type_ == 'extra_vars':
validate_extra_vars(None, injector)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This None is coming through in the error message with a grammar that's not really desirable. I've actually been using the docs as an error case, because the existing example is badly formatted. That does need to be changed... but the value is:

"injectors": {
    "env": {
        "THIRD_PARTY_CLOUD_API_TOKEN": "{{api_token}}"
    },
    "extra_vars": {
        "some_extra_var": "{{username}}:{{password}",
        "auth": {
            "username": "{{username}}",
            "password": "{{password}}"
        }
    }
}

A bit here is missing a closing }. The currently produced error message is:

{'injectors': ["Syntax error rendering template for None.auth.username inside of extra_vars (unexpected '}')"]}

You know, in this line, injector must be a dictionary. This is fixable, but I think it will take an extra line or two of string formatting inside of validate_extra_vars.

I am going to write down a few validation test cases and save them for later.

@jay-steurer jay-steurer self-assigned this Jan 10, 2023
@AlanCoding
Copy link
Member

I poked at this some more and put up philipsd6#571, which is mainly concerned with error messages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants