Repository: ansible/awx
File: awx/api/serializers.py (Python)

Code before:
        return ret

    def get_related(self, obj):
        res = super(CredentialTypeSerializer, self).get_related(obj)
        res['credentials'] = self.reverse(
            'api:credential_type_credential_list',
            kwargs={'pk': obj.pk}
        )
        res['activity_stream'] = self.reverse(
            'api:credential_type_activity_stream_list',
            kwargs={'pk': obj.pk}
        )
        return res

    def to_representation(self, data):
        value = super(CredentialTypeSerializer, self).to_representation(data)

        # translate labels and help_text for credential fields "managed by Tower"
        if value.get('managed_by_tower'):
            value['name'] = _(value['name'])
            for field in value.get('inputs', {}).get('fields', []):
                field['label'] = _(field['label'])
                if 'help_text' in field:
                    field['help_text'] = _(field['help_text'])
                if field['type'] == 'become_method':
                    field['type'] = 'string'
        return value

    def filter_field_metadata(self, fields, method):
        # API-created/modified CredentialType kinds are limited to
        # `cloud` and `net`
        if method in ('PUT', 'POST'):
            fields['kind']['choices'] = list(filter(
                lambda choice: choice[0] in ('cloud', 'net'),
                fields['kind']['choices']
            ))
        return fields


# TODO: remove when API v1 is removed
class V1CredentialFields(BaseSerializer, metaclass=BaseSerializerMetaclass):

    class Meta:
        model = Credential
        fields = ('*', 'kind', 'cloud', 'host', 'username',
                  'password', 'security_token', 'project', 'domain',
                  'ssh_key_data', 'ssh_key_unlock', 'become_method',
                  'become_username', 'become_password', 'vault_password',
                  'subscription', 'tenant', 'secret', 'client', 'authorize',
                  'authorize_password')


Review comment (security):
So.  `<pulls up chair>`

This logic is here as part of a hack that @wwitzel3 and I are responsible for, and we can probably lose a lot of this complexity now.

When a `CredentialType` is defined, it has a "schema" we store in the database, and that schema includes a list of fields it takes (username, password, become_method, etc...):

https://github.com/ansible/awx/blob/devel/awx/main/models/credential/__init__.py#L707

These field definitions have names, types (string/boolean), help text, etc...  Part of that definition for some fields - like `become_method` - is a list of _choices_.  This means that as time went on, every time Ansible supported a _new_ value to the list of choices, we'd have to write code that would _update_ this schema in the database so that it reflected the _new_ list of possible options (`sudo`, `su`, etc...).

Gross.

So @wwitzel3 and I wrote some code that made a _new_ **special** type of field, which was `type='become_method'`:

https://github.com/ansible/awx/blob/devel/awx/main/models/credential/__init__.py#L739

A field with _that_ specific special type uses the custom logic you see *here* such that it would populate the choices/options from the `CHOICES_PRIVILEGE_ESCALATION_METHODS` constants defined _in code_.  This meant that if a new `become_method` was added in Ansible, we would just add to `CHOICES_PRIVILEGE_ESCALATION_METHODS` and wouldn't have to modify some JSON-serialized schema defined in the database.

Now that there _are_ no choices, we don't have to do _any_ of this.  We can likely lose any code that has special `field['type'] == 'become_method'` logic (like here and https://github.com/ansible/awx/pull/3093/files#diff-ea6da88b8c0cd3fbf7858ae87933b296L728 and https://github.com/ansible/awx/pull/3093/files#diff-ea6da88b8c0cd3fbf7858ae87933b296L729) and _just_ update the actual schema definition:

https://github.com/ansible/awx/blob/devel/awx/main/models/credential/__init__.py#L739

...to be `type: string` instead of `type: become_method`.

Code after:
        return ret

    def get_related(self, obj):
        res = super(CredentialTypeSerializer, self).get_related(obj)
        res['credentials'] = self.reverse(
            'api:credential_type_credential_list',
            kwargs={'pk': obj.pk}
        )
        res['activity_stream'] = self.reverse(
            'api:credential_type_activity_stream_list',
            kwargs={'pk': obj.pk}
        )
        return res

    def to_representation(self, data):
        value = super(CredentialTypeSerializer, self).to_representation(data)

        # translate labels and help_text for credential fields "managed by Tower"
        if value.get('managed_by_tower'):
            value['name'] = _(value['name'])
            for field in value.get('inputs', {}).get('fields', []):
                field['label'] = _(field['label'])
                if 'help_text' in field:
                    field['help_text'] = _(field['help_text'])
        return value

    def filter_field_metadata(self, fields, method):
        # API-created/modified CredentialType kinds are limited to
        # `cloud` and `net`
        if method in ('PUT', 'POST'):
            fields['kind']['choices'] = list(filter(
                lambda choice: choice[0] in ('cloud', 'net'),
                fields['kind']['choices']
            ))
        return fields


# TODO: remove when API v1 is removed
class V1CredentialFields(BaseSerializer, metaclass=BaseSerializerMetaclass):

    class Meta:
        model = Credential
        fields = ('*', 'kind', 'cloud', 'host', 'username',
                  'password', 'security_token', 'project', 'domain',
                  'ssh_key_data', 'ssh_key_unlock', 'become_method',
                  'become_username', 'become_password', 'vault_password',
                  'subscription', 'tenant', 'secret', 'client', 'authorize',
                  'authorize_password')

    def build_field(self, field_name, info, model_class, nested_depth):
        if field_name in V1Credential.FIELDS: