cdk8s-team / cdk8s

Define Kubernetes native apps and abstractions using object-oriented programming
https://cdk8s.io
Apache License 2.0
4.36k stars 294 forks source link

Question: why are all the attributes read only? #423

Closed mcouthon closed 3 years ago

mcouthon commented 3 years ago

I see that (at least in python) all the attributes of the auto generated k8s classes are decorated as properties. Why is that? Why not allow changing the values after creation?

If there's a good reason to disallow that, then what would be the canonical way to change only one value inside an already created object?

For example, my scenario is that I have a list of containers, and post hoc want to be able to enforce some security rules. So I wanted to be able to do something like:

        # Enforce security best practices
        for container in containers:
            if not container.security_context:
                container.security_context = k8s.SecurityContext()
            container.security_context.read_only_root_filesystem = True
            container.security_context.privileged = False

But, of course, security_context is a read-only attribute (as are read_only_root_filesystem and privileged).

I can hack around that with direct access to the private _values dict, but I'm assuming this is frowned upon.

Thanks!

iliapolo commented 3 years ago

@mcouthon We currently opted for immutability on the generated L1 level constructs to provide a more predictable experience (think about another function possibly overriding your changes).

In contrast, the L2 level constructs do offer some mutation capabilities, for example, you can add containers to existing jobs:

https://github.com/awslabs/cdk8s/blob/38a703411c7162bfa4d1904ef6b94e2a017ea4da/packages/cdk8s-plus-17/test/job.test.ts#L56-L62

But since these are hand-crafted, its easier to make them safer, since the author can hand pick what can and can't be modified.

Having said that, there is a distinction between spec classes (e.g k8s.Container), which are mainly used as inputs, and resource classes (e.g k8s.KubePod), which are mainly used as outputs. Inputs normally shouldn't be modified because it makes it too easy to cause unexpected behavior. Outputs are less susceptible to that, and we have an issue to reconsider this, as it creates some awkward authoring ergonomics.

If there's a good reason to disallow that, then what would be the canonical way to change only one value inside an already created object?

Currently the way to do this is by using escape hatches. But they are only available on resource classes. So for example, if those containers are eventually used to configure, say a KubeJob, then you can do something like this:

from imports import k8s
from cdk8s import JsonPatch

def enforce_security_policies_on_job(self, job: k8s.KubeJob):

  containers = job.to_json()['spec']['template']['spec']['containers']

  for i, container in enumerate(containers):
    if not container.get('securityContext'):
      job.add_json_patch(JsonPatch.add(f'/spec/template/spec/containers/{i}/securityContext', {
        'readOnlyRootFilesystem': True,
        'privileged': False
      }))

Another, possibly better but more boilerplaty approach, is to create your own SecureJob, class that extends k8s.KubeJob and passes a modified copy of the containers to super.

github-actions[bot] commented 3 years ago

This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.