We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Set Athena Workgroup Encryption also tries to change readonly field
### Describe the bug

Hi, we are currently trying to set the encryption for our primary athena workgroups that are unencrypted. The policy looks like this:

```yaml
- name: set-athena-workgroup-encryption
  resource: awscc.athena_workgroup
  filters:
    - type: value
      key: Name
      value: "primary"
    - type: value
      key:  "WorkGroupConfiguration.ResultConfiguration.EncryptionConfiguration.EncryptionOption"
      value: absent
  actions:
    - type: update
      WorkGroupConfiguration:
        EnforceWorkGroupConfiguration: true
    - type: update
      WorkGroupConfiguration:
        ResultConfiguration:
          EncryptionConfiguration:
            EncryptionOption: SSE_S3
```

When executing this policy we get this error though: 

```
2024-02-15 09:59:25,626: custodian.commands:ERROR Error while executing policy set-athena-workgroup-encryption, continuing
Traceback (most recent call last):
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n/commands.py", line 307, in run
    policy()
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py", line 1357, in __call__
    resources = mode.run()
                ^^^^^^^^^^
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py", line 364, in run
    results = a.process(resources)
              ^^^^^^^^^^^^^^^^^^^^
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n_awscc/actions.py", line 43, in process
    client.update_resource(
  File "/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py", line 553, in _api_call
    return self._make_api_call(operation_name, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py", line 1009, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateResource operation: Invalid patch update: readOnlyProperties [/properties/WorkGroupConfiguration/EngineVersion/EffectiveEngineVersion] cannot be updated
2024-02-15 09:59:25,627: custodian.commands:ERROR The following policies had errors while executing
 - set-athena-workgroup-encryption
```

But we don't want to update the engine version itself.

### What did you expect to happen?

We expected the policy to update the encryption setting and not touch the engine version, because the attribute was not specified in our policy

### Cloud Provider

Amazon Web Services (AWS)

### Cloud Custodian version and dependency information

```shell
Custodian:   0.9.34
Python:      3.11.4 (main, Dec  7 2023, 15:43:41) [GCC 12.3.0]
Platform:    posix.uname_result(sysname='Linux', nodename='marcel', release='6.2.0-39-generic', version='#40-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 14 14:18:00 UTC 2023', machine='x86_64')
Using venv:  True
Docker: False
Installed:

argcomplete==3.2.1
attrs==23.2.0
boto3==1.34.21
botocore==1.34.21
docutils==0.18.1
importlib-metadata==6.11.0
jmespath==1.0.1
jsonschema==4.21.0
jsonschema-specifications==2023.12.1
python-dateutil==2.8.2
pyyaml==6.0.1
referencing==0.31.1
rpds-py==0.17.1
s3transfer==0.10.0
six==1.16.0
tabulate==0.9.0
urllib3==1.26.18
zipp==3.17.0
```


### Policy

```shell
- name: set-athena-workgroup-encryption
  resource: awscc.athena_workgroup
  filters:
    - type: value
      key: Name
      value: "primary"
    - type: value
      key:  "WorkGroupConfiguration.ResultConfiguration.EncryptionConfiguration.EncryptionOption"
      value: absent
  actions:
    - type: update
      WorkGroupConfiguration:
        EnforceWorkGroupConfiguration: true
    - type: update
      WorkGroupConfiguration:
        ResultConfiguration:
          EncryptionConfiguration:
            EncryptionOption: SSE_S3
```


### Relevant log/traceback output

```shell
2024-02-15 09:59:25,626: custodian.commands:ERROR Error while executing policy set-athena-workgroup-encryption, continuing
Traceback (most recent call last):
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n/commands.py", line 307, in run
    policy()
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py", line 1357, in __call__
    resources = mode.run()
                ^^^^^^^^^^
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py", line 364, in run
    results = a.process(resources)
              ^^^^^^^^^^^^^^^^^^^^
  File "/home/.../policies/.venv/lib/python3.11/site-packages/c7n_awscc/actions.py", line 43, in process
    client.update_resource(
  File "/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py", line 553, in _api_call
    return self._make_api_call(operation_name, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py", line 1009, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateResource operation: Invalid patch update: readOnlyProperties [/properties/WorkGroupConfiguration/EngineVersion/EffectiveEngineVersion] cannot be updated
2024-02-15 09:59:25,627: custodian.commands:ERROR The following policies had errors while executing
 - set-athena-workgroup-encryption
```


### Extra information or context

We tried to use the update attributes like this 

```yaml
    - type: update
      WorkGroupConfigurationUpdates:
        EnforceWorkGroupConfiguration: true
        ResultConfigurationUpdates:
          EncryptionConfiguration:
            EncryptionOption: SSE_S3
```

but there is currently a bug in AWS which resets the workgroup right after the operation. We are in communication with AWS Support there, but in the meantime we tried to make it work with the approach described above.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `tools/c7n_awscc/c7n_awscc/actions.py`
Content:
```
1 # Copyright The Cloud Custodian Authors.
2 # SPDX-License-Identifier: Apache-2.0
3 import json
4 
5 import jsonpatch
6 
7 from c7n.actions import Action
8 from c7n.utils import local_session, type_schema
9 
10 
11 class ControlAction(Action):
12     def get_identity(self, r):
13         id_fields = self.manager.schema["primaryIdentifier"]
14         idv = {}
15         for idf in id_fields:
16             idn = idf.rsplit("/", 1)[-1]
17             idv[idn] = r[idn]
18         if len(idv) == 1:
19             return idv[idn]
20         return json.dumps(idv)
21 
22 
23 class Delete(ControlAction):
24     schema = type_schema("delete")
25 
26     def process(self, resources):
27         client = local_session(self.manager.session_factory).client("cloudcontrol")
28         for r in resources:
29             client.delete_resource(
30                 TypeName=self.manager.resource_type.cfn_type,
31                 Identifier=self.get_identity(r),
32             )
33 
34 
35 class Update(ControlAction):
36     # schema is setup at resource type initialization
37 
38     def process(self, resources):
39         client = local_session(self.manager.session_factory).client("cloudcontrol")
40         for r in resources:
41             patch = self.get_patch(r)
42             client.update_resource(
43                 TypeName=self.manager.resource_type.cfn_type,
44                 Identifier=self.get_identity(r),
45                 PatchDocument=patch.to_string(),
46             )
47 
48     def get_patch(self, r):
49         tgt = dict(r)
50         for k, v in self.data.items():
51             if k == "type":
52                 continue
53             tgt[k] = v
54         return jsonpatch.make_patch(r, tgt)
55
```

Path: `tools/c7n_awscc/c7n_awscc/manager.py`
Content:
```
1 # Copyright The Cloud Custodian Authors.
2 # SPDX-License-Identifier: Apache-2.0
3 import json
4 from pathlib import Path
5 
6 from c7n.filters import Filter  # noqa
7 
8 from .actions import Delete, Update
9 from .query import CloudControl
10 from .provider import resources
11 
12 from c7n.query import TypeInfo, QueryResourceManager
13 
14 
15 _IndexData = None
16 
17 
18 def get_index():
19     global _IndexData
20 
21     if _IndexData is not None:
22         return _IndexData
23 
24     index_path = Path(__file__).parent / "data" / "index.json"
25     _IndexData = json.loads(index_path.read_text(encoding="utf8"))
26     return _IndexData
27 
28 
29 def initialize_resource(resource_name):
30     """Load a resource class from its name"""
31     rpath = Path(__file__).parent / "data" / f"aws_{resource_name}.json"
32     if not rpath.exists():
33         return None
34     rinfo = json.loads(rpath.read_text(encoding="utf8"))
35 
36     type_info = type(
37         "resource_type",
38         (TypeInfo,),
39         dict(
40             id=rinfo["primaryIdentifier"][0].split("/", 1)[-1],
41             service=rinfo["typeName"].split("::")[1].lower(),
42             cfn_type=rinfo["typeName"],
43         ),
44     )
45 
46     rname = "_".join([s.lower() for s in rinfo["typeName"].split("::")[1:]])
47     class_name = "".join([s.lower().capitalize() for s in rinfo["typeName"].split("::")[1:]])
48     mod_name = f"c7n_awscc.resources.{resource_name}"
49 
50     permissions = rinfo.get("handlers", {}).get("read", {}).get("permissions", []) + rinfo.get(
51         "handlers", {}
52     ).get("list", {}).get("permissions", [])
53 
54     rtype = type(
55         class_name,
56         (QueryResourceManager,),
57         dict(
58             __module__=mod_name,
59             source_mapping={"describe": CloudControl},
60             resource_type=type_info,
61             permissions=permissions,
62             schema=rinfo,
63         ),
64     )
65 
66     rtype.action_registry.register(
67         "delete",
68         type(
69             class_name + "Delete",
70             (Delete,),
71             {
72                 "permissions": rinfo["handlers"]["delete"]["permissions"],
73                 "__module__": mod_name,
74             },
75         ),
76     )
77 
78     if "update" in rinfo["handlers"]:
79         rtype.action_registry.register(
80             "update",
81             type(
82                 class_name + "Update",
83                 (Update,),
84                 {
85                     "schema": get_update_schema(rtype.schema, rname),
86                     "permissions": rinfo["handlers"]["update"]["permissions"],
87                     "__module__": mod_name,
88                 },
89             ),
90         )
91 
92     process_supplementary_data(rtype)
93     resources.register(rname, rtype)
94 
95     return {rtype.__name__: rtype}
96 
97 
98 def process_supplementary_data(rtype):
99     idx = get_index()
100     augment = idx["augment"][rtype.resource_type.cfn_type]
101     rtype.resource_type.service = augment.get("service") or ""
102 
103 
104 def get_update_schema(schema, rname):
105     prop_names = set(schema["properties"])
106     create_only = {s.rsplit("/", 1)[-1] for s in schema.get("createOnlyProperties", ())}
107     read_only = {s.rsplit("/", 1)[-1] for s in schema.get("readOnlyProperties", ())}
108 
109     updatable = prop_names - (create_only | read_only)
110     update_schema = {
111         "additionalProperties": False,
112         "properties": {u: schema["properties"][u] for u in updatable},
113     }
114     update_schema["properties"]["type"] = {"enum": ["update"]}
115 
116     if "definitions" in schema:
117         update_schema["definitions"] = dict(schema["definitions"])
118         update_refs(update_schema, rname)
119 
120     return update_schema
121 
122 
123 def update_refs(schema_node, rname):
124     for k, v in schema_node.items():
125         if k == "$ref" and v.startswith("#/definitions/"):
126             # mutating while iterating but there's only ref value ever
127             schema_node[k] = "#/definitions/resources/awscc.%s/actions/update/%s" % (
128                 rname,
129                 v[2:],
130             )
131         elif isinstance(v, dict):
132             update_refs(v, rname)
133
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/tools/c7n_awscc/c7n_awscc/actions.py b/tools/c7n_awscc/c7n_awscc/actions.py
--- a/tools/c7n_awscc/c7n_awscc/actions.py
+++ b/tools/c7n_awscc/c7n_awscc/actions.py
@@ -33,8 +33,16 @@
 
 
 class Update(ControlAction):
+    """Update a resource.
+
+    Supports either whole key/value replacement via an attribute mapping
+    or jsonpatch `patch:`
+    """
+
     # schema is setup at resource type initialization
 
+    _action_meta = ("type", "patch")
+
     def process(self, resources):
         client = local_session(self.manager.session_factory).client("cloudcontrol")
         for r in resources:
@@ -46,9 +54,23 @@
             )
 
     def get_patch(self, r):
-        tgt = dict(r)
+        # we support either using json patch to do a partial modification.
+        if self.data.get("patch"):
+            return jsonpatch.JsonPatch(self.data["patch"])
+
+        current = dict(r)
+
+        # the action's schema reflects updatable properties
+        updatable = {k for k in self.schema["properties"] if k not in self._action_meta}
+        for k in list(set(current) - updatable):
+            del current[k]
+
+        # shallow copy for patch generation
+        tgt = dict(current)
+
+        # or whole key value replacement.
         for k, v in self.data.items():
             if k == "type":
                 continue
             tgt[k] = v
-        return jsonpatch.make_patch(r, tgt)
+        return jsonpatch.make_patch(current, tgt)
diff --git a/tools/c7n_awscc/c7n_awscc/manager.py b/tools/c7n_awscc/c7n_awscc/manager.py
--- a/tools/c7n_awscc/c7n_awscc/manager.py
+++ b/tools/c7n_awscc/c7n_awscc/manager.py
@@ -112,6 +112,19 @@
         "properties": {u: schema["properties"][u] for u in updatable},
     }
     update_schema["properties"]["type"] = {"enum": ["update"]}
+    update_schema["properties"]["patch"] = {
+        # This schema is pretty minimal
+        "description": "Json patch to apply to resources",
+        "type": "array",
+        "items": {
+            "type": "object",
+            "required": ["op", "path"],
+            "properties": {
+                "path": {"type": "string"},
+                "op": {"enum": ["add", "remove", "update", "replace", "move", "copy", "test"]},
+            },
+        },
+    }
 
     if "definitions" in schema:
         update_schema["definitions"] = dict(schema["definitions"])

{"golden_diff": "diff --git a/tools/c7n_awscc/c7n_awscc/actions.py b/tools/c7n_awscc/c7n_awscc/actions.py\n--- a/tools/c7n_awscc/c7n_awscc/actions.py\n+++ b/tools/c7n_awscc/c7n_awscc/actions.py\n@@ -33,8 +33,16 @@\n \n \n class Update(ControlAction):\n+    \"\"\"Update a resource.\n+\n+    Supports either whole key/value replacement via an attribute mapping\n+    or jsonpatch `patch:`\n+    \"\"\"\n+\n     # schema is setup at resource type initialization\n \n+    _action_meta = (\"type\", \"patch\")\n+\n     def process(self, resources):\n         client = local_session(self.manager.session_factory).client(\"cloudcontrol\")\n         for r in resources:\n@@ -46,9 +54,23 @@\n             )\n \n     def get_patch(self, r):\n-        tgt = dict(r)\n+        # we support either using json patch to do a partial modification.\n+        if self.data.get(\"patch\"):\n+            return jsonpatch.JsonPatch(self.data[\"patch\"])\n+\n+        current = dict(r)\n+\n+        # the action's schema reflects updatable properties\n+        updatable = {k for k in self.schema[\"properties\"] if k not in self._action_meta}\n+        for k in list(set(current) - updatable):\n+            del current[k]\n+\n+        # shallow copy for patch generation\n+        tgt = dict(current)\n+\n+        # or whole key value replacement.\n         for k, v in self.data.items():\n             if k == \"type\":\n                 continue\n             tgt[k] = v\n-        return jsonpatch.make_patch(r, tgt)\n+        return jsonpatch.make_patch(current, tgt)\ndiff --git a/tools/c7n_awscc/c7n_awscc/manager.py b/tools/c7n_awscc/c7n_awscc/manager.py\n--- a/tools/c7n_awscc/c7n_awscc/manager.py\n+++ b/tools/c7n_awscc/c7n_awscc/manager.py\n@@ -112,6 +112,19 @@\n         \"properties\": {u: schema[\"properties\"][u] for u in updatable},\n     }\n     update_schema[\"properties\"][\"type\"] = {\"enum\": [\"update\"]}\n+    update_schema[\"properties\"][\"patch\"] = {\n+        # This schema is pretty minimal\n+        \"description\": \"Json patch to apply to resources\",\n+        \"type\": \"array\",\n+        \"items\": {\n+            \"type\": \"object\",\n+            \"required\": [\"op\", \"path\"],\n+            \"properties\": {\n+                \"path\": {\"type\": \"string\"},\n+                \"op\": {\"enum\": [\"add\", \"remove\", \"update\", \"replace\", \"move\", \"copy\", \"test\"]},\n+            },\n+        },\n+    }\n \n     if \"definitions\" in schema:\n         update_schema[\"definitions\"] = dict(schema[\"definitions\"])\n", "issue": "Set Athena Workgroup Encryption also tries to change readonly field\n### Describe the bug\n\nHi, we are currently trying to set the encryption for our primary athena workgroups that are unencrypted. The policy looks like this:\r\n\r\n```yaml\r\n- name: set-athena-workgroup-encryption\r\n  resource: awscc.athena_workgroup\r\n  filters:\r\n    - type: value\r\n      key: Name\r\n      value: \"primary\"\r\n    - type: value\r\n      key:  \"WorkGroupConfiguration.ResultConfiguration.EncryptionConfiguration.EncryptionOption\"\r\n      value: absent\r\n  actions:\r\n    - type: update\r\n      WorkGroupConfiguration:\r\n        EnforceWorkGroupConfiguration: true\r\n    - type: update\r\n      WorkGroupConfiguration:\r\n        ResultConfiguration:\r\n          EncryptionConfiguration:\r\n            EncryptionOption: SSE_S3\r\n```\r\n\r\nWhen executing this policy we get this error though: \r\n\r\n```\r\n2024-02-15 09:59:25,626: custodian.commands:ERROR Error while executing policy set-athena-workgroup-encryption, continuing\r\nTraceback (most recent call last):\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n/commands.py\", line 307, in run\r\n    policy()\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py\", line 1357, in __call__\r\n    resources = mode.run()\r\n                ^^^^^^^^^^\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py\", line 364, in run\r\n    results = a.process(resources)\r\n              ^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n_awscc/actions.py\", line 43, in process\r\n    client.update_resource(\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py\", line 553, in _api_call\r\n    return self._make_api_call(operation_name, kwargs)\r\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py\", line 1009, in _make_api_call\r\n    raise error_class(parsed_response, operation_name)\r\nbotocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateResource operation: Invalid patch update: readOnlyProperties [/properties/WorkGroupConfiguration/EngineVersion/EffectiveEngineVersion] cannot be updated\r\n2024-02-15 09:59:25,627: custodian.commands:ERROR The following policies had errors while executing\r\n - set-athena-workgroup-encryption\r\n```\r\n\r\nBut we don't want to update the engine version itself.\n\n### What did you expect to happen?\n\nWe expected the policy to update the encryption setting and not touch the engine version, because the attribute was not specified in our policy\n\n### Cloud Provider\n\nAmazon Web Services (AWS)\n\n### Cloud Custodian version and dependency information\n\n```shell\nCustodian:   0.9.34\r\nPython:      3.11.4 (main, Dec  7 2023, 15:43:41) [GCC 12.3.0]\r\nPlatform:    posix.uname_result(sysname='Linux', nodename='marcel', release='6.2.0-39-generic', version='#40-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 14 14:18:00 UTC 2023', machine='x86_64')\r\nUsing venv:  True\r\nDocker: False\r\nInstalled:\r\n\r\nargcomplete==3.2.1\r\nattrs==23.2.0\r\nboto3==1.34.21\r\nbotocore==1.34.21\r\ndocutils==0.18.1\r\nimportlib-metadata==6.11.0\r\njmespath==1.0.1\r\njsonschema==4.21.0\r\njsonschema-specifications==2023.12.1\r\npython-dateutil==2.8.2\r\npyyaml==6.0.1\r\nreferencing==0.31.1\r\nrpds-py==0.17.1\r\ns3transfer==0.10.0\r\nsix==1.16.0\r\ntabulate==0.9.0\r\nurllib3==1.26.18\r\nzipp==3.17.0\n```\n\n\n### Policy\n\n```shell\n- name: set-athena-workgroup-encryption\r\n  resource: awscc.athena_workgroup\r\n  filters:\r\n    - type: value\r\n      key: Name\r\n      value: \"primary\"\r\n    - type: value\r\n      key:  \"WorkGroupConfiguration.ResultConfiguration.EncryptionConfiguration.EncryptionOption\"\r\n      value: absent\r\n  actions:\r\n    - type: update\r\n      WorkGroupConfiguration:\r\n        EnforceWorkGroupConfiguration: true\r\n    - type: update\r\n      WorkGroupConfiguration:\r\n        ResultConfiguration:\r\n          EncryptionConfiguration:\r\n            EncryptionOption: SSE_S3\n```\n\n\n### Relevant log/traceback output\n\n```shell\n2024-02-15 09:59:25,626: custodian.commands:ERROR Error while executing policy set-athena-workgroup-encryption, continuing\r\nTraceback (most recent call last):\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n/commands.py\", line 307, in run\r\n    policy()\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py\", line 1357, in __call__\r\n    resources = mode.run()\r\n                ^^^^^^^^^^\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n/policy.py\", line 364, in run\r\n    results = a.process(resources)\r\n              ^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/c7n_awscc/actions.py\", line 43, in process\r\n    client.update_resource(\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py\", line 553, in _api_call\r\n    return self._make_api_call(operation_name, kwargs)\r\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/.../policies/.venv/lib/python3.11/site-packages/botocore/client.py\", line 1009, in _make_api_call\r\n    raise error_class(parsed_response, operation_name)\r\nbotocore.exceptions.ClientError: An error occurred (ValidationException) when calling the UpdateResource operation: Invalid patch update: readOnlyProperties [/properties/WorkGroupConfiguration/EngineVersion/EffectiveEngineVersion] cannot be updated\r\n2024-02-15 09:59:25,627: custodian.commands:ERROR The following policies had errors while executing\r\n - set-athena-workgroup-encryption\n```\n\n\n### Extra information or context\n\nWe tried to use the update attributes like this \r\n\r\n```yaml\r\n    - type: update\r\n      WorkGroupConfigurationUpdates:\r\n        EnforceWorkGroupConfiguration: true\r\n        ResultConfigurationUpdates:\r\n          EncryptionConfiguration:\r\n            EncryptionOption: SSE_S3\r\n```\r\n\r\nbut there is currently a bug in AWS which resets the workgroup right after the operation. We are in communication with AWS Support there, but in the meantime we tried to make it work with the approach described above.\n", "before_files": [{"content": "# Copyright The Cloud Custodian Authors.\n# SPDX-License-Identifier: Apache-2.0\nimport json\n\nimport jsonpatch\n\nfrom c7n.actions import Action\nfrom c7n.utils import local_session, type_schema\n\n\nclass ControlAction(Action):\n    def get_identity(self, r):\n        id_fields = self.manager.schema[\"primaryIdentifier\"]\n        idv = {}\n        for idf in id_fields:\n            idn = idf.rsplit(\"/\", 1)[-1]\n            idv[idn] = r[idn]\n        if len(idv) == 1:\n            return idv[idn]\n        return json.dumps(idv)\n\n\nclass Delete(ControlAction):\n    schema = type_schema(\"delete\")\n\n    def process(self, resources):\n        client = local_session(self.manager.session_factory).client(\"cloudcontrol\")\n        for r in resources:\n            client.delete_resource(\n                TypeName=self.manager.resource_type.cfn_type,\n                Identifier=self.get_identity(r),\n            )\n\n\nclass Update(ControlAction):\n    # schema is setup at resource type initialization\n\n    def process(self, resources):\n        client = local_session(self.manager.session_factory).client(\"cloudcontrol\")\n        for r in resources:\n            patch = self.get_patch(r)\n            client.update_resource(\n                TypeName=self.manager.resource_type.cfn_type,\n                Identifier=self.get_identity(r),\n                PatchDocument=patch.to_string(),\n            )\n\n    def get_patch(self, r):\n        tgt = dict(r)\n        for k, v in self.data.items():\n            if k == \"type\":\n                continue\n            tgt[k] = v\n        return jsonpatch.make_patch(r, tgt)\n", "path": "tools/c7n_awscc/c7n_awscc/actions.py"}, {"content": "# Copyright The Cloud Custodian Authors.\n# SPDX-License-Identifier: Apache-2.0\nimport json\nfrom pathlib import Path\n\nfrom c7n.filters import Filter  # noqa\n\nfrom .actions import Delete, Update\nfrom .query import CloudControl\nfrom .provider import resources\n\nfrom c7n.query import TypeInfo, QueryResourceManager\n\n\n_IndexData = None\n\n\ndef get_index():\n    global _IndexData\n\n    if _IndexData is not None:\n        return _IndexData\n\n    index_path = Path(__file__).parent / \"data\" / \"index.json\"\n    _IndexData = json.loads(index_path.read_text(encoding=\"utf8\"))\n    return _IndexData\n\n\ndef initialize_resource(resource_name):\n    \"\"\"Load a resource class from its name\"\"\"\n    rpath = Path(__file__).parent / \"data\" / f\"aws_{resource_name}.json\"\n    if not rpath.exists():\n        return None\n    rinfo = json.loads(rpath.read_text(encoding=\"utf8\"))\n\n    type_info = type(\n        \"resource_type\",\n        (TypeInfo,),\n        dict(\n            id=rinfo[\"primaryIdentifier\"][0].split(\"/\", 1)[-1],\n            service=rinfo[\"typeName\"].split(\"::\")[1].lower(),\n            cfn_type=rinfo[\"typeName\"],\n        ),\n    )\n\n    rname = \"_\".join([s.lower() for s in rinfo[\"typeName\"].split(\"::\")[1:]])\n    class_name = \"\".join([s.lower().capitalize() for s in rinfo[\"typeName\"].split(\"::\")[1:]])\n    mod_name = f\"c7n_awscc.resources.{resource_name}\"\n\n    permissions = rinfo.get(\"handlers\", {}).get(\"read\", {}).get(\"permissions\", []) + rinfo.get(\n        \"handlers\", {}\n    ).get(\"list\", {}).get(\"permissions\", [])\n\n    rtype = type(\n        class_name,\n        (QueryResourceManager,),\n        dict(\n            __module__=mod_name,\n            source_mapping={\"describe\": CloudControl},\n            resource_type=type_info,\n            permissions=permissions,\n            schema=rinfo,\n        ),\n    )\n\n    rtype.action_registry.register(\n        \"delete\",\n        type(\n            class_name + \"Delete\",\n            (Delete,),\n            {\n                \"permissions\": rinfo[\"handlers\"][\"delete\"][\"permissions\"],\n                \"__module__\": mod_name,\n            },\n        ),\n    )\n\n    if \"update\" in rinfo[\"handlers\"]:\n        rtype.action_registry.register(\n            \"update\",\n            type(\n                class_name + \"Update\",\n                (Update,),\n                {\n                    \"schema\": get_update_schema(rtype.schema, rname),\n                    \"permissions\": rinfo[\"handlers\"][\"update\"][\"permissions\"],\n                    \"__module__\": mod_name,\n                },\n            ),\n        )\n\n    process_supplementary_data(rtype)\n    resources.register(rname, rtype)\n\n    return {rtype.__name__: rtype}\n\n\ndef process_supplementary_data(rtype):\n    idx = get_index()\n    augment = idx[\"augment\"][rtype.resource_type.cfn_type]\n    rtype.resource_type.service = augment.get(\"service\") or \"\"\n\n\ndef get_update_schema(schema, rname):\n    prop_names = set(schema[\"properties\"])\n    create_only = {s.rsplit(\"/\", 1)[-1] for s in schema.get(\"createOnlyProperties\", ())}\n    read_only = {s.rsplit(\"/\", 1)[-1] for s in schema.get(\"readOnlyProperties\", ())}\n\n    updatable = prop_names - (create_only | read_only)\n    update_schema = {\n        \"additionalProperties\": False,\n        \"properties\": {u: schema[\"properties\"][u] for u in updatable},\n    }\n    update_schema[\"properties\"][\"type\"] = {\"enum\": [\"update\"]}\n\n    if \"definitions\" in schema:\n        update_schema[\"definitions\"] = dict(schema[\"definitions\"])\n        update_refs(update_schema, rname)\n\n    return update_schema\n\n\ndef update_refs(schema_node, rname):\n    for k, v in schema_node.items():\n        if k == \"$ref\" and v.startswith(\"#/definitions/\"):\n            # mutating while iterating but there's only ref value ever\n            schema_node[k] = \"#/definitions/resources/awscc.%s/actions/update/%s\" % (\n                rname,\n                v[2:],\n            )\n        elif isinstance(v, dict):\n            update_refs(v, rname)\n", "path": "tools/c7n_awscc/c7n_awscc/manager.py"}], "after_files": [{"content": "# Copyright The Cloud Custodian Authors.\n# SPDX-License-Identifier: Apache-2.0\nimport json\n\nimport jsonpatch\n\nfrom c7n.actions import Action\nfrom c7n.utils import local_session, type_schema\n\n\nclass ControlAction(Action):\n    def get_identity(self, r):\n        id_fields = self.manager.schema[\"primaryIdentifier\"]\n        idv = {}\n        for idf in id_fields:\n            idn = idf.rsplit(\"/\", 1)[-1]\n            idv[idn] = r[idn]\n        if len(idv) == 1:\n            return idv[idn]\n        return json.dumps(idv)\n\n\nclass Delete(ControlAction):\n    schema = type_schema(\"delete\")\n\n    def process(self, resources):\n        client = local_session(self.manager.session_factory).client(\"cloudcontrol\")\n        for r in resources:\n            client.delete_resource(\n                TypeName=self.manager.resource_type.cfn_type,\n                Identifier=self.get_identity(r),\n            )\n\n\nclass Update(ControlAction):\n    \"\"\"Update a resource.\n\n    Supports either whole key/value replacement via an attribute mapping\n    or jsonpatch `patch:`\n    \"\"\"\n\n    # schema is setup at resource type initialization\n\n    _action_meta = (\"type\", \"patch\")\n\n    def process(self, resources):\n        client = local_session(self.manager.session_factory).client(\"cloudcontrol\")\n        for r in resources:\n            patch = self.get_patch(r)\n            client.update_resource(\n                TypeName=self.manager.resource_type.cfn_type,\n                Identifier=self.get_identity(r),\n                PatchDocument=patch.to_string(),\n            )\n\n    def get_patch(self, r):\n        # we support either using json patch to do a partial modification.\n        if self.data.get(\"patch\"):\n            return jsonpatch.JsonPatch(self.data[\"patch\"])\n\n        current = dict(r)\n\n        # the action's schema reflects updatable properties\n        updatable = {k for k in self.schema[\"properties\"] if k not in self._action_meta}\n        for k in list(set(current) - updatable):\n            del current[k]\n\n        # shallow copy for patch generation\n        tgt = dict(current)\n\n        # or whole key value replacement.\n        for k, v in self.data.items():\n            if k == \"type\":\n                continue\n            tgt[k] = v\n        return jsonpatch.make_patch(current, tgt)\n", "path": "tools/c7n_awscc/c7n_awscc/actions.py"}, {"content": "# Copyright The Cloud Custodian Authors.\n# SPDX-License-Identifier: Apache-2.0\nimport json\nfrom pathlib import Path\n\nfrom c7n.filters import Filter  # noqa\n\nfrom .actions import Delete, Update\nfrom .query import CloudControl\nfrom .provider import resources\n\nfrom c7n.query import TypeInfo, QueryResourceManager\n\n\n_IndexData = None\n\n\ndef get_index():\n    global _IndexData\n\n    if _IndexData is not None:\n        return _IndexData\n\n    index_path = Path(__file__).parent / \"data\" / \"index.json\"\n    _IndexData = json.loads(index_path.read_text(encoding=\"utf8\"))\n    return _IndexData\n\n\ndef initialize_resource(resource_name):\n    \"\"\"Load a resource class from its name\"\"\"\n    rpath = Path(__file__).parent / \"data\" / f\"aws_{resource_name}.json\"\n    if not rpath.exists():\n        return None\n    rinfo = json.loads(rpath.read_text(encoding=\"utf8\"))\n\n    type_info = type(\n        \"resource_type\",\n        (TypeInfo,),\n        dict(\n            id=rinfo[\"primaryIdentifier\"][0].split(\"/\", 1)[-1],\n            service=rinfo[\"typeName\"].split(\"::\")[1].lower(),\n            cfn_type=rinfo[\"typeName\"],\n        ),\n    )\n\n    rname = \"_\".join([s.lower() for s in rinfo[\"typeName\"].split(\"::\")[1:]])\n    class_name = \"\".join([s.lower().capitalize() for s in rinfo[\"typeName\"].split(\"::\")[1:]])\n    mod_name = f\"c7n_awscc.resources.{resource_name}\"\n\n    permissions = rinfo.get(\"handlers\", {}).get(\"read\", {}).get(\"permissions\", []) + rinfo.get(\n        \"handlers\", {}\n    ).get(\"list\", {}).get(\"permissions\", [])\n\n    rtype = type(\n        class_name,\n        (QueryResourceManager,),\n        dict(\n            __module__=mod_name,\n            source_mapping={\"describe\": CloudControl},\n            resource_type=type_info,\n            permissions=permissions,\n            schema=rinfo,\n        ),\n    )\n\n    rtype.action_registry.register(\n        \"delete\",\n        type(\n            class_name + \"Delete\",\n            (Delete,),\n            {\n                \"permissions\": rinfo[\"handlers\"][\"delete\"][\"permissions\"],\n                \"__module__\": mod_name,\n            },\n        ),\n    )\n\n    if \"update\" in rinfo[\"handlers\"]:\n        rtype.action_registry.register(\n            \"update\",\n            type(\n                class_name + \"Update\",\n                (Update,),\n                {\n                    \"schema\": get_update_schema(rtype.schema, rname),\n                    \"permissions\": rinfo[\"handlers\"][\"update\"][\"permissions\"],\n                    \"__module__\": mod_name,\n                },\n            ),\n        )\n\n    process_supplementary_data(rtype)\n    resources.register(rname, rtype)\n\n    return {rtype.__name__: rtype}\n\n\ndef process_supplementary_data(rtype):\n    idx = get_index()\n    augment = idx[\"augment\"][rtype.resource_type.cfn_type]\n    rtype.resource_type.service = augment.get(\"service\") or \"\"\n\n\ndef get_update_schema(schema, rname):\n    prop_names = set(schema[\"properties\"])\n    create_only = {s.rsplit(\"/\", 1)[-1] for s in schema.get(\"createOnlyProperties\", ())}\n    read_only = {s.rsplit(\"/\", 1)[-1] for s in schema.get(\"readOnlyProperties\", ())}\n\n    updatable = prop_names - (create_only | read_only)\n    update_schema = {\n        \"additionalProperties\": False,\n        \"properties\": {u: schema[\"properties\"][u] for u in updatable},\n    }\n    update_schema[\"properties\"][\"type\"] = {\"enum\": [\"update\"]}\n    update_schema[\"properties\"][\"patch\"] = {\n        # This schema is pretty minimal\n        \"description\": \"Json patch to apply to resources\",\n        \"type\": \"array\",\n        \"items\": {\n            \"type\": \"object\",\n            \"required\": [\"op\", \"path\"],\n            \"properties\": {\n                \"path\": {\"type\": \"string\"},\n                \"op\": {\"enum\": [\"add\", \"remove\", \"update\", \"replace\", \"move\", \"copy\", \"test\"]},\n            },\n        },\n    }\n\n    if \"definitions\" in schema:\n        update_schema[\"definitions\"] = dict(schema[\"definitions\"])\n        update_refs(update_schema, rname)\n\n    return update_schema\n\n\ndef update_refs(schema_node, rname):\n    for k, v in schema_node.items():\n        if k == \"$ref\" and v.startswith(\"#/definitions/\"):\n            # mutating while iterating but there's only ref value ever\n            schema_node[k] = \"#/definitions/resources/awscc.%s/actions/update/%s\" % (\n                rname,\n                v[2:],\n            )\n        elif isinstance(v, dict):\n            update_refs(v, rname)\n", "path": "tools/c7n_awscc/c7n_awscc/manager.py"}]}