Skip to content

Commit

Permalink
add support for helm registry login/logout via new module
Browse files Browse the repository at this point in the history
  • Loading branch information
abikouo committed Mar 29, 2023
1 parent fb2af07 commit 8bf598c
Show file tree
Hide file tree
Showing 25 changed files with 473 additions and 5 deletions.
10 changes: 5 additions & 5 deletions plugins/module_utils/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,11 @@ def env_update(self):
self.helm_env = self._prepare_helm_environment()
return self.helm_env

def run_helm_command(self, command, fails_on_error=True):
if not HAS_YAML:
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)

rc, out, err = self.run_command(command, environ_update=self.env_update)
def run_helm_command(self, command, fails_on_error=True, add_env_update=None):
env_update = self.env_update
if add_env_update:
env_update.update(add_env_update)
rc, out, err = self.run_command(command, environ_update=env_update)
if fails_on_error and rc != 0:
self.fail_json(
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
Expand Down
190 changes: 190 additions & 0 deletions plugins/modules/helm_registry_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2023, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function

__metaclass__ = type


DOCUMENTATION = r"""
---
module: helm_registry_auth
short_description: login or logout to a registry.
version_added: "2.5.0"
author:
- Aubin Bikouo (@abikouo)
requirements:
- "helm (https://github.com/helm/helm/releases)"
description:
- Authenticate to a remote registry analogous to C(helm registry login)
or Remove credentials stored for a remote registry analogous to C(helm registry logout).
options:
state:
description:
- If set to I(present) attempt to log in to the remote registry server using the URL specified in C(host).
- If set to I(absent) attempt to log out by removing credentials stored for the remote registry server specified in C(host).
default: present
choices:
- present
- absent
type: str
host:
description:
- Provide a URL for accessing the remote registry.
type: str
required: True
validate_certs:
description:
- Whether or not to verify the Registry server's SSL certificates.
type: bool
aliases: [ verify_ssl ]
default: True
username:
description:
- Provide a username for authenticating with the remote registry.
- Required when C(state) is set to I(present).
type: str
password:
description:
- Provide a password for authenticating with the remote registry.
- Required when C(state) is set to I(present).
type: str
binary_path:
description:
- The path of a helm binary to use.
type: path
"""

EXAMPLES = r"""
- hosts: localhost
tasks:
- block:
# It's good practice to store login credentials in a secure vault and not
# directly in playbooks.
- include_vars: helm_registry_passwords.yml
- name: Login to remote registry
kubernetes.core.helm_registry_auth:
username: admin
password: "{{ helm_admin_password }}"
host: localhost:5000
- name: Download Chart from Registry
kubernetes.core.helm_pull:
chart_ref: mychart
repo_url: oci://localhost:5000/helm-charts
always:
- name: Logout to Remote registry
kubernetes.core.helm_registry_auth:
host: localhost:5000
state: absent
"""

RETURN = r"""
stdout:
type: str
description: Full `helm` command stdout, in case you want to display it or examine the event log
returned: always
sample: ''
stderr:
type: str
description: Full `helm` command stderr, in case you want to display it or examine the event log
returned: always
sample: ''
command:
type: str
description: Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.
returned: always
sample: helm registry login...
"""


from ansible_collections.kubernetes.core.plugins.module_utils.helm import (
AnsibleHelmModule,
)
from ansible_collections.kubernetes.core.plugins.module_utils.version import (
LooseVersion,
)


def argument_spec():
arg_spec = {
"state": {
"type": "str",
"default": "present",
"choices": ["present", "absent"],
},
"host": {"type": "str", "required": True},
"validate_certs": {"type": "bool", "default": True, "aliases": ["verify_ssl"]},
"username": {},
"password": {"no_log": True},
"binary_path": {"type": "path"},
}

return arg_spec


def main():

module = AnsibleHelmModule(
argument_spec=argument_spec(),
required_if=[
("state", "present", ["username", "password"]),
],
supports_check_mode=True,
)

state = module.params.get("state")
command = [module.get_helm_binary(), "registry"]
if state == "present":
command.extend(
[
"login",
"--username",
module.params.get("username"),
"--password",
module.params.get("password"),
]
)
else:
command.append("logout")

command.append(module.params.get("host"))
command = " ".join(command)
out, err = "", ""
changed = True
if not module.check_mode:
env_update = {}
if LooseVersion(module.get_helm_version()) < LooseVersion("3.0.0"):
env_update["HELM_EXPERIMENTAL_OCI"] = "1"
rc, out, err = module.run_helm_command(
command, fails_on_error=False, add_env_update=env_update
)
if rc != 0:
if state == "absent" and "Error: not logged in" in err:
err = err.replace("Error: ", "")
changed = False
else:
module.fail_json(
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(
rc, out, err
),
stdout=out,
stderr=err,
command=command,
)

module.exit_json(changed=changed, stdout=out, stderr=err, command=command)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions tests/integration/targets/helm_kubeconfig/meta/main.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
dependencies:
- remove_namespace
- install_helm
2 changes: 2 additions & 0 deletions tests/integration/targets/helm_registry_auth/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
time=16
helm_registry_auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
# user: testuser, password: pass123!
registry_credentials: testuser:$2y$05$d8tw6L1hojRFW.FjHOAnIOihJWAvFb0/Pu/30hLbQNJIYzCmlyBCi
registry_name: helm_registry
registry_port: 6035
test_namespace:
- "helm-registry-auth-1"
- "helm-registry-auth-2"
helm_version: v3.8.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v2
name: python-app
description: |
A Helm chart used to test OCI registry login for Kubernetes.core Ansible collection
type: application
version: 0.1.0
appVersion: "default"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-python-deployment
labels:
app: python
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: python
template:
metadata:
labels:
app: python
spec:
containers:
- name: {{ .Values.container.name }}
image: {{ .Values.container.image }}
imagePullPolicy: {{ .Values.container.imagePullPolicy }}
args: ['/bin/sh', '-c', 'while true; do echo $(date); sleep 10; done']
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
replicaCount: 1
container:
name: "python3"
image: "python:3.7-alpine"
imagePullPolicy: "IfNotPresent"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
- name: teardown registry
include_tasks: teardown_registry.yml
5 changes: 5 additions & 0 deletions tests/integration/targets/helm_registry_auth/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
dependencies:
- remove_namespace
- role: install_helm
helm_version: v3.8.0
3 changes: 3 additions & 0 deletions tests/integration/targets/helm_registry_auth/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
- include_tasks: setup_registry.yml
- include_tasks: tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
- name: Inspect docker container
command: docker container inspect {{ registry_name }} -f '{{ '{{' }} .State.Running {{ '}}' }}'
register: _inspect
ignore_errors: true

- name: Remove container
when: _inspect.rc == 0
block:
- name: Stop running container
command: docker container stop {{ registry_name }}
when: _inspect.stdout == "true"

- name: Remove container
command: docker container rm {{ registry_name }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
- name: Ensure docker is present
command: |
command -v docker
ignore_errors: true
register: check_docker

- name: Setup OCI registry
block:
- name: create docker registry
tempfile:
state: directory
suffix: .auth
register: _tmpfile
notify:
- teardown registry

- name: create auth file
copy:
content: "{{ registry_credentials }}"
dest: "{{ _tmpfile.path }}/htpasswd"

- include_tasks: remove_docker_container.yml

- name: Create registry container
command: >-
docker run -d
-p {{ registry_port }}:5000
--restart=always
--name "{{ registry_name }}"
-v "{{ _tmpfile.path }}:/auth"
-e "REGISTRY_AUTH=htpasswd"
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm"
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
registry:2
- name: try login to OCI registry
command: >-
{{ helm_binary_path }} registry login
-u testuser
-p 'pass123!'
localhost:{{ registry_port }}
- name: Package helm chart
command: >-
{{ helm_binary_path }} package
"{{ role_path }}/files/python-chart"
--destination {{ _tmpfile.path }}
- name: Helm push chart to the registry
command: >-
{{ helm_binary_path }} push
{{ _tmpfile.path }}/python-app-0.1.0.tgz
oci://localhost:{{ registry_port }}/helm-charts
- name: Show chart from registry
command: >-
{{ helm_binary_path }} show all oci://localhost:{{ registry_port }}/helm-charts/python-app
- name: Logout from registry
command: >-
{{ helm_binary_path }} registry logout localhost:{{ registry_port }}
when:
- check_docker.rc == 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
- name: Remove Docker container
include_tasks: remove_docker_container.yml

- name: Delete temporary directory
file:
state: absent
path: '{{ _tmpfile.path }}'
ignore_errors: true
Loading

0 comments on commit 8bf598c

Please sign in to comment.