Repository: cloud-custodian/cloud-custodian

Issue:
Use of {now} produces exception: AttributeError: 'datetime.datetime' object has no attribute 'format'
### Describe the bug

When running a policy with an action to tag a resource using a value of {now} custodian produces the error:

AttributeError: 'datetime.datetime' object has no attribute 'format'


### What did you expect to happen?

Expect custodian to add a tag to the resource with a value of the current date.

### Cloud Provider

Amazon Web Services (AWS)

### Cloud Custodian version and dependency information

```shell
Custodian:   0.9.23
Python:      3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)]
Platform:   win32
Using venv:  True
Docker: False
Installed:

argcomplete==2.0.0
attrs==22.2.0
boto3==1.26.72
botocore==1.29.72
docutils==0.17.1
importlib-metadata==4.13.0
importlib-resources==5.10.2
jmespath==1.0.1
jsonschema==4.17.3
pkgutil-resolve-name==1.3.10
pyrsistent==0.19.3
python-dateutil==2.8.2
pyyaml==6.0
s3transfer==0.6.0
six==1.16.0
tabulate==0.8.10
typing-extensions==4.5.0
urllib3==1.26.14
zipp==3.13.0
```


### Policy

```shell
policies:
  - name: tag-unattached-volumes
    resource: aws.ebs
    filters:
      - Attachments: []
    actions:
      - type: tag
        key: unattached
        value: "{now}"
```


### Relevant log/traceback output

```shell
2023-02-24 10:00:02,425: custodian.output:ERROR Error while executing policy
Traceback (most recent call last):
  File "C:\path\to\custodian\lib\site-packages\c7n\policy.py", line 364, in run
    results = a.process(resources)
  File "C:\path\to\custodian\lib\site-packages\c7n\tags.py", line 412, in process
    self.interpolate_values(tags)
  File "C:\path\to\custodian\lib\site-packages\c7n\tags.py", line 442, in interpolate_values
    t['Value'] = self.interpolate_single_value(t['Value'])
  File "C:\path\to\custodian\lib\site-packages\c7n\tags.py", line 436, in interpolate_single_value
    return tag.format(**params)
  File "C:\path\to\custodian\lib\site-packages\c7n\utils.py", line 674, in __getattr__
    return getattr(self._d, k)
AttributeError: 'datetime.datetime' object has no attribute 'format'
```


### Extra information or context

_No response_


Discussion:
Thanks for the report, makes it easy to reproduce :+1: . As an immediate workaround, I'd suggest using doubled braces in your tag value:

```yaml
    actions:
      - type: tag
        key: unattached
        value: "{{now}}"
```

Because when #7532 landed we started having two variable interpolation passes: one [during the policy load](https://github.com/cloud-custodian/cloud-custodian/blob/ec229450512d7efb375262775c3d9cf9a0d32cc6/c7n/commands.py#L125), and another [during the tag action](https://github.com/cloud-custodian/cloud-custodian/blob/ec229450512d7efb375262775c3d9cf9a0d32cc6/c7n/tags.py#L868).

That wouldn't have caused this error until #7832, where we started trying to preserve variable types through format passes.

I'm not actually sure the best way to "fix" this :thinking: . One way would be to explicitly produce a string for `{now}` during that first pass rather than passing the date object through, but that may be flawed in another way.
this feels like a regression / release blocker, I'll try and dig before the release next week, thanks for the analysis @ajkerrigan 
a better more portable work around then double wrapping (which might break if we fix this) is going to be applying a format string to the value https://pyformat.info/#datetime  ie. `"{now:%Y-%m-%d}"`



Resolving patch:
diff --git a/c7n/tags.py b/c7n/tags.py
--- a/c7n/tags.py
+++ b/c7n/tags.py
@@ -430,7 +430,7 @@ def interpolate_single_value(self, tag):
             'account_id': self.manager.config.account_id,
             'now': utils.FormatDate.utcnow(),
             'region': self.manager.config.region}
-        return tag.format(**params)
+        return str(tag).format(**params)
 
     def interpolate_values(self, tags):
         """Interpolate in a list of tags - 'old' ec2 format
diff --git a/c7n/utils.py b/c7n/utils.py
--- a/c7n/utils.py
+++ b/c7n/utils.py
@@ -688,6 +688,9 @@ class FormatDate:
     def __init__(self, d=None):
         self._d = d
 
+    def __str__(self):
+        return str(self._d)
+
     @property
     def datetime(self):
         return self._d


Test patch:
diff --git a/tests/test_mu.py b/tests/test_mu.py
--- a/tests/test_mu.py
+++ b/tests/test_mu.py
@@ -938,6 +938,11 @@ def test_different_lambda_handler(self):
     def test_config_defaults(self):
         p = PolicyLambda(Bag({"name": "hello", "data": {"mode": {}}}))
         self.maxDiff = None
+        default_arch = platform.machine()
+        if default_arch in ('aarch64', 'arm64'):
+            default_arch = 'arm64'
+        else:
+            default_arch = 'x86_64'
         self.assertEqual(
             p.get_config(),
             {
@@ -949,7 +954,7 @@ def test_config_defaults(self):
                 "MemorySize": 512,
                 "Role": "",
                 "Runtime": "python3.9",
-                "Architectures": ["x86_64"],
+                "Architectures": [default_arch],
                 "Tags": {},
                 "Timeout": 900,
                 "TracingConfig": {"Mode": "PassThrough"},
diff --git a/tests/test_tags.py b/tests/test_tags.py
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -80,6 +80,7 @@ def __tag_interpolation_helper(self, resource_name, resources):
             },
             session_factory=mock_factory,
         )
+        policy.expand_variables(policy.get_variables())
         policy.resource_manager.actions[0].process(resources)
 
         return (create_tags, tag_resources)
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -230,6 +230,7 @@ def test_format_date(self):
         self.assertEqual(json.dumps(utils.FormatDate(d),
                                     cls=utils.DateTimeEncoder, indent=2),
                          '"2018-02-02T12:00:00"')
+        self.assertEqual(str(d), '2018-02-02 12:00:00')
 
     def test_group_by(self):
         items = [{}, {"Type": "a"}, {"Type": "a"}, {"Type": "b"}]