Skip to content

Does not support patternProperties #172

Closed
@JohnPreston

Description

@JohnPreston

Hello.
Version: cloudformation-cli-python-lib==2.1.8; python_version >= "3.6"

JSON Schema

{
  "typeName": "MongoDb::Atlas::AwsIamDatabaseUser",
  "description": "CRUD for AWS IAM MongoDB users in a project for your clusters/databases.",
  "sourceUrl": "https://github.com/compose-x/mongodb-atlas-resources",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "AwsIamResource": {
      "description": "The AWS IAM user or role ARN used as the database username.",
      "type": "string",
      "pattern": "^arn:aws(?:-[a-z-]+)?:iam::[0-9]{12}:(role|user)/[\\S]+$"
    },
    "ApiKeys": {
      "$ref": "#/definitions/apiKeyDefinition"
    },
    "ProjectId": {
      "description": "Unique identifier of the Atlas project to which the user belongs.",
      "type": "string",
      "pattern": "^[a-zA-Z0-9]+$"
    },
    "DatabaseAccess": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^[a-zA-Z0-9-_]+$": {
          "$ref": "#/definitions/databaseAccessDefinition"
        }
      }
    },
    "Scopes": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^[a-zA-Z0-9-_]+$": {
          "type": "string",
          "description": "Database/Lake name to the type",
          "enum": [
            "CLUSTER",
            "DATA_LAKE"
          ]
        }
      }
    },
    "MongoDbUsername": {
      "description": "MongoDB username for the AWS IAM resource.",
      "type": "string"
    }
  },
  "definitions": {
    "databaseAccessDefinition": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "RoleName"
      ],
      "properties": {
        "RoleName": {
          "type": "string"
        },
        "CollectionName": {
          "type": "string"
        }
      }
    },
    "apiKeyDefinition": {
      "additionalProperties": false,
      "properties": {
        "PrivateKey": {
          "type": "string"
        },
        "PublicKey": {
          "type": "string"
        }
      },
      "type": "object",
      "required": [
        "PublicKey",
        "PrivateKey"
      ]
    },
    "roleDefinition": {
      "additionalProperties": false,
      "properties": {
        "CollectionName": {
          "type": "string"
        },
        "DatabaseName": {
          "type": "string"
        },
        "RoleName": {
          "minLength": 1,
          "type": "string"
        }
      },
      "type": "object",
      "required": [
        "RoleName"
      ]
    },
    "scopeDefinition": {
      "additionalProperties": false,
      "properties": {
        "ScopeName": {
          "minLength": 1,
          "type": "string"
        },
        "ScopeType": {
          "enum": [
            "CLUSTER",
            "DATA_LAKE"
          ],
          "type": "string"
        }
      },
      "type": "object",
      "required": [
        "ScopeName",
        "ScopeType"
      ]
    }
  },
  "primaryIdentifier": [
    "/properties/MongoDbUsername"
  ],
  "readOnlyProperties": [
    "/properties/MongoDbUsername"
  ],
  "createOnlyProperties": [
    "/properties/Username",
    "/properties/ProjectId"
  ],
  "required": [
    "AwsIamResource",
    "ProjectId",
    "ApiKeys",
    "DatabaseAccess"
  ],
  "handlers": {
    "create": {
      "permissions": [
        "secretsmanager:GetSecretValue"
      ]
    },
    "read": {
      "permissions": [
        "secretsmanager:GetSecretValue"
      ]
    },
    "update": {
      "permissions": [
        "secretsmanager:GetSecretValue"
      ]
    },
    "delete": {
      "permissions": [
        "secretsmanager:GetSecretValue"
      ]
    }
  }
}

stack trace

HELLO K DatabaseAccess <class 'str'>
HI V {'db0101': {'RoleName': 'readWrite'}, 'db2020': {'RoleName': 'readWriteAnyDatabase'}} <class 'dict'>
HELLO K db0101 <class 'str'>
HI V {'RoleName': 'readWrite'} <class 'dict'>
Handler error
Traceback (most recent call last):
  File "/var/task/cloudformation_cli_python_lib/resource.py", line 176, in _cast_resource_request
    ).to_modelled(self._model_cls, self._type_configuration_model_cls)
  File "/var/task/cloudformation_cli_python_lib/utils.py", line 148, in to_modelled
    desiredResourceState=model_cls._deserialize(self.desiredResourceState),
  File "/var/task/mongodb_atlas_awsiamdatabaseuser/models.py", line 63, in _deserialize
    recast_object(cls, json_data, dataclasses)
  File "/var/task/cloudformation_cli_python_lib/recast.py", line 24, in recast_object
    recast_object(child_cls, v, classes)
  File "/var/task/cloudformation_cli_python_lib/recast.py", line 23, in recast_object
    child_cls = _field_to_type(cls.__dataclass_fields__[k].type, k, classes)
KeyError: 'db0101'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/var/task/cloudformation_cli_python_lib/resource.py", line 199, in __call__
    request = self._cast_resource_request(event)
  File "/var/task/cloudformation_cli_python_lib/resource.py", line 179, in _cast_resource_request
    raise InvalidRequest(f"{e} ({type(e).__name__})") from e
cloudformation_cli_python_lib.exceptions.InvalidRequest: 'db0101' (KeyError)

So. what's happening is that the recast the first time around sees DatabaseAccess as the key to value dict

{'db0101': {'RoleName': 'readWrite'}, 'db2020': {'RoleName': 'readWriteAnyDatabase'}}

Then goes into the nesting. But then it has lost the info that the dict in db0101 represents another class.
That seems to happen due to the "lack" of typing for DatabaseAccess.{} objects

@dataclass
class ResourceModel(BaseModel):
    AwsIamResource: Optional[str]
    ApiKeys: Optional["_ApiKeyDefinition"]
    ProjectId: Optional[str]
    DatabaseAccess: Optional[MutableMapping[str, "_DatabaseAccessDefinition"]]
    Scopes: Optional[MutableMapping[str, str]]
    MongoDbUsername: Optional[str]

    @classmethod
    def _deserialize(
        cls: Type["_ResourceModel"],
        json_data: Optional[Mapping[str, Any]],
    ) -> Optional["_ResourceModel"]:
        if not json_data:
            return None
        dataclasses = {n: o for n, o in getmembers(sys.modules[__name__]) if isclass(o)}
        print(dataclasses.items())
        print(json_data)
        print(cls, type(cls))
        for k, v in cls.__dict__.items():
            print(k, v)
        recast_object(cls, json_data, dataclasses)
        return cls(
            AwsIamResource=json_data.get("AwsIamResource"),
            ApiKeys=ApiKeyDefinition._deserialize(json_data.get("ApiKeys")),
            ProjectId=json_data.get("ProjectId"),
            DatabaseAccess=json_data.get("DatabaseAccess"),
            Scopes=json_data.get("Scopes"),
            MongoDbUsername=json_data.get("MongoDbUsername"),
        )

The input is totally valid (jsonschema approves).
I am trying to come up with a fix but thought that's worth others either telling me what I am doing wrong or knowing about it

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions