Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/20250731-feature-k8s-subresource.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- Added support for `subresources` to the `k8s` module.
33 changes: 32 additions & 1 deletion docs/kubernetes.core.k8s_module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,22 @@ Parameters
<div>Ignored if <code>wait</code> is not set.</div>
</td>
</tr>
<tr>
<td colspan="3">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>subresource</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
<b>Default:</b><br/><div style="color: blue"></div>
</td>
<td>
<div>Provide the <code>subresource</code> to run your definition against.</div>
</td>
</tr>
</table>
<br/>

Expand Down Expand Up @@ -1275,7 +1291,22 @@ Examples
kind: Deployment
delete_all: true


# Approve a CSR using the approval <code>subresource</code> option.
- kubernetes.core.k8s:
subresource: approval
definition:
apiVersion: certificates.k8s.io/v1
kind: certificatesigningrequests
metadata:
name: testuser
status:
conditions:
- lastTransitionTime: "2025-07-31T16:00:00Z"
lastUpdateTime: "2025-07-31T16:00:00Z"
message: Approved by Ansible
reason: Approved
status: "True"
type: Approved

Return Values
-------------
Expand Down
16 changes: 15 additions & 1 deletion plugins/module_utils/k8s/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def perform_action(svc, definition: Dict, params: Dict) -> Dict:
kind = definition.get("kind")
api_version = definition.get("apiVersion")
hidden_fields = params.get("hidden_fields")
subresource = params.get("subresource")

result = {"changed": False, "result": {}}
instance = {}
Expand All @@ -144,7 +145,20 @@ def perform_action(svc, definition: Dict, params: Dict) -> Dict:
resource = svc.find_resource(kind, api_version, fail=True)
definition["kind"] = resource.kind
definition["apiVersion"] = resource.group_version
existing = svc.retrieve(resource, definition)

if subresource and subresource is not None:
if subresource not in resource.subresources.keys():
raise CoreException(
"The resource {resource} doesn't support the subresource {subresource}".format(
resource=resource.kind,
subresource=subresource,
)
)

existing = svc.retrieve(resource.subresources[subresource], definition)
resource = resource.subresources[subresource]
else:
existing = svc.retrieve(resource, definition)

if state == "absent":
if exists(existing) and existing.kind.endswith("List"):
Expand Down
6 changes: 6 additions & 0 deletions plugins/modules/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@
type: list
elements: str
version_added: 3.0.0
subresource:
description:
- Provide the C(subresource) to run your definition against.
type: str
version_added: 6.2.0

requirements:
- "python >= 3.9"
Expand Down Expand Up @@ -481,6 +486,7 @@ def argspec():
)
argument_spec["delete_all"] = dict(type="bool", default=False, aliases=["all"])
argument_spec["hidden_fields"] = dict(type="list", elements="str")
argument_spec["subresource"] = dict(type="str")

return argument_spec

Expand Down
3 changes: 3 additions & 0 deletions tests/integration/targets/k8s_subresource/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
time=20
k8s
k8s_info
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# python 3 headers, required if submitting to Ansible
from __future__ import absolute_import, division, print_function

__metaclass__ = type

DOCUMENTATION = r"""
name: create_dummy_csr
author: Yorick Gruijthuijzen (@yorick1989) <[email protected]>
version_added: "1.0"
short_description: Returns test csr content
description:
- Returns test csr content
options:
_term:
description: The Common Name
required: True
type: string
"""

from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display

display = Display()

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID


class LookupModule(LookupBase):
"""Main module implementation"""

def run(self, terms, variables=None, **kwargs):
common_name = terms[0]

display.debug("Generating CSR with the Common Name: %s" % common_name)

key = rsa.generate_private_key(
public_exponent=65537,
key_size=int(kwargs.get("key_size", 2048)),
)

return [
x509.CertificateSigningRequestBuilder()
.subject_name(
x509.Name(
[
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
]
)
)
.sign(key, hashes.SHA256())
.public_bytes(serialization.Encoding.PEM)
]
8 changes: 8 additions & 0 deletions tests/integration/targets/k8s_subresource/playbook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
- connection: local
gather_facts: false
hosts: localhost
environment:
K8S_AUTH_KUBECONFIG: "{{ lookup('ansible.builtin.env', 'K8S_AUTH_KUBECONFIG', default='~/.kube/config') }}"
roles:
- k8s_subresource
5 changes: 5 additions & 0 deletions tests/integration/targets/k8s_subresource/runme.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -eux
export ANSIBLE_CALLBACKS_ENABLED=profile_tasks
export ANSIBLE_ROLES_PATH=../
ansible-playbook playbook.yaml "$@"
61 changes: 61 additions & 0 deletions tests/integration/targets/k8s_subresource/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
- name: Create a CSR
kubernetes.core.k8s:
definition:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: testuser
spec:
request: '{{ lookup("create_dummy_csr", "testuser") | b64encode }}'
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
wait: yes

- block:
- name: Get the date_time
ansible.builtin.setup:
gather_subset: date_time

- name: Set CSR condition fact
ansible.builtin.set_fact:
csr_approval_condition:
- lastUpdateTime: "{{ ansible_date_time.iso8601 }}"
lastTransitionTime: "{{ ansible_date_time.iso8601 }}"
message: Approval testing
reason: Approved
status: "True"
type: Approved

- name: Approve the CSR
kubernetes.core.k8s:
subresource: approval
definition:
apiVersion: certificates.k8s.io/v1
kind: certificatesigningrequests
metadata:
name: testuser
status:
conditions: "{{ csr_approval_condition }}"
wait: yes

- name: Get the CSR info
kubernetes.core.k8s_info:
api_version: certificates.k8s.io/v1
kind: certificatesigningrequests
name: testuser
register: csr_check

- name: assert that the CSR is approved
ansible.builtin.assert:
that:
- csr_check.resources.0.status.conditions.0 == csr_approval_condition.0

always:
- name: Remove the CSR
kubernetes.core.k8s:
api_version: certificates.k8s.io/v1
kind: certificatesigningrequests
name: testuser
state: absent
Loading