diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 15d9c10..6502705 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,10 @@ jobs: make init - name: Run tests run: | - molecule test --scenario-name "${{ matrix.scenario }}" + molecule test --scenario-name "${{ matrix.scenario }}" --destroy=never env: PY_COLORS: '1' ANSIBLE_FORCE_COLOR: '1' + - name: Setup tmate session + if: ${{ failure() }} + uses: mxschmitt/action-tmate@v3 diff --git a/.yamllint b/.yamllint index 1900594..876e94d 100644 --- a/.yamllint +++ b/.yamllint @@ -4,6 +4,7 @@ extends: default ignore: | .github/* + molecule/end-to-end_demo/deploy_files/namespace-helm-chart/* # https://yamllint.readthedocs.io/en/stable/rules.html rules: diff --git a/molecule/end-to-end_demo/converge.yml b/molecule/end-to-end_demo/converge.yml new file mode 100644 index 0000000..4802a22 --- /dev/null +++ b/molecule/end-to-end_demo/converge.yml @@ -0,0 +1,198 @@ +--- + +- name: Converge using end-to-end + ansible.builtin.import_playbook: ../end-to-end/converge.yml + +- name: Install Helm + hosts: + - control_plane + gather_facts: false + become: true + become_method: su + roles: + - { role: geerlingguy.helm } + +- name: Prepare control-plane + hosts: + - k8s-control-plane-01 + gather_facts: false + become: true + become_method: su + tasks: + - name: Copy manifests and helm files + ansible.builtin.copy: + src: deploy_files/ + dest: /root/demo + mode: 0644 + + - name: Install pip library + ansible.builtin.pip: + name: kubernetes + +- name: Create namespace using self written chart + hosts: + - k8s-control-plane-01 + gather_facts: false + tasks: + - name: Create namespaces + kubernetes.core.helm: + name: namespaces + release_namespace: default + chart_ref: /root/demo/namespace-helm-chart + +- name: Loki and Grafana deployment + hosts: + - k8s-control-plane-01 + gather_facts: false + become: true + become_method: su + tasks: + - name: Loki installation + block: + - name: Add grafana/loki helm repo + kubernetes.core.helm_repository: + name: grafana + repo_url: "https://grafana.github.io/helm-charts" + + - name: Install loki helm release + kubernetes.core.helm: + name: loki + chart_ref: grafana/loki-stack + release_namespace: loki-ns + + - name: Grafana installation + block: + - name: Install grafana helm release + kubernetes.core.helm: + name: grafana + chart_ref: grafana/grafana + release_namespace: grafana-ns + values_files: + - /root/demo/grafana/helm-values.yml + +- name: Ingress Controller + hosts: + - k8s-control-plane-01 + gather_facts: false + become: true + become_method: su + tasks: + - name: Add ingress controller helm repo + kubernetes.core.helm_repository: + name: ingress-nginx + repo_url: "https://kubernetes.github.io/ingress-nginx" + + - name: Install ingress controller helm release + kubernetes.core.helm: + name: ingress-nginx + chart_ref: ingress-nginx/ingress-nginx + release_namespace: ingress-nginx-ns + timeout: 2m + values_files: + - /root/demo/ingress-controller/helm-values.yml + register: helm_result + changed_when: false + failed_when: false + + - name: Debug + ansible.builtin.debug: + msg: "{{ helm_result }}" + +- name: Cert Manager + hosts: + - k8s-control-plane-01 + gather_facts: false + become: true + become_method: su + tasks: + - name: Install Cert Manager + block: + - name: Add cert-manager helm repo + kubernetes.core.helm_repository: + name: jetstack + repo_url: "https://charts.jetstack.io" + + - name: Install cert-manager helm release + kubernetes.core.helm: + name: cert-manager + chart_ref: jetstack/cert-manager + release_namespace: cert-manager-ns + timeout: 2m + values_files: + - /root/demo/cert-manager/helm-values.yml + register: helm_result + changed_when: false + failed_when: false + + - name: Debug + ansible.builtin.debug: + msg: "{{ helm_result }}" + - name: Pause + ansible.builtin.pause: + + - name: Prepare CA certificate + block: + - name: Create selfsigned issuer + kubernetes.core.k8s: + state: present + src: /root/demo/cert-manager/selfsigned-issuer.yml + + - name: Create CA certificate + kubernetes.core.k8s: + state: present + namespace: default + src: /root/demo/cert-manager/selfsigned-cert.yml + + - name: Create CA issuer + kubernetes.core.k8s: + state: present + src: /root/demo/cert-manager/root-ca-issuer.yml + +- name: Ingress + hosts: + - k8s-control-plane-01 + gather_facts: false + become: true + become_method: su + tasks: + - name: Ingress to grafana + kubernetes.core.k8s: + state: present + src: /root/demo/grafana/ingress-to-grafana.yml + +- name: Useful output + hosts: + - k8s-control-plane-01 + gather_facts: false + become: true + become_method: su + tasks: + - name: CA cert + block: + - name: Collect CA cert + ansible.builtin.shell: | + set -o pipefail && \ + kubectl get secret -n default root-ca -o jsonpath="{.data.ca\.crt}" | base64 -d + args: + executable: /bin/bash + register: root_ca_secret + changed_when: False + + - name: Print CA cert + ansible.builtin.debug: + msg: "{{ root_ca_secret.stdout }}" + + - name: Grafana admin password + block: + - name: Collect password + ansible.builtin.shell: | + set -o pipefail && \ + kubectl get secret --namespace grafana-ns grafana -o jsonpath="{.data.admin-password}" | base64 -d + args: + executable: /bin/bash + register: grafana_password + changed_when: False + + - name: Print Grafana password + ansible.builtin.debug: + msg: "{{ grafana_password.stdout }}" diff --git a/molecule/end-to-end_demo/deploy_files/cert-manager/helm-values.yml b/molecule/end-to-end_demo/deploy_files/cert-manager/helm-values.yml new file mode 100644 index 0000000..5040d7c --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/cert-manager/helm-values.yml @@ -0,0 +1,6 @@ +--- + +installCRDs: true +prometheus: + enabled: false +clusterResourceNamespace: default diff --git a/molecule/end-to-end_demo/deploy_files/cert-manager/root-ca-issuer.yml b/molecule/end-to-end_demo/deploy_files/cert-manager/root-ca-issuer.yml new file mode 100644 index 0000000..d4eca5a --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/cert-manager/root-ca-issuer.yml @@ -0,0 +1,9 @@ +--- + +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: root-ca-issuer +spec: + ca: + secretName: root-ca diff --git a/molecule/end-to-end_demo/deploy_files/cert-manager/selfsigned-cert.yml b/molecule/end-to-end_demo/deploy_files/cert-manager/selfsigned-cert.yml new file mode 100644 index 0000000..dae377f --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/cert-manager/selfsigned-cert.yml @@ -0,0 +1,25 @@ +--- + +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: self-signed-root-cert +spec: + duration: 24h + renewBefore: 2h + dnsNames: + - k8s.myorg.com + secretName: root-ca + subject: + organizations: + - myorg + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + isCA: true + usages: + - signing + - cert sign + privateKey: + algorithm: RSA + size: 2048 diff --git a/molecule/end-to-end_demo/deploy_files/cert-manager/selfsigned-issuer.yml b/molecule/end-to-end_demo/deploy_files/cert-manager/selfsigned-issuer.yml new file mode 100644 index 0000000..6f9e70f --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/cert-manager/selfsigned-issuer.yml @@ -0,0 +1,8 @@ +--- + +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} diff --git a/molecule/end-to-end_demo/deploy_files/grafana/helm-values.yml b/molecule/end-to-end_demo/deploy_files/grafana/helm-values.yml new file mode 100644 index 0000000..de470f6 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/grafana/helm-values.yml @@ -0,0 +1,4 @@ +--- + +persistence: + size: 2Gi diff --git a/molecule/end-to-end_demo/deploy_files/grafana/ingress-to-grafana.yml b/molecule/end-to-end_demo/deploy_files/grafana/ingress-to-grafana.yml new file mode 100644 index 0000000..7ff9dad --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/grafana/ingress-to-grafana.yml @@ -0,0 +1,27 @@ +--- + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: grafana-ingress + namespace: grafana-ns + annotations: + cert-manager.io/cluster-issuer: "root-ca-issuer" + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + tls: + - hosts: + - grafana.k8s.myorg.com + secretName: grafana-tls + rules: + - host: grafana.k8s.myorg.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: grafana + port: + number: 80 diff --git a/molecule/end-to-end_demo/deploy_files/ingress-controller/helm-values.yml b/molecule/end-to-end_demo/deploy_files/ingress-controller/helm-values.yml new file mode 100644 index 0000000..8e3a974 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/ingress-controller/helm-values.yml @@ -0,0 +1,11 @@ +--- + +controller: + ingressClassResource: + default: true + kind: DaemonSet + service: + type: NodePort + nodePorts: + http: 30080 + https: 30443 diff --git a/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/Chart.yaml b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/Chart.yaml new file mode 100644 index 0000000..0e215e6 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: sa-chart +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/NOTES.txt b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/NOTES.txt new file mode 100644 index 0000000..21d25c2 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/NOTES.txt @@ -0,0 +1,23 @@ +1. +{{- range $namespace := .Values.namespaces }} +Namespace {{ $namespace.name }} was created +{{- end }} +2. +{{- range $namespace := .Values.namespaces }} +{{- range $user := $namespace.users }} +ServiceAccount for {{ $user }} in {{ $namespace.name }} namespace was created +{{- end }} +{{- end }} +3. +{{- range $namespace := .Values.namespaces }} +{{- range $user := $namespace.users }} +Secret for {{ $user }} in {{ $namespace.name }} namespace was created +{{- end }} +{{- end }} +4. +{{- range $namespace := .Values.namespaces }} +{{- range $user := $namespace.users }} +RoleBinding for {{ $user }} in {{ $namespace.name }} namespace was created +{{- end }} +{{- end }} + diff --git a/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/namespaces.yml b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/namespaces.yml new file mode 100644 index 0000000..6e6b090 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/namespaces.yml @@ -0,0 +1,10 @@ +{{- range $namespace := .Values.namespaces }} +--- +apiVersion: v1 +kind: Namespace +metadata: + name: {{ $namespace.name }} + labels: + name: {{ $namespace.name }} +{{- end }} + diff --git a/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/rolebindings.yml b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/rolebindings.yml new file mode 100644 index 0000000..73e5356 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/rolebindings.yml @@ -0,0 +1,17 @@ +{{- range $namespace := .Values.namespaces }} +{{- range $user := $namespace.users }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: admin-{{ $user }}-{{ $namespace.name }}-binding + namespace: {{ $namespace.name }} +subjects: +- kind: ServiceAccount + name: {{ $user }} +roleRef: + kind: ClusterRole + name: admin +{{ end }} +{{ end }} + diff --git a/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/secrets.yml b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/secrets.yml new file mode 100644 index 0000000..c0c54e4 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/secrets.yml @@ -0,0 +1,14 @@ +{{- range $namespace := .Values.namespaces }} +{{- range $user := $namespace.users }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ $user }}-secret + namespace: {{ $namespace.name }} + annotations: + kubernetes.io/service-account.name: {{ $user }} +type: kubernetes.io/service-account-token +{{ end }} +{{ end }} + diff --git a/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/serviceaccounts.yml b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/serviceaccounts.yml new file mode 100644 index 0000000..a9371f1 --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/templates/serviceaccounts.yml @@ -0,0 +1,11 @@ +{{- range $namespace := .Values.namespaces }} +{{- range $user := $namespace.users }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $user }} + namespace: {{ $namespace.name }} +{{ end }} +{{ end }} + diff --git a/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/values.yaml b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/values.yaml new file mode 100644 index 0000000..7d74f3b --- /dev/null +++ b/molecule/end-to-end_demo/deploy_files/namespace-helm-chart/values.yaml @@ -0,0 +1,9 @@ +# Default values for sa-chart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +namespaces: +- name: loki-ns +- name: grafana-ns +- name: ingress-nginx-ns +- name: cert-manager-ns diff --git a/molecule/end-to-end_demo/molecule.yml b/molecule/end-to-end_demo/molecule.yml new file mode 100644 index 0000000..5ed2845 --- /dev/null +++ b/molecule/end-to-end_demo/molecule.yml @@ -0,0 +1,245 @@ +--- + +dependency: + name: galaxy +driver: + name: docker +platforms: + # etcd + - &default_platform_common + name: etcd-instance-01 + hostname: etcd-instance-01 + image: mpaivabarbosa/molecule-systemd-ubuntu:20.04 + groups: + - etcd + command: /sbin/init + security_opts: + - seccomp=unconfined + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + pre_build_image: true + override_command: false + keep_volumes: false + # https://github.com/ansible-community/molecule-docker/blob/main/src/molecule_docker/driver.py + docker_networks: + - name: k8s_cluster + ipam_config: + - subnet: '172.18.0.0/24' + gateway: '172.18.0.254' + networks: + - name: k8s_cluster + - <<: *default_platform_common + name: etcd-instance-02 + hostname: etcd-instance-02 + # load balancers + - <<: *default_platform_common + name: lb-etcd + hostname: lb-etcd + groups: + - lb + networks: + - name: k8s_cluster + ipv4_address: "172.18.0.100" + - <<: *default_platform_common + name: lb-master + hostname: lb-master + groups: + - lb + networks: + - name: k8s_cluster + ipv4_address: "172.18.0.200" + # -------- + # k8s + - &default_platform + name: k8s-control-plane-01 + hostname: k8s-control-plane-01 + image: kindest/node:v1.26.3 + groups: + - control_plane + - etcd_clients + volumes: + - /lib/modules:/lib/modules:ro + - /var/lib/containerd + privileged: true + pre_build_image: true + keep_volumes: false + sysctls: + net.bridge.bridge-nf-call-iptables: 1 + net.bridge.bridge-nf-call-ip6tables: 1 + net.ipv4.ip_forward: 1 + docker_networks: + - name: k8s_cluster + ipam_config: + - subnet: '172.18.0.0/24' + gateway: '172.18.0.254' + networks: + - name: k8s_cluster + etc_hosts: + 'etcd.cloudlabsinfra.local': '172.18.0.100' # dns name of etcd load balancer + 'control-plane.cloudlabsinfra.local': '172.18.0.200' # dns name of control-plane load balancer + - <<: *default_platform + name: k8s-control-plane-02 + hostname: k8s-control-plane-02 + - <<: *default_platform + name: k8s-worker-01 + hostname: k8s-worker-01 + groups: + - workers +provisioner: + name: ansible + config_options: + defaults: + stdout_callback: yaml + stderr_callback: yaml + inventory: + host_vars: + k8s-control-plane-01: + # cloudlabsinfra.k8s_cluster role related variables + k8s_cluster_initial_master: true + k8s_cluster_kubernetes_version: 1.26.0 + # we can't use default cluster configuration here because it doesn't have 'etcd' section + k8s_cluster_cluster_configuration: + etcd: + external: + endpoints: ["https://{{ etcd_frontend_name }}:2379"] + caFile: "/etc/ssl/private/ca.pem" + certFile: "/etc/ssl/private/client.pem" + keyFile: "/etc/ssl/private/client-key.pem" + networking: + serviceSubnet: 10.96.0.0/12 + podSubnet: 10.244.0.0/16 + dnsDomain: cluster.local + kubernetesVersion: "{{ k8s_cluster_kubernetes_version }}" + controlPlaneEndpoint: "{{ k8s_cluster_control_plane_endpoint }}:6443" + # custom networking + k8s_cluster_flannel_apply: "" + k8s_cluster_custom_networking_tasks_path: "network/custom-networking.yml" + lb-etcd: + haproxy_frontend_name: 'etcd' + haproxy_frontend_bind_address: '*' + haproxy_frontend_port: 2379 + haproxy_frontend_mode: 'tcp' + haproxy_backend_name: 'etcd' + haproxy_backend_mode: 'tcp' + haproxy_backend_balance_method: 'roundrobin' + haproxy_backend_httpchk: '' + haproxy_backend_servers: + - name: etcd-instance-01 + address: "{{ hostvars['etcd-instance-01']['ansible_facts']['default_ipv4']['address'] }}:2379" + - name: etcd-instance-02 + address: "{{ hostvars['etcd-instance-02']['ansible_facts']['default_ipv4']['address'] }}:2379" + lb-master: + haproxy_frontend_name: 'master' + haproxy_frontend_bind_address: '*' + haproxy_frontend_port: 6443 + haproxy_frontend_mode: 'tcp' + haproxy_backend_name: 'master' + haproxy_backend_mode: 'tcp' + haproxy_backend_balance_method: 'roundrobin' + haproxy_backend_httpchk: '' + haproxy_backend_servers: + - name: k8s-control-plane-01 + address: "{{ hostvars['k8s-control-plane-01']['ansible_facts']['default_ipv4']['address'] }}:6443" + - name: k8s-control-plane-02 + address: "{{ hostvars['k8s-control-plane-02']['ansible_facts']['default_ipv4']['address'] }}:6443" + group_vars: + all: + # required for control-plane nodes and etcd as well + etcd_frontend_name: "etcd.cloudlabsinfra.local" + k8s_cluster_control_plane_endpoint: "control-plane.cloudlabsinfra.local" + control_plane: + # default value of variable below is 'worker' + k8s_cluster_node_type: "master" + # we can't use default join configuration here because it doesn't have 'controlPlane' section + k8s_cluster_join_configuration: + nodeRegistration: + name: "{{ k8s_cluster_node_name }}" + ignorePreflightErrors: + - SystemVerification + discovery: + bootstrapToken: + token: "{{ k8s_cluster_join_token }}" + apiServerEndpoint: "{{ k8s_cluster_control_plane_endpoint }}:6443" + caCertHashes: + - "sha256:{{ k8s_cluster_root_ca_hash }}" + unsafeSkipCAVerification: false + controlPlane: + localAPIEndpoint: + advertiseAddress: "{{ hostvars[inventory_hostname]['ansible_facts']['default_ipv4']['address'] }}" + bindPort: 6443 + etcd_clients: + etcd_cert_matrix: + - profile_name: client + output_name: client + csr: + CN: client + hosts: [] + key: + algo: ecdsa + size: 256 + names: + - C: RU + L: Moscow + O: Organization + OU: Organizational Unit + ST: Moscow region + etcd: + # cloudlabsinfra.etcd_cluster_certificates role related variables + etcd_cert_dir: /etc/ssl/private + # cloudlabsinfra.etcd_cluster role related variables + # client/server + etcd_trusted_ca_file: "{{ etcd_conf_dir }}/ca.pem" + etcd_key_file: "{{ etcd_conf_dir }}/server-key.pem" + etcd_cert_file: "{{ etcd_conf_dir }}/server.pem" + etcd_client_cert_auth: 'true' + # peer + etcd_peer_trusted_ca_file: "{{ etcd_trusted_ca_file }}" + etcd_peer_key_file: "{{ etcd_conf_dir }}/peer-key.pem" + etcd_peer_cert_file: "{{ etcd_conf_dir }}/peer.pem" + etcd_peer_client_cert_auth: 'true' + etcd_remote_cert_files: + - "{{ etcd_cert_dir }}/ca.pem" + - "{{ etcd_cert_dir }}/server-key.pem" + - "{{ etcd_cert_dir }}/server.pem" + - "{{ etcd_cert_dir }}/peer-key.pem" + - "{{ etcd_cert_dir }}/peer.pem" + etcd_cert_matrix: + - profile_name: server + output_name: server + csr: &default_csr + CN: "{{ inventory_hostname }}" + hosts: + - "{{ inventory_hostname }}" + - "{{ ansible_default_ipv4.address }}" + - "{{ etcd_frontend_name }}" + key: + algo: ecdsa + size: 256 + names: + - C: RU + L: Moscow + O: Organization + OU: Organizational Unit + ST: Moscow region + - profile_name: peer + output_name: peer + csr: + <<: *default_csr + - profile_name: client + output_name: client + csr: + <<: *default_csr + CN: client + hosts: [] +scenario: + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - side_effect + - verify diff --git a/molecule/verify-common.yml b/molecule/verify-common.yml index b3cb7fd..2f177cd 100644 --- a/molecule/verify-common.yml +++ b/molecule/verify-common.yml @@ -5,7 +5,7 @@ - name: Query health endpoint to show output # noqa: command-instead-of-module ansible.builtin.shell: | set -o pipefail && \ - curl -k https://localhost:6443/livez?verbose 2>/dev/null + curl -k --retry 5 https://localhost:6443/livez?verbose 2>/dev/null args: executable: /bin/bash register: curl_show diff --git a/requirements.yml b/requirements.yml index e644d3f..5939ac5 100644 --- a/requirements.yml +++ b/requirements.yml @@ -19,6 +19,12 @@ roles: - name: cloudlabsinfra.etcd_cluster_certificates version: v1.0.0 + - name: geerlingguy.helm + version: 1.0.1 + collections: - name: community.general version: 7.0.1 + + - name: kubernetes.core + version: 2.4.0