├── .gitignore ├── inventory ├── roles ├── gogs-ocp │ ├── templates │ │ ├── service_account.j2 │ │ ├── persistent_volume_claim.j2 │ │ ├── service.j2 │ │ ├── route.j2 │ │ ├── config_map.j2 │ │ └── deployment.j2 │ ├── meta │ │ └── main.yml │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── README.adoc ├── gitea-ocp │ ├── templates │ │ ├── service_account.yaml.j2 │ │ ├── service.yaml.j2 │ │ ├── persistent_volume_claim.yaml.j2 │ │ ├── route.yaml.j2 │ │ ├── deployment.yaml.j2 │ │ └── config_map.yaml.j2 │ ├── meta │ │ └── main.yml │ ├── defaults │ │ └── main.yml │ ├── README.adoc │ └── tasks │ │ └── main.yml ├── nexus-ocp │ ├── templates │ │ ├── service_account.j2 │ │ ├── persistent_volume_claim.j2 │ │ ├── service.j2 │ │ ├── registry_service.j2 │ │ ├── registry_route.j2 │ │ ├── route.j2 │ │ └── deployment.j2 │ ├── meta │ │ └── main.yml │ ├── README.adoc │ ├── tasks │ │ ├── main.yml │ │ └── setup.yml │ └── defaults │ │ └── main.yml ├── sonarqube-ocp │ ├── templates │ │ ├── service_account.j2 │ │ ├── persistent_volume_claim.j2 │ │ ├── service.j2 │ │ ├── route.j2 │ │ └── deployment.j2 │ ├── meta │ │ └── main.yml │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── README.adoc └── postgresql-ocp │ ├── templates │ ├── secret.yaml.j2 │ ├── service.yaml.j2 │ ├── persistent_volume_claim.yaml.j2 │ └── deployment.yaml.j2 │ ├── meta │ └── main.yml │ ├── defaults │ └── main.yml │ ├── tasks │ └── main.yml │ └── README.adoc ├── delete_nexus.sh ├── delete_gogs.sh ├── delete_gitea.sh ├── delete_sonarqube.sh ├── README.adoc ├── create_gogs.sh ├── create_gitea.sh ├── create_sonarqube.sh ├── .github ├── linters │ └── ansible.yml └── workflows │ └── linter.yml ├── playbooks ├── sonarqube.yaml ├── gogs.yaml ├── nexus.yaml ├── gitea.yml └── library │ └── k8s_status.py └── create_nexus.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.retry 2 | -------------------------------------------------------------------------------- /inventory: -------------------------------------------------------------------------------- 1 | # Run on Localhost 2 | [local] 3 | localhost ansible_connection=local 4 | -------------------------------------------------------------------------------- /roles/gogs-ocp/templates/service_account.j2: -------------------------------------------------------------------------------- 1 | kind: ServiceAccount 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gogs_name }}" 5 | namespace: "{{ _gogs_namespace }}" 6 | -------------------------------------------------------------------------------- /roles/gitea-ocp/templates/service_account.yaml.j2: -------------------------------------------------------------------------------- 1 | kind: ServiceAccount 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gitea_name }}" 5 | namespace: "{{ _gitea_namespace }}" 6 | -------------------------------------------------------------------------------- /roles/nexus-ocp/templates/service_account.j2: -------------------------------------------------------------------------------- 1 | kind: ServiceAccount 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ nexus_ocp_name }}" 5 | namespace: "{{ nexus_ocp_namespace }}" 6 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/templates/service_account.j2: -------------------------------------------------------------------------------- 1 | kind: ServiceAccount 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _sonarqube_name }}" 5 | namespace: "{{ _sonarqube_namespace }}" 6 | -------------------------------------------------------------------------------- /roles/gogs-ocp/templates/persistent_volume_claim.j2: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gogs_name }}-pvc" 5 | namespace: "{{ _gogs_namespace }}" 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: "{{ _gogs_volume_size }}" 12 | -------------------------------------------------------------------------------- /roles/nexus-ocp/templates/persistent_volume_claim.j2: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ nexus_ocp_name }}-pvc" 5 | namespace: "{{ nexus_ocp_namespace }}" 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: "{{ nexus_ocp_volume_size }}" 12 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/templates/persistent_volume_claim.j2: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _sonarqube_name }}-pvc" 5 | namespace: "{{ _sonarqube_namespace }}" 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: "{{ _sonarqube_volume_size }}" 12 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/templates/secret.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: "{{ _postgresql_name }}" 5 | namespace: "{{ _postgresql_namespace }}" 6 | stringData: 7 | database-name: "{{ _postgresql_database_name }}" 8 | database-user: "{{ _postgresql_user }}" 9 | database-password: "{{ _postgresql_password }}" 10 | -------------------------------------------------------------------------------- /delete_nexus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The following variables would from the ansible-operator 3 | # - meta.namespace 4 | # - meta.name (from the name of the CR) 5 | # Set to valid values when running the playbook outside of the operator 6 | 7 | ansible-playbook ./playbooks/nexus.yaml \ 8 | -e "nexus_ocp_state=absent" \ 9 | -e '{"meta": {"namespace":"xyz-nexus","name":"nexus"}}' 10 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Wolfgang Kulhanek 3 | description: Role to set up Sonarqube on an OpenShift (Kubernetes) Cluster 4 | company: Red Hat 5 | license: Apache 6 | min_ansible_version: 2.7 7 | galaxy_tags: 8 | - openshift 9 | - kubernetes 10 | - k8s 11 | - ansible-operator 12 | - sonarqube 13 | dependencies: [] 14 | -------------------------------------------------------------------------------- /roles/gogs-ocp/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Wolfgang Kulhanek 3 | description: Role to set up Gogs on an OpenShift (Kubernetes) Cluster 4 | company: Red Hat 5 | license: Apache 6 | min_ansible_version: 2.6 7 | galaxy_tags: 8 | - openshift 9 | - kubernetes 10 | - k8s 11 | - ansible-operator 12 | - gogs 13 | - git 14 | - repository 15 | dependencies: [] 16 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/templates/service.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: "{{ _postgresql_name }}" 5 | namespace: "{{ _postgresql_namespace }}" 6 | spec: 7 | ports: 8 | - name: postgresql 9 | port: 5432 10 | protocol: TCP 11 | targetPort: 5432 12 | selector: 13 | name: "{{ _postgresql_name }}" 14 | sessionAffinity: None 15 | type: ClusterIP 16 | -------------------------------------------------------------------------------- /roles/gitea-ocp/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Wolfgang Kulhanek 3 | description: Role to set up Gitea on an OpenShift (Kubernetes) Cluster 4 | company: Red Hat 5 | license: Apache 6 | min_ansible_version: 2.10 7 | galaxy_tags: 8 | - openshift 9 | - kubernetes 10 | - k8s 11 | - ansible-operator 12 | - gitea 13 | - git 14 | - repository 15 | dependencies: [] 16 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Wolfgang Kulhanek 3 | description: Role to set up PostgreSQL on an OpenShift (Kubernetes) Cluster 4 | company: Red Hat 5 | license: Apache 6 | min_ansible_version: 2.10 7 | galaxy_tags: 8 | - openshift 9 | - kubernetes 10 | - k8s 11 | - ansible-operator 12 | - postgresql 13 | - database 14 | dependencies: [] 15 | -------------------------------------------------------------------------------- /delete_gogs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The following variables would from the ansible-operator 3 | # - meta.namespace 4 | # - meta.name (from the name of the CR) 5 | # Set to valid values when running the playbook outside of the operator 6 | 7 | ansible-playbook ./playbooks/gogs.yaml \ 8 | -e "_postgresql_state=absent" \ 9 | -e "_gogs_state=absent" \ 10 | -e '{"meta": {"namespace":"xyz-gogs","name":"gogs"}}' 11 | -------------------------------------------------------------------------------- /roles/gogs-ocp/templates/service.j2: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gogs_name }}" 5 | namespace: "{{ _gogs_namespace }}" 6 | labels: 7 | app: "{{ _gogs_name }}" 8 | spec: 9 | selector: 10 | app: "{{ _gogs_name }}" 11 | ports: 12 | - name: gogs 13 | port: 3000 14 | protocol: TCP 15 | targetPort: 3000 16 | sessionAffinity: None 17 | type: ClusterIP 18 | -------------------------------------------------------------------------------- /delete_gitea.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The following variables would from the ansible-operator 3 | # - meta.namespace 4 | # - meta.name (from the name of the CR) 5 | # Set to valid values when running the playbook outside of the operator 6 | 7 | ansible-playbook ./playbooks/gitea.yaml \ 8 | -e "_postgresql_state=absent" \ 9 | -e "_gitea_state=absent" \ 10 | -e '{"meta": {"namespace":"xyz-gitea","name":"gitea"}}' 11 | -------------------------------------------------------------------------------- /roles/gitea-ocp/templates/service.yaml.j2: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gitea_name }}" 5 | namespace: "{{ _gitea_namespace }}" 6 | labels: 7 | app: "{{ _gitea_name }}" 8 | spec: 9 | selector: 10 | app: "{{ _gitea_name }}" 11 | ports: 12 | - name: gitea 13 | port: 3000 14 | protocol: TCP 15 | targetPort: 3000 16 | sessionAffinity: None 17 | type: ClusterIP 18 | -------------------------------------------------------------------------------- /roles/nexus-ocp/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Wolfgang Kulhanek, Jim Rigsbee 3 | description: Role to set up Nexus Artifact Repository 3.24+ on an OpenShift (Kubernetes) Cluster 4 | company: Red Hat 5 | license: Apache 6 | min_ansible_version: 2.9 7 | galaxy_tags: 8 | - openshift 9 | - kubernetes 10 | - k8s 11 | - ansible-operator 12 | - nexus 13 | - repository 14 | dependencies: [] 15 | -------------------------------------------------------------------------------- /roles/nexus-ocp/templates/service.j2: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ nexus_ocp_name }}" 5 | namespace: "{{ nexus_ocp_namespace }}" 6 | labels: 7 | app: "{{ nexus_ocp_name }}" 8 | spec: 9 | selector: 10 | app: "{{ nexus_ocp_name }}" 11 | ports: 12 | - name: "nexus-tcp" 13 | port: 8081 14 | protocol: TCP 15 | targetPort: 8081 16 | sessionAffinity: None 17 | type: ClusterIP 18 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/templates/service.j2: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _sonarqube_name }}" 5 | namespace: "{{ _sonarqube_namespace }}" 6 | labels: 7 | app: "{{ _sonarqube_name }}" 8 | spec: 9 | selector: 10 | app: "{{ _sonarqube_name }}" 11 | ports: 12 | - name: sonarqube 13 | port: 9000 14 | protocol: TCP 15 | targetPort: 9000 16 | sessionAffinity: None 17 | type: ClusterIP -------------------------------------------------------------------------------- /roles/nexus-ocp/templates/registry_service.j2: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ nexus_ocp_name }}-registry" 5 | namespace: "{{ nexus_ocp_namespace }}" 6 | labels: 7 | app: "{{ nexus_ocp_name }}" 8 | spec: 9 | selector: 10 | app: "{{ nexus_ocp_name }}" 11 | ports: 12 | - name: "nexus-registry-tcp" 13 | port: 5000 14 | protocol: TCP 15 | targetPort: 5000 16 | sessionAffinity: None 17 | type: ClusterIP 18 | -------------------------------------------------------------------------------- /roles/gitea-ocp/templates/persistent_volume_claim.yaml.j2: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gitea_name }}-pvc" 5 | namespace: "{{ _gitea_namespace }}" 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: "{{ _gitea_volume_size }}" 12 | {% if _gitea_volume_storage_class | length > 0 %} 13 | storageClassName: "{{ _gitea_volume_storage_class }}" 14 | {% endif %} 15 | volumeMode: Filesystem -------------------------------------------------------------------------------- /delete_sonarqube.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The following variables would from the ansible-operator 3 | # - meta.namespace 4 | # - meta.name (from the name of the CR) 5 | # Set to valid values when running the playbook outside of the operator 6 | 7 | ansible-playbook ./playbooks/sonarqube.yaml \ 8 | -e "_postgresql_state=absent" \ 9 | -e "_sonarqube_state=absent" \ 10 | -e "postgresql_volume_size=2Gi" \ 11 | -e "sonarqube_volume_size=3Gi" \ 12 | -e '{"meta": {"namespace":"xyz-sonarqube","name":"sonarqube"}}' 13 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/templates/persistent_volume_claim.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: "{{ _postgresql_name }}-pvc" 5 | namespace: "{{ _postgresql_namespace }}" 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: "{{ _postgresql_volume_size }}" 12 | {% if _postgresql_volume_storage_class | length > 0 %} 13 | storageClassName: "{{ _postgresql_volume_storage_class }}" 14 | {% endif %} 15 | volumeMode: Filesystem -------------------------------------------------------------------------------- /roles/gogs-ocp/templates/route.j2: -------------------------------------------------------------------------------- 1 | kind: Route 2 | apiVersion: route.openshift.io/v1 3 | metadata: 4 | name: "{{ _gogs_name }}" 5 | namespace: "{{ _gogs_namespace }}" 6 | labels: 7 | app: "{{ _gogs_name }}" 8 | spec: 9 | to: 10 | kind: Service 11 | name: "{{ _gogs_name }}" 12 | {% if _gogs_route|d("") != "" %} 13 | host: "{{ _gogs_route }}" 14 | {% endif %} 15 | {% if _gogs_ssl|d(False)|bool %} 16 | tls: 17 | insecureEdgeTerminationPolicy: Redirect 18 | termination: edge 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Ansible Roles for Ansible Operators 2 | 3 | This repository contains roles that can be used to set up various applications on Red Hat OpenShift Container Platform. The primary purpose of these roles is to be used in Operators to be deployed to a cluster. 4 | 5 | Available roles: 6 | 7 | * PostgreSQL (used by other applications) 8 | * Gogs: Github compatible repository 9 | * Gitea: Github compatible repository 10 | * SonarQube: Code Analysis tool 11 | * Nexus: Artifact Manager, Proxy and Container Registry 12 | -------------------------------------------------------------------------------- /roles/gitea-ocp/templates/route.yaml.j2: -------------------------------------------------------------------------------- 1 | kind: Route 2 | apiVersion: route.openshift.io/v1 3 | metadata: 4 | name: "{{ _gitea_name }}" 5 | namespace: "{{ _gitea_namespace }}" 6 | labels: 7 | app: "{{ _gitea_name }}" 8 | spec: 9 | to: 10 | kind: Service 11 | name: "{{ _gitea_name }}" 12 | {% if _gitea_route|d("") != "" %} 13 | host: "{{ _gitea_route }}" 14 | {% endif %} 15 | {% if _gitea_ssl|d(False)|bool %} 16 | tls: 17 | insecureEdgeTerminationPolicy: Redirect 18 | termination: edge 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/templates/route.j2: -------------------------------------------------------------------------------- 1 | kind: Route 2 | apiVersion: route.openshift.io/v1 3 | metadata: 4 | name: "{{ _sonarqube_name }}" 5 | namespace: "{{ _sonarqube_namespace }}" 6 | labels: 7 | app: "{{ _sonarqube_name }}" 8 | spec: 9 | to: 10 | kind: Service 11 | name: "{{ _sonarqube_name }}" 12 | {% if _sonarqube_route|d("") != "" %} 13 | host: "{{ _sonarqube_route }}" 14 | {% endif %} 15 | {% if _sonarqube_ssl|d(False)|bool %} 16 | tls: 17 | insecureEdgeTerminationPolicy: Redirect 18 | termination: edge 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /create_gogs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The playbook expects the following variables to be set 3 | # - postgresql_volume_size 4 | # - gogs_volume_size 5 | # - gogs_ssl 6 | 7 | # The following variables would from the ansible-operator 8 | # - meta.namespace 9 | # - meta.name (from the name of the CR) 10 | # Set to valid values when running the playbook outside of the operator 11 | 12 | ansible-playbook ./playbooks/gogs.yaml \ 13 | -e "postgresql_volume_size=2Gi" \ 14 | -e "gogs_volume_size=3Gi" \ 15 | -e "gogs_ssl=True" \ 16 | -e "gogs_image_tag=latest" \ 17 | -e '{"meta": {"namespace":"xyz-gogs","name":"gogs"}}' 18 | -------------------------------------------------------------------------------- /roles/nexus-ocp/templates/registry_route.j2: -------------------------------------------------------------------------------- 1 | kind: Route 2 | apiVersion: route.openshift.io/v1 3 | metadata: 4 | annotations: 5 | console.alpha.openshift.io/overview-app-route: "false" 6 | name: "{{ nexus_ocp_name }}-registry" 7 | namespace: "{{ nexus_ocp_namespace }}" 8 | labels: 9 | app: "{{ nexus_ocp_name }}" 10 | spec: 11 | {% if nexus_ocp_registry_route|d("") != "" %} 12 | host: "{{ nexus_ocp_registry_route }}" 13 | {% endif %} 14 | to: 15 | kind: Service 16 | name: "{{ nexus_ocp_name }}-registry" 17 | tls: 18 | insecureEdgeTerminationPolicy: Redirect 19 | termination: edge 20 | -------------------------------------------------------------------------------- /roles/nexus-ocp/templates/route.j2: -------------------------------------------------------------------------------- 1 | kind: Route 2 | apiVersion: route.openshift.io/v1 3 | metadata: 4 | annotations: 5 | console.alpha.openshift.io/overview-app-route: "true" 6 | name: "{{ nexus_ocp_name }}" 7 | namespace: "{{ nexus_ocp_namespace }}" 8 | labels: 9 | app: "{{ nexus_ocp_name }}" 10 | spec: 11 | {% if nexus_ocp_route|d("") != "" %} 12 | host: "{{ nexus_ocp_route }}" 13 | {% endif %} 14 | to: 15 | kind: Service 16 | name: "{{ nexus_ocp_name }}" 17 | {% if nexus_ocp_ssl|d(False)|bool %} 18 | tls: 19 | insecureEdgeTerminationPolicy: Redirect 20 | termination: edge 21 | {% endif %} 22 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for postgresql-ocp 3 | _postgresql_state: present 4 | _postgresql_namespace: postgresql 5 | _postgresql_name: postgresql 6 | 7 | _postgresql_database_name: postgresql 8 | _postgresql_user: postgresql 9 | _postgresql_password: postgresql 10 | _postgresql_volume_size: 1Gi 11 | _postgresql_volume_storage_class: "" 12 | _postgresql_memory_request: 512Mi 13 | _postgresql_memory_limit: 512Mi 14 | _postgresql_cpu_request: 200m 15 | _postgresql_cpu_limit: 500m 16 | 17 | _postgresql_wait_for_init: true 18 | _postgresql_image: registry.redhat.io/rhel8/postgresql-12 19 | _postgresql_image_tag: latest 20 | -------------------------------------------------------------------------------- /create_gitea.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The playbook expects the following variables to be set 3 | # - postgresql_volume_size 4 | # - gogs_volume_size 5 | # - gogs_ssl 6 | 7 | # The following variables would from the ansible-operator 8 | # - meta.namespace 9 | # - meta.name (from the name of the CR) 10 | # Set to valid values when running the playbook outside of the operator 11 | 12 | ansible-playbook ./playbooks/gitea.yaml \ 13 | -e "postgresql_volume_size=2Gi" \ 14 | -e "gitea_volume_size=3Gi" \ 15 | -e "gitea_ssl=True" \ 16 | -e "gitea_image_tag=latest" \ 17 | -e "gitea_route=gitea.apps.shared-dev.dev.openshift.opentlc.com" \ 18 | -e '{"meta": {"namespace":"xyz-gitea","name":"gitea"}}' 19 | -------------------------------------------------------------------------------- /create_sonarqube.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The playbook expects the following variables to be set 3 | # - postgresql_volume_size 4 | # - sonarqube_volume_size 5 | # - sonarqube_ssl 6 | 7 | # The following variables would from the ansible-operator 8 | # - meta.namespace 9 | # - meta.name (from the name of the CR) 10 | # Set to valid values when running the playbook outside of the operator 11 | 12 | ansible-playbook ./playbooks/sonarqube.yaml \ 13 | -e "postgresql_volume_size=2Gi" \ 14 | -e "sonarqube_volume_size=3Gi" \ 15 | -e "sonarqube_ssl=True" \ 16 | -e "sonarqube_image_tag=7.9.1" \ 17 | -e "sonarqube_route=sonarqube.apps.shared-dev.dev.openshift.opentlc.com" \ 18 | -e '{"meta": {"namespace":"xyz-sonarqube","name":"sonarqube"}}' 19 | -------------------------------------------------------------------------------- /roles/gogs-ocp/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for gogs-ocp 3 | _gogs_state: present 4 | _gogs_namespace: gogs 5 | _gogs_name: gogs 6 | 7 | _gogs_ssl: False 8 | _gogs_postgresql_database_name: postgresql 9 | _gogs_postgresql_service_name: postgresql 10 | _gogs_postgresql_user: postgresql 11 | _gogs_postgresql_password: postgresql 12 | _gogs_volume_size: 1Gi 13 | _gogs_memory_request: 512Mi 14 | _gogs_memory_limit: 512Mi 15 | _gogs_cpu_request: 200m 16 | _gogs_cpu_limit: 500m 17 | _gogs_wait_for_init: true 18 | _gogs_image: quay.io/gpte-devops-automation/gogs 19 | _gogs_image_tag: latest 20 | 21 | # Set to a valid route for the cluster to make a nicer route than svc-project.apps. Leave empty for default route 22 | _gogs_route: "" 23 | 24 | # Internal variable. Do not change 25 | _gogs_actual_route: "" 26 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for sonarqube-ocp 3 | _sonarqube_state: present 4 | _sonarqube_namespace: sonarqube 5 | _sonarqube_name: sonarqube 6 | 7 | _sonarqube_ssl: False 8 | _sonarqube_postgresql_service_name: postgresql 9 | _sonarqube_postgresql_database_name: sonar 10 | _sonarqube_postgresql_user: sonar 11 | _sonarqube_postgresql_password: sonar 12 | _sonarqube_volume_size: 1Gi 13 | _sonarqube_memory_request: 2Gi 14 | _sonarqube_memory_limit: 3Gi 15 | _sonarqube_cpu_request: 1 16 | _sonarqube_cpu_limit: 2 17 | _sonarqube_wait_for_init: true 18 | _sonarqube_image: quay.io/gpte-devops-automation/sonarqube 19 | _sonarqube_image_tag: latest 20 | 21 | # Set to a valid route for the cluster to make a nicer route than svc-project.apps. Leave empty for default route 22 | _sonarqube_route: "" 23 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for postgresql-ocp 3 | 4 | - name: Set OpenShift Objects for Postgresql to {{ _postgresql_state }} 5 | community.kubernetes.k8s: 6 | state: "{{ _postgresql_state }}" 7 | merge_type: 8 | - strategic-merge 9 | - merge 10 | definition: "{{ lookup('template', item ) | from_yaml }}" 11 | loop: 12 | - secret.yaml.j2 13 | - service.yaml.j2 14 | - persistent_volume_claim.yaml.j2 15 | - deployment.yaml.j2 16 | 17 | - name: Wait until application is available 18 | when: 19 | - _postgresql_state == "present" 20 | - _postgresql_wait_for_init|bool 21 | community.kubernetes.k8s_info: 22 | api_version: apps/v1 23 | kind: Deployment 24 | name: "{{ _postgresql_name }}" 25 | namespace: "{{ _postgresql_namespace }}" 26 | register: r_deployment 27 | until: 28 | - r_deployment.resources[0].status.availableReplicas is defined 29 | - r_deployment.resources[0].status.availableReplicas == 1 30 | retries: 50 31 | delay: 10 32 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tasks file for SonarQube 3 | - name: Set OpenShift objects for SonarQube to {{ _sonarqube_state }} 4 | k8s: 5 | state: "{{ _sonarqube_state }}" 6 | merge_type: 7 | - strategic-merge 8 | - merge 9 | definition: "{{ lookup('template', item ) | from_yaml }}" 10 | loop: 11 | - ./templates/service_account.j2 12 | - ./templates/persistent_volume_claim.j2 13 | - ./templates/service.j2 14 | - ./templates/route.j2 15 | - ./templates/deployment.j2 16 | 17 | - name: Wait until application is available 18 | when: 19 | - _sonarqube_state == "present" 20 | - _sonarqube_wait_for_init|bool 21 | k8s_facts: 22 | api_version: apps/v1 23 | kind: Deployment 24 | name: "{{ _sonarqube_name }}" 25 | namespace: "{{ _sonarqube_namespace }}" 26 | register: r_deployment 27 | until: 28 | - r_deployment.resources[0].status.availableReplicas is defined 29 | - r_deployment.resources[0].status.availableReplicas == 1 30 | retries: 50 31 | delay: 10 32 | ignore_errors: yes 33 | -------------------------------------------------------------------------------- /roles/gogs-ocp/templates/config_map.j2: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gogs_name }}-config" 5 | namespace: "{{ _gogs_namespace }}" 6 | data: 7 | app.ini: | 8 | APP_NAME = {{ _gogs_name }} 9 | RUN_MODE = prod 10 | RUN_USER = gogs 11 | 12 | [database] 13 | DB_TYPE = postgres 14 | HOST = "{{ _gogs_postgresql_service_name }}:5432" 15 | NAME = "{{ _gogs_postgresql_database_name }}" 16 | USER = "{{ _gogs_postgresql_user }}" 17 | PASSWD = "{{ _gogs_postgresql_password }}" 18 | SSL_MODE = disable 19 | 20 | [repository] 21 | ROOT = /gogs-repositories 22 | 23 | [server] 24 | ROOT_URL="{{ 'http' if not _gogs_ssl|d(False)|bool else 'https' }}://{{ _gogs_actual_route }}/" 25 | 26 | [security] 27 | INSTALL_LOCK = true 28 | 29 | [mailer] 30 | ENABLED = false 31 | 32 | [service] 33 | ENABLE_CAPTCHA = false 34 | REGISTER_EMAIL_CONFIRM = false 35 | ENABLE_NOTIFY_MAIL = false 36 | DISABLE_REGISTRATION = false 37 | REQUIRE_SIGNIN_VIEW = false 38 | 39 | [picture] 40 | DISABLE_GRAVATAR = false 41 | ENABLE_FEDERATED_AVATAR = true 42 | 43 | [webhook] 44 | SKIP_TLS_VERIFY = true 45 | -------------------------------------------------------------------------------- /.github/linters/ansible.yml: -------------------------------------------------------------------------------- 1 | ########################## 2 | ########################## 3 | ## Ansible Linter rules ## 4 | ########################## 5 | ########################## 6 | 7 | ############################# 8 | # Exclude paths from linter # 9 | ############################# 10 | #exclude_paths: 11 | 12 | ######################## 13 | # Make output parsable # 14 | ######################## 15 | parseable: true 16 | 17 | ####################### 18 | # Set output to quiet # 19 | ####################### 20 | quiet: true 21 | 22 | ##################### 23 | # Path to rules dir # 24 | ##################### 25 | #rulesdir: 26 | 27 | ################ 28 | # Tags to skip # 29 | ################ 30 | skip_list: 31 | - '602' # Allow compare to empty string 32 | - '204' # Allow string length greater that 160 chars 33 | - '301' # False positives for running command shells 34 | - '303' # Allow git commands for push add, etc... 35 | - '305' # Allow use of shell when you want 36 | - '503' # Allow step to run like handler 37 | 38 | ################## 39 | # Tags to follow # 40 | ################## 41 | #tags: 42 | 43 | ############# 44 | # Use rules # 45 | ############# 46 | use_default_rules: true 47 | 48 | ################# 49 | # Set verbosity # 50 | ################# 51 | verbosity: 1 52 | 53 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ########################### 3 | ########################### 4 | ## Linter GitHub Actions ## 5 | ########################### 6 | ########################### 7 | name: Lint Code Base 8 | 9 | # 10 | # Documentation: 11 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 12 | # 13 | 14 | ############################# 15 | # Start the job on all push # 16 | ############################# 17 | on: 18 | push: 19 | branches-ignore: 20 | - 'master' 21 | 22 | ############### 23 | # Set the Job # 24 | ############### 25 | jobs: 26 | build: 27 | # Name the Job 28 | name: Lint Code Base 29 | # Set the agent to run on 30 | runs-on: ubuntu-latest 31 | 32 | ################## 33 | # Load all steps # 34 | ################## 35 | steps: 36 | ########################## 37 | # Checkout the code base # 38 | ########################## 39 | - name: Checkout Code 40 | uses: actions/checkout@v2 41 | 42 | ################################ 43 | # Run Linter against code base # 44 | ################################ 45 | - name: Lint Code Base 46 | uses: docker://github/super-linter:v2.1.0 47 | env: 48 | VALIDATE_ALL_CODEBASE: true 49 | VALIDATE_ANSIBLE: true 50 | ... 51 | -------------------------------------------------------------------------------- /roles/gogs-ocp/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for Gogs 3 | - name: Set OpenShift Objects for Gogs to {{ _gogs_state }} 4 | k8s: 5 | state: "{{ _gogs_state }}" 6 | merge_type: 7 | - strategic-merge 8 | - merge 9 | definition: "{{ lookup('template', item ) | from_yaml }}" 10 | loop: 11 | - ./templates/service_account.j2 12 | - ./templates/service.j2 13 | - ./templates/route.j2 14 | - ./templates/persistent_volume_claim.j2 15 | 16 | # Get gogs Route 17 | - name: Get Gogs Route Hostname 18 | when: 19 | - _gogs_state == "present" 20 | k8s_facts: 21 | api_version: route.openshift.io/v1 22 | kind: Route 23 | name: "{{ _gogs_name }}" 24 | namespace: "{{ _gogs_namespace }}" 25 | register: r_route 26 | 27 | - name: Store Gogs Route Hostname 28 | when: 29 | - _gogs_state == "present" 30 | set_fact: 31 | _gogs_actual_route: "{{ r_route.resources[0].status.ingress[0].host }}" 32 | 33 | - name: Set Route dependant OpenShift Objects for Gogs to {{ _gogs_state }} 34 | k8s: 35 | state: "{{ _gogs_state }}" 36 | merge_type: 37 | - strategic-merge 38 | - merge 39 | definition: "{{ lookup('template', item ) | from_yaml }}" 40 | loop: 41 | - ./templates/config_map.j2 42 | - ./templates/deployment.j2 43 | 44 | - name: Wait until application is available 45 | when: 46 | - _gogs_state == "present" 47 | - _gogs_wait_for_init|bool 48 | k8s_facts: 49 | api_version: apps/v1 50 | kind: Deployment 51 | name: "{{ _gogs_name }}" 52 | namespace: "{{ _gogs_namespace }}" 53 | register: r_deployment 54 | until: 55 | - r_deployment.resources[0].status.availableReplicas is defined 56 | - r_deployment.resources[0].status.availableReplicas == 1 57 | retries: 50 58 | delay: 10 59 | ignore_errors: yes 60 | -------------------------------------------------------------------------------- /playbooks/sonarqube.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Persistent Sonarqube deployment playbook. 3 | # 4 | # The Playbook expects the following variables to be set in the CR: 5 | # (Note that Camel case gets converted by the ansible-operator to Snake case) 6 | # - PostgresqlVolumeSize 7 | # - SonarqubeVolumeSize 8 | # - SonarqubeSsl 9 | # The following variables come from the ansible-operator 10 | # - meta.namespace 11 | # - meta.name (from the name of the CR) 12 | 13 | - hosts: localhost 14 | gather_facts: no 15 | tasks: 16 | - name: Set up PostgreSQL 17 | include_role: 18 | name: ./roles/postgresql-ocp 19 | vars: 20 | _postgresql_namespace: "{{ meta.namespace }}" 21 | _postgresql_name: "postgresql-{{ meta.name }}" 22 | _postgresql_database_name: "sonardb" 23 | _postgresql_user: "sonar" 24 | _postgresql_password: "sonar" 25 | _postgresql_volume_size: "{{ postgresql_volume_size }}" 26 | _postgresql_image: "{{ postgresql_image | d('registry.redhat.io/rhscl/postgresql-10-rhel7') }}" 27 | _postgresql_image_tag: "{{ postgresql_image_tag | d('latest') }}" 28 | 29 | - name: Set up Sonarqube 30 | include_role: 31 | name: ./roles/sonarqube-ocp 32 | vars: 33 | _sonarqube_namespace: "{{ meta.namespace }}" 34 | _sonarqube_name: "{{ sonarqube_service_name | d( meta.name) }}" 35 | _sonarqube_ssl: "{{ sonarqube_ssl|d(False)|bool }}" 36 | _sonarqube_route: "{{ sonarqube_route | d('') }}" 37 | _sonarqube_image_tag: "{{ sonarqube_image_tag | d('latest') }}" 38 | _sonarqube_volume_size: "{{ sonarqube_volume_size}}" 39 | _sonarqube_postgresql_service_name: "postgresql-{{ meta.name }}" 40 | _sonarqube_postgresql_database_name: sonardb 41 | _sonarqube_postgresql_user: sonar 42 | _sonarqube_postgresql_password: sonar 43 | -------------------------------------------------------------------------------- /roles/nexus-ocp/templates/deployment.j2: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: "{{ nexus_ocp_name }}" 5 | namespace: "{{ nexus_ocp_namespace }}" 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: "{{ nexus_ocp_name }}" 11 | strategy: 12 | type: Recreate 13 | template: 14 | metadata: 15 | labels: 16 | app: "{{ nexus_ocp_name }}" 17 | spec: 18 | serviceAccountName: "{{ nexus_ocp_name }}" 19 | containers: 20 | - name: nexus 21 | image: "{{ nexus_ocp_image }}:{{ nexus_ocp_image_tag }}" 22 | imagePullPolicy: Always 23 | ports: 24 | - containerPort: 8081 25 | protocol: TCP 26 | - containerPort: 5000 27 | protocol: TCP 28 | readinessProbe: 29 | httpGet: 30 | path: / 31 | port: 8081 32 | scheme: HTTP 33 | initialDelaySeconds: 60 34 | timeoutSeconds: 1 35 | periodSeconds: 20 36 | successThreshold: 1 37 | failureThreshold: 3 38 | livenessProbe: 39 | httpGet: 40 | path: / 41 | port: 8081 42 | scheme: HTTP 43 | initialDelaySeconds: 60 44 | timeoutSeconds: 1 45 | periodSeconds: 10 46 | successThreshold: 1 47 | failureThreshold: 3 48 | resources: 49 | requests: 50 | cpu: "{{ nexus_ocp_cpu_request }}" 51 | memory: "{{ nexus_ocp_memory_request}}" 52 | limits: 53 | cpu: "{{ nexus_ocp_cpu_limit}}" 54 | memory: "{{ nexus_ocp_memory_limit }}" 55 | volumeMounts: 56 | - name: nexus-data 57 | mountPath: /nexus-data 58 | volumes: 59 | - name: nexus-data 60 | persistentVolumeClaim: 61 | claimName: "{{ nexus_ocp_name }}-pvc" -------------------------------------------------------------------------------- /playbooks/gogs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Persistent Gogs deployment playbook. 3 | # 4 | # The Playbook expects the following variables to be set in the CR: 5 | # (Note that Camel case gets converted by the ansible-operator to Snake case) 6 | # - PostgresqlVolumeSize 7 | # - GogsVolumeSize 8 | # - GogsSSL 9 | # The following variables come from the ansible-operator 10 | # - meta.namespace 11 | # - meta.name (from the name of the CR) 12 | 13 | - hosts: localhost 14 | gather_facts: no 15 | tasks: 16 | - name: Set up PostgreSQL 17 | include_role: 18 | name: ./roles/postgresql-ocp 19 | vars: 20 | _postgresql_namespace: "{{ meta.namespace }}" 21 | _postgresql_name: "postgresql-gogs-{{ meta.name }}" 22 | _postgresql_database_name: "gogsdb" 23 | _postgresql_user: "gogsuser" 24 | _postgresql_password: "gogspassword" 25 | _postgresql_volume_size: "{{ postgresql_volume_size|d('4Gi') }}" 26 | _postgresql_image: "{{ postgresql_image|d('registry.redhat.io/rhscl/postgresql-10-rhel7') }}" 27 | _postgresql_image_tag: "{{ postgresql_image_tag|d('latest') }}" 28 | 29 | - name: Set Gogs Service name to default value 30 | set_fact: 31 | gogs_service_name: "gogs-{{ meta.name }}" 32 | when: 33 | gogs_service_name is not defined 34 | - name: Set up Gogs 35 | include_role: 36 | name: ./roles/gogs-ocp 37 | vars: 38 | _gogs_namespace: "{{ meta.namespace }}" 39 | _gogs_name: "{{ gogs_service_name }}" 40 | _gogs_ssl: "{{ gogs_ssl|d(False)|bool }}" 41 | _gogs_route: "{{ gogs_route | d('') }}" 42 | _gogs_image_tag: "{{ gogs_image_tag | d('latest') }}" 43 | _gogs_volume_size: "{{ gogs_volume_size|d('4Gi') }}" 44 | _gogs_postgresql_service_name: "postgresql-gogs-{{ meta.name }}" 45 | _gogs_postgresql_database_name: gogsdb 46 | _gogs_postgresql_user: gogsuser 47 | _gogs_postgresql_password: gogspassword 48 | -------------------------------------------------------------------------------- /roles/nexus-ocp/README.adoc: -------------------------------------------------------------------------------- 1 | = nexus-ocp 2 | 3 | == Requirements 4 | 5 | This role is designed to set up a Nexus artifact manager on an OpenShift cluster. The intended use for this role is in an Operator. The role does not set up Kubernetes controllers (like Deployments or ReplicaSets) but creates the Nexus Pod directly - this is the preferred approach to be used by an Operator. 6 | 7 | == Role Variables 8 | 9 | [cols="2,1,1,4",options="header"] 10 | |==== 11 | |Variable Name|Default|Required|Description 12 | |nexus_ocp_namespace|nexus|Yes|Project Name to install Nexus into 13 | |nexus_ocp_name|nexus3|No|Name of the Nexus service 14 | |nexus_ocp_ssl|False|No|Set up SSL on the Nexus Route 15 | |nexus_ocp_volume_size|1Gi|No|Size of Persistent Volume to be created 16 | |nexus_ocp_memory_request|512Mi|No|Minimum Memory Requirement 17 | |nexus_ocp_memory_limit|512Mi|No|Maximum Memory Requirement 18 | |nexus_ocp_cpu_request|200m|No|Minimum CPU Requirement 19 | |nexus_ocp_cpu_limit|500m|No|Maximum CPU Requirement 20 | |nexus_ocp_wait_for_init|true|No|Wait for the Nexs pod to be running and ready 21 | |nexus_ocp_image|docker.io/sonatype/nexus3|Yes|Nexus image for Gitea 22 | |nexus_ocp_image_tag|latest|Yes|Tag for Nexus container image 23 | |nexus_ocp_state|present|No|`present` to install, `absent` to remove 24 | |==== 25 | 26 | == Dependencies 27 | 28 | * k8s module. 29 | * Working .kube/config configuration. 30 | * The Project/Namespace must exist 31 | 32 | == Example Playbook 33 | 34 | [source,yaml] 35 | ---- 36 | - hosts: localhost 37 | gather_facts: no 38 | tasks: 39 | - name: Set up Nexus 40 | include_role: 41 | name: ./roles/nexus-ocp 42 | vars: 43 | nexus_ocp_namespace: "nexus" 44 | nexus_ocp_name: "nexus" 45 | nexus_ocp_volume_size: "5Gi" 46 | nexus_ocp_ssl: "True" 47 | --- 48 | 49 | == License 50 | 51 | BSD 52 | 53 | == Credits 54 | 55 | Some portions of this code came from: https://github.com/savoirfairelinux/ansible-nexus3-oss 56 | -------------------------------------------------------------------------------- /roles/gitea-ocp/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for gitea-ocp 3 | _gitea_state: present 4 | _gitea_namespace: gitea 5 | _gitea_name: gitea 6 | 7 | _gitea_ssl: False 8 | _gitea_port: 3000 9 | _gitea_postgresql_database_name: postgresql 10 | _gitea_postgresql_port: 5432 11 | _gitea_postgresql_service_name: postgresql 12 | _gitea_postgresql_user: postgresql 13 | _gitea_postgresql_password: postgresql 14 | _gitea_volume_size: 1Gi 15 | _gitea_volume_storage_class: "" 16 | _gitea_memory_request: 1Gi 17 | _gitea_memory_limit: 1Gi 18 | _gitea_cpu_request: 200m 19 | _gitea_cpu_limit: 500m 20 | _gitea_image: quay.io/gpte-devops-automation/gitea 21 | _gitea_image_tag: latest 22 | 23 | # If Admin user is specified the Admin User will 24 | # be created. If not, or empty no admin user will be created 25 | # If no password is specified a password with specified length will be created 26 | _gitea_admin_user: opentlc-mgr 27 | _gitea_admin_password: "" 28 | _gitea_admin_password_length: 16 29 | _gitea_admin_email: "opentlc-mgr@open.redhat.com" 30 | 31 | # Gitea Settings 32 | _gitea_http_port: 3000 33 | _gitea_ssh_port: 2022 34 | _gitea_disable_ssh: false 35 | _gitea_start_ssh_server: true 36 | _gitea_disable_registration: false 37 | _gitea_enable_captcha: false 38 | _gitea_allow_create_organization: true 39 | _gitea_allow_local_network_migration: false 40 | 41 | # Gitea e-mail Setup 42 | _gitea_mailer_enabled: false 43 | _gitea_mailer_from: gitea@mydomain.com 44 | _gitea_mailer_type: smtp 45 | _gitea_mailer_host: mail.mydomain.com:587 46 | _gitea_mailer_tls: true 47 | _gitea_mailer_user: gitea@mydomain.com 48 | _gitea_mailer_password: password 49 | _gitea_mailer_helo_hostname: "" 50 | 51 | _gitea_register_email_confirm: false 52 | _gitea_enable_notify_mail: false 53 | 54 | # Set to a valid route for the cluster to make a nicer route than svc-project.apps. Leave empty for default route 55 | _gitea_route: "" 56 | 57 | # Internal variable. Do not change 58 | _gitea_actual_route: "" 59 | 60 | # Actual Gitea Admin Password 61 | _gitea_actual_admin_password: "" -------------------------------------------------------------------------------- /roles/gogs-ocp/templates/deployment.j2: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: "{{ _gogs_name }}" 5 | namespace: "{{ _gogs_namespace }}" 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: "{{ _gogs_name }}" 11 | strategy: 12 | type: Recreate 13 | template: 14 | metadata: 15 | labels: 16 | app: "{{ _gogs_name }}" 17 | spec: 18 | serviceAccountName: "{{ _gogs_name }}" 19 | containers: 20 | - name: gogs 21 | image: "{{ _gogs_image }}:{{ _gogs_image_tag }}" 22 | imagePullPolicy: Always 23 | ports: 24 | - containerPort: 3000 25 | protocol: TCP 26 | readinessProbe: 27 | httpGet: 28 | path: / 29 | port: 3000 30 | scheme: HTTP 31 | initialDelaySeconds: 10 32 | timeoutSeconds: 1 33 | periodSeconds: 20 34 | successThreshold: 1 35 | failureThreshold: 3 36 | livenessProbe: 37 | httpGet: 38 | path: / 39 | port: 3000 40 | scheme: HTTP 41 | initialDelaySeconds: 10 42 | timeoutSeconds: 1 43 | periodSeconds: 10 44 | successThreshold: 1 45 | failureThreshold: 3 46 | resources: 47 | requests: 48 | cpu: "{{ _gogs_cpu_request }}" 49 | memory: "{{ _gogs_memory_request}}" 50 | limits: 51 | cpu: "{{ _gogs_cpu_limit}}" 52 | memory: "{{ _gogs_memory_limit }}" 53 | volumeMounts: 54 | - name: gogs-repositories 55 | mountPath: /gogs-repositories 56 | - name: gogs-config 57 | mountPath: /opt/gogs/custom/conf 58 | volumes: 59 | - name: gogs-repositories 60 | persistentVolumeClaim: 61 | claimName: "{{ _gogs_name }}-pvc" 62 | - name: gogs-config 63 | configMap: 64 | name: "{{ _gogs_name }}-config" 65 | items: 66 | - key: app.ini 67 | path: app.ini -------------------------------------------------------------------------------- /roles/gitea-ocp/templates/deployment.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: "{{ _gitea_name }}" 5 | namespace: "{{ _gitea_namespace }}" 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: "{{ _gitea_name }}" 11 | strategy: 12 | type: Recreate 13 | template: 14 | metadata: 15 | labels: 16 | app: "{{ _gitea_name }}" 17 | spec: 18 | serviceAccountName: "{{ _gitea_name }}" 19 | containers: 20 | - name: gitea 21 | image: "{{ _gitea_image }}:{{ _gitea_image_tag }}" 22 | imagePullPolicy: Always 23 | ports: 24 | - containerPort: 3000 25 | protocol: TCP 26 | readinessProbe: 27 | httpGet: 28 | path: / 29 | port: 3000 30 | scheme: HTTP 31 | initialDelaySeconds: 10 32 | timeoutSeconds: 1 33 | periodSeconds: 20 34 | successThreshold: 1 35 | failureThreshold: 3 36 | livenessProbe: 37 | httpGet: 38 | path: / 39 | port: 3000 40 | scheme: HTTP 41 | initialDelaySeconds: 10 42 | timeoutSeconds: 1 43 | periodSeconds: 10 44 | successThreshold: 1 45 | failureThreshold: 3 46 | resources: 47 | requests: 48 | cpu: "{{ _gitea_cpu_request }}" 49 | memory: "{{ _gitea_memory_request}}" 50 | limits: 51 | cpu: "{{ _gitea_cpu_limit}}" 52 | memory: "{{ _gitea_memory_limit }}" 53 | volumeMounts: 54 | - name: gitea-repositories 55 | mountPath: /gitea-repositories 56 | - name: gitea-config 57 | mountPath: /home/gitea/conf 58 | volumes: 59 | - name: gitea-repositories 60 | persistentVolumeClaim: 61 | claimName: "{{ _gitea_name }}-pvc" 62 | - name: gitea-config 63 | configMap: 64 | name: "{{ _gitea_name }}-config" 65 | items: 66 | - key: app.ini 67 | path: app.ini -------------------------------------------------------------------------------- /create_nexus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The Shell script can be used to provision a Nexus instance. 3 | # Any variable can be set using -e 4 | # All variables have sensible defaults if not specified. 5 | 6 | # The following variables would from the ansible-operator 7 | # - meta.namespace 8 | # - meta.name (from the name of the CR) 9 | # Set to valid values when calling the playbook outside of the operator 10 | 11 | ansible-playbook ./playbooks/nexus.yaml \ 12 | -e '{"meta": {"namespace":"xyz-nexus","name":"nexus"}}' \ 13 | -e "nexus_volume_size=5Gi" \ 14 | -e "nexus_ssl=True" \ 15 | -e "nexus_image=docker.io/sonatype/nexus3" \ 16 | -e "nexus_image_tag='3.24.0'" \ 17 | -e "nexus_new_admin_password=redhat" \ 18 | -e 'nexus_enable_anonymous=True' \ 19 | -e 'nexus_ocp_cpu_request="4"' \ 20 | -e 'nexus_ocp_cpu_limit="4"' \ 21 | -e 'nexus_repos_delete_default=True' 22 | 23 | # -e "nexus_admin_password=redhat" \ 24 | # -e '{"nexus_ocp_repos_maven_proxy": [{"name": "maven-central","remote_url": "https://repo1.maven.org/maven2/","blob_store": "default","strict_content_validation": true,"version_policy": "release","layout_policy": "strict"},{"name": "rh-ga","remote_url": "https://maven.repository.redhat.com/ga/","blob_store": "default","strict_content_validation": true,"version_policy": "release","layout_policy": "strict"}]}' \ 25 | # -e '{"nexus_ocp_repos_maven_hosted": [{"name": "releases","blob_store": "default","write_policy": "allow","strict_content_validation": true,"version_policy": "release","layout_policy": "strict"}]}' \ 26 | # -e '{"nexus_ocp_repos_docker_hosted": [{"name": "docker","http_port": 5000,"https_port": 5001,"v1_enabled": true,"blob_store": "default","strict_content_validation": true,"write_policy": "allow"}]}' \ 27 | # -e '{"nexus_ocp_repos_maven_group":[{"name": "maven-all", "blob_store": "default","strict_content_validation": true, "member_repos": ["maven-central","rh-ga"]}]}' \ 28 | # -e "nexus_route=nexus.apps.shared-dev4.dev4.openshift.opentlc.com" \ 29 | # -e "nexus_registry_route=nexus-registry.apps.shared-dev4.dev4.openshift.opentlc.com" \ 30 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/templates/deployment.j2: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: "{{ _sonarqube_name }}" 5 | namespace: "{{ _sonarqube_namespace }}" 6 | labels: 7 | tuned.openshift.io/elasticsearch: "true" 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: "{{ _sonarqube_name }}" 13 | strategy: 14 | type: Recreate 15 | template: 16 | metadata: 17 | labels: 18 | app: "{{ _sonarqube_name }}" 19 | tuned.openshift.io/elasticsearch: "true" 20 | spec: 21 | serviceAccountName: "{{ _sonarqube_name }}" 22 | containers: 23 | - name: sonarqube 24 | image: "{{ _sonarqube_image }}:{{ _sonarqube_image_tag }}" 25 | imagePullPolicy: IfNotPresent 26 | ports: 27 | - containerPort: 9000 28 | protocol: TCP 29 | readinessProbe: 30 | httpGet: 31 | path: /about 32 | port: 9000 33 | scheme: HTTP 34 | initialDelaySeconds: 20 35 | timeoutSeconds: 1 36 | periodSeconds: 10 37 | successThreshold: 1 38 | failureThreshold: 3 39 | livenessProbe: 40 | httpGet: 41 | path: /about 42 | port: 9000 43 | scheme: HTTP 44 | initialDelaySeconds: 40 45 | timeoutSeconds: 1 46 | periodSeconds: 10 47 | successThreshold: 1 48 | failureThreshold: 3 49 | resources: 50 | requests: 51 | cpu: "{{ _sonarqube_cpu_request }}" 52 | memory: "{{ _sonarqube_memory_request}}" 53 | limits: 54 | cpu: "{{ _sonarqube_cpu_limit}}" 55 | memory: "{{ _sonarqube_memory_limit }}" 56 | env: 57 | - name: SONARQUBE_JDBC_PASSWORD 58 | value: "{{ _sonarqube_postgresql_password }}" 59 | - name: SONARQUBE_JDBC_URL 60 | value: "jdbc:postgresql://{{ _sonarqube_postgresql_service_name }}/{{ _sonarqube_postgresql_database_name }}" 61 | - name: SONARQUBE_JDBC_USERNAME 62 | value: "{{ _sonarqube_postgresql_user }}" 63 | volumeMounts: 64 | - name: sonarqube-data 65 | mountPath: /opt/sonarqube/data 66 | volumes: 67 | - name: sonarqube-data 68 | persistentVolumeClaim: 69 | claimName: "{{ _sonarqube_name }}-pvc" 70 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/README.adoc: -------------------------------------------------------------------------------- 1 | postgresql-ocp 2 | ============== 3 | 4 | Requirements 5 | ------------ 6 | 7 | This role is designed to set up PostgreSQL on an OpenShift cluster. The intended use for this role is in an Operator. The role does not set up Kubernetes controllers (like Deployments or ReplicaSets) but creates the PostgreSQL Pod directly - this is the preferred approach to be used by an Operator. 8 | 9 | Role Variables 10 | -------------- 11 | 12 | [cols="2,1,1,4",options="header"] 13 | |==== 14 | |Variable Name|Default|Required|Description 15 | |_postgresql_namespace|postgresql|Yes|Project Name to install the database into 16 | |_postgresql_name|postgresql|No|Name of the database service 17 | |_postgresql_database_name|postgresql|No|Name of Database to be created 18 | |_postgresql_user|postgresql|No|Database User Name 19 | |_postgresql_password|postgresql|No|Database Password 20 | |_postgresql_volume_size|1Gi|No|Size of Persistent Volume to be created 21 | |_postgresql_volume_storage_class|""|Storage class to use for the PostgreSQL volume. Default storage class is used when empty 22 | |_postgresql_memory_request|512Mi|No|Minimum Memory Requirement 23 | |_postgresql_memory_limit|512Mi|No|Maximum Memory Requirement 24 | |_postgresql_cpu_request|200m|No|Minimum CPU Requirement 25 | |_postgresql_cpu_limit|500m|No|Maximum CPU Requirement 26 | |_postgresql_wait_for_init|true|No|Wait for the database pod to be running and ready 27 | |_postgresql_image|registry.redhat.io/rhscl/postgresql-96-rhel7|Yes|The image to use for PostgreSQL 28 | |_postgresql_image_tag|latest|Yes|The image tag to use for PostgreSQL 29 | |_postgresql_state|present|No|`present` to install, `absent` to remove 30 | |==== 31 | 32 | Dependencies 33 | ------------ 34 | 35 | * k8s module. 36 | * Working .kube/config configuration. 37 | * The Project/Namespace must exist 38 | 39 | Example Playbook 40 | ---------------- 41 | 42 | [source,yaml] 43 | ---- 44 | - hosts: localhost 45 | gather_facts: no 46 | tasks: 47 | - name: Set up PostgreSQL 48 | include_role: 49 | name: ./roles/postgresql-ocp 50 | vars: 51 | _postgresql_namespace: "postgresql" 52 | _postgresql_name: "postgresql" 53 | _postgresql_database_name: "gogsdb" 54 | _postgresql_user: "gogsuser" 55 | _postgresql_password: "gogspassword" 56 | _postgresql_volume_size: "4Gi" 57 | ---- 58 | 59 | License 60 | ------- 61 | 62 | BSD 63 | -------------------------------------------------------------------------------- /roles/postgresql-ocp/templates/deployment.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: "{{ _postgresql_name }}" 5 | namespace: "{{ _postgresql_namespace }}" 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | name: "{{ _postgresql_name }}" 11 | strategy: 12 | type: Recreate 13 | template: 14 | metadata: 15 | labels: 16 | name: "{{ _postgresql_name }}" 17 | spec: 18 | containers: 19 | - env: 20 | - name: POSTGRESQL_USER 21 | valueFrom: 22 | secretKeyRef: 23 | key: database-user 24 | name: "{{ _postgresql_name }}" 25 | - name: POSTGRESQL_PASSWORD 26 | valueFrom: 27 | secretKeyRef: 28 | key: database-password 29 | name: "{{ _postgresql_name }}" 30 | - name: POSTGRESQL_DATABASE 31 | valueFrom: 32 | secretKeyRef: 33 | key: database-name 34 | name: "{{ _postgresql_name }}" 35 | image: "{{ _postgresql_image }}:{{ _postgresql_image_tag }}" 36 | imagePullPolicy: IfNotPresent 37 | livenessProbe: 38 | exec: 39 | command: 40 | - /usr/libexec/check-container 41 | - --live 42 | failureThreshold: 3 43 | initialDelaySeconds: 120 44 | periodSeconds: 10 45 | successThreshold: 1 46 | timeoutSeconds: 10 47 | name: postgresql 48 | ports: 49 | - containerPort: 5432 50 | protocol: TCP 51 | readinessProbe: 52 | exec: 53 | command: 54 | - /usr/libexec/check-container 55 | failureThreshold: 3 56 | initialDelaySeconds: 5 57 | periodSeconds: 10 58 | successThreshold: 1 59 | timeoutSeconds: 1 60 | resources: 61 | requests: 62 | cpu: "{{ _postgresql_cpu_request }}" 63 | memory: "{{ _postgresql_memory_request }}" 64 | limits: 65 | cpu: "{{ _postgresql_cpu_limit }}" 66 | memory: "{{ _postgresql_memory_limit }}" 67 | securityContext: 68 | capabilities: {} 69 | privileged: false 70 | terminationMessagePath: /dev/termination-log 71 | terminationMessagePolicy: File 72 | volumeMounts: 73 | - mountPath: /var/lib/pgsql/data 74 | name: postgresql-data 75 | dnsPolicy: ClusterFirst 76 | restartPolicy: Always 77 | schedulerName: default-scheduler 78 | securityContext: {} 79 | terminationGracePeriodSeconds: 30 80 | volumes: 81 | - name: postgresql-data 82 | persistentVolumeClaim: 83 | claimName: "{{ _postgresql_name }}-pvc" 84 | -------------------------------------------------------------------------------- /roles/nexus-ocp/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Set up Nexus 3 | 4 | - name: Check for valid Nexus image tag 5 | when: nexus_ocp_state == 'present' 6 | assert: 7 | that: 8 | - nexus_ocp_image_tag is not match('latest') 9 | fail_msg: "Due to the frequent changes in the Nexus container image, 'latest' is not a valid image tag. Please use a specific version starting with 3.30.0." 10 | quiet: true 11 | 12 | - name: Check for supported Nexus image version 13 | when: nexus_ocp_state == 'present' 14 | assert: 15 | that: 16 | - nexus_ocp_image_tag is version_compare('3.30', '>=') 17 | fail_msg: "Minimum supported Nexus Version is 3.30." 18 | quiet: true 19 | 20 | - name: Set OpenShift Objects for Nexus to {{ nexus_ocp_state }} 21 | community.kubernetes.k8s: 22 | state: "{{ nexus_ocp_state }}" 23 | definition: "{{ lookup('template', item ) | from_yaml }}" 24 | loop: 25 | - service_account.j2 26 | - service.j2 27 | - registry_service.j2 28 | - route.j2 29 | - registry_route.j2 30 | - persistent_volume_claim.j2 31 | 32 | - name: Set Nexus Deployment to {{ nexus_ocp_state }} 33 | community.kubernetes.k8s: 34 | state: "{{ nexus_ocp_state }}" 35 | definition: "{{ lookup('template', 'deployment.j2' ) | from_yaml }}" 36 | 37 | - name: Wait until Nexus pod is ready 38 | when: nexus_ocp_state == "present" 39 | community.kubernetes.k8s_info: 40 | api_version: apps/v1 41 | kind: Deployment 42 | namespace: "{{ nexus_ocp_namespace }}" 43 | name: "{{ nexus_ocp_name }}" 44 | register: r_nexus_deployment 45 | until: 46 | - r_nexus_deployment.resources | length | int > 0 47 | - r_nexus_deployment.resources[0].status.readyReplicas is defined 48 | - r_nexus_deployment.resources[0].status.readyReplicas | int == r_nexus_deployment.resources[0].spec.replicas | int 49 | retries: 40 50 | delay: 5 51 | ignore_errors: yes 52 | 53 | # Get Nexus Route 54 | - name: Get Nexus Route Hostname 55 | when: nexus_ocp_state == "present" 56 | community.kubernetes.k8s_info: 57 | api_version: route.openshift.io/v1 58 | kind: Route 59 | name: "{{ nexus_ocp_name }}" 60 | namespace: "{{ nexus_ocp_namespace }}" 61 | register: r_route 62 | 63 | - name: Store Nexus Route Hostname 64 | when: nexus_ocp_state == "present" 65 | set_fact: 66 | nexus_ocp_actual_route: "{{ r_route.resources[0].status.ingress[0].host }}" 67 | 68 | - name: Get Nexus CR 69 | community.kubernetes.k8s_info: 70 | api_version: gpte.opentlc.com/v1 71 | kind: Nexus 72 | name: "{{ ansible_operator_meta.name }}" 73 | namespace: "{{ ansible_operator_meta.namespace }}" 74 | register: r_nexus 75 | 76 | - name: Perform Initial Nexus Configuration 77 | when: r_nexus.resources[0].status.admin_password is not defined 78 | include_tasks: ./setup.yml 79 | vars: 80 | nexus_url: "{{ 'http' if not nexus_ocp_ssl | default( False ) | bool else 'https' }}://{{ nexus_ocp_actual_route }}" 81 | -------------------------------------------------------------------------------- /roles/gitea-ocp/README.adoc: -------------------------------------------------------------------------------- 1 | = gitea-ocp 2 | 3 | == Requirements 4 | 5 | This role is designed to set up Gitea ("Git with a cup of Tea") on an OpenShift cluster. The intended use for this role is in an Operator. The role does not set up Kubernetes controllers (like Deployments or ReplicaSets) but creates the Gitea Pod directly - this is the preferred approach to be used by an Operator. 6 | 7 | This role also requires a PostgreSQL Database to be running in the same OpenShift project as the Gitea Server. This database needs to be set up first. 8 | 9 | == Role Variables 10 | 11 | [cols="2,1,1,4",options="header"] 12 | |==== 13 | |Variable Name|Default|Required|Description 14 | |_gitea_namespace|gitea|Yes|Project Name to install Gitea into 15 | |_gitea_name|gitea|Yes|Name of the Gitea service 16 | |_gitea_ssl|False|No|Set up HTTPS for the Gitea Route 17 | |_gitea_postgresql_service_name|postgresql|Yes|Name of the PostgreSQL service to connect (should be listening on port 5432) 18 | |_gitea_postgresql_database_name|postgresql|Yes|Name of Database connect to 19 | |_gitea_postgresql_user|postgresql|Yes|Database User Name 20 | |_gitea_postgresql_password|postgresql|Yes|Database Password 21 | |_gitea_volume_size|1Gi|No|Size of Persistent Volume to be created 22 | |_gitea_memory_request|512Mi|No|Minimum Memory Requirement 23 | |_gitea_memory_limit|512Mi|No|Maximum Memory Requirement 24 | |_gitea_cpu_request|200m|No|Minimum CPU Requirement 25 | |_gitea_cpu_limit|500m|No|Maximum CPU Requirement 26 | |_gitea_wait_for_init|true|No|Wait for the database pod to be running and ready 27 | |_gitea_image|docker.io/wkulhanek/gitea|Yes|Container image for Gitea 28 | |_gitea_image_tag|latest|Yes|Tag for Gitea container image 29 | |_gitea_state|present|No|`present` to install, `absent` to remove 30 | |==== 31 | 32 | == Dependencies 33 | 34 | * k8s module. 35 | * Running PostgreSQL database listening on port 5432 36 | * Working .kube/config configuration. 37 | * The Project/Namespace must exist 38 | 39 | == Example Playbook 40 | 41 | [source,yaml] 42 | ---- 43 | - hosts: localhost 44 | gather_facts: no 45 | tasks: 46 | - name: Set up PostgreSQL 47 | include_role: 48 | name: ./roles/postgresql-ocp 49 | vars: 50 | _postgresql_namespace: "gitea" 51 | _postgresql_name: "postgresql-gitea" 52 | _postgresql_database_name: "giteadb" 53 | _postgresql_user: "giteauser" 54 | _postgresql_password: "giteapassword" 55 | _postgresql_volume_size: "4Gi" 56 | 57 | - name: Set up gitea 58 | include_role: 59 | name: ./roles/gitea-ocp 60 | vars: 61 | _gitea_namespace: "gitea" 62 | _gitea_name: "gitea" 63 | _gitea_ssl: "True| 64 | _gitea_volume_size: "4Gi" 65 | _gitea_postgresql_service_name: "postgresql-gitea" 66 | _gitea_postgresql_database_name: giteadb 67 | _gitea_postgresql_user: giteauser 68 | _gitea_postgresql_password: giteapassword 69 | ---- 70 | 71 | == License 72 | 73 | BSD 74 | -------------------------------------------------------------------------------- /roles/gogs-ocp/README.adoc: -------------------------------------------------------------------------------- 1 | = gogs-ocp 2 | 3 | == Requirements 4 | 5 | This role is designed to set up Gogs (the Go Github Server) on an OpenShift cluster. The intended use for this role is in an Operator. The role does not set up Kubernetes controllers (like Deployments or ReplicaSets) but creates the Gogs Pod directly - this is the preferred approach to be used by an Operator. 6 | 7 | This role also requires a PostgreSQL Database to be running in the same OpenShift project as the Gogs Server. This database needs to be set up first. 8 | 9 | == Role Variables 10 | 11 | [cols="2,1,1,4",options="header"] 12 | |==== 13 | |Variable Name|Default|Required|Description 14 | |_gogs_namespace|gogs|Yes|Project Name to install Gogs into 15 | |_gogs_name|gogs|Yes|Name of the Gogs service. Ensure this name is unique in your namespace/project 16 | |_gogs_ssl|False|No|Set up HTTPS for the Gogs Route 17 | |_gogs_postgresql_service_name|postgresql|Yes|Name of the PostgreSQL service to connect (should be listening on port 5432) 18 | |_gogs_postgresql_database_name|postgresql|Yes|Name of Database to connect to 19 | |_gogs_postgresql_user|postgresql|Yes|Database User Name 20 | |_gogs_postgresql_password|postgresql|Yes|Database Password 21 | |_gogs_volume_size|1Gi|No|Size of Persistent Volume to be created 22 | |_gogs_memory_request|512Mi|No|Minimum Memory Requirement 23 | |_gogs_memory_limit|512Mi|No|Maximum Memory Requirement 24 | |_gogs_cpu_request|200m|No|Minimum CPU Requirement 25 | |_gogs_cpu_limit|500m|No|Maximum CPU Requirement 26 | |_gogs_wait_for_init|true|No|Wait for the database pod to be running and ready 27 | |_gogs_image|docker.io/wkulhanek/gitea|Yes|Container image for Gogs 28 | |_gogs_image_tag|latest|Yes|Tag for Gogs container image 29 | |_gogs_state|present|No|`present` to install, `absent` to remove 30 | |==== 31 | 32 | == Dependencies 33 | 34 | * k8s module. 35 | * Running PostgreSQL database listening on port 5432 36 | * Working .kube/config configuration. 37 | * The Project/Namespace must exist 38 | 39 | == Example Playbook 40 | 41 | [source,yaml] 42 | ---- 43 | - hosts: localhost 44 | gather_facts: no 45 | tasks: 46 | - name: Set up PostgreSQL 47 | include_role: 48 | name: ./roles/postgresql-ocp 49 | vars: 50 | _postgresql_namespace: "gogs" 51 | _postgresql_name: "postgresql-gogs" 52 | _postgresql_database_name: "gogsdb" 53 | _postgresql_user: "gogsuser" 54 | _postgresql_password: "gogspassword" 55 | _postgresql_volume_size: "4Gi" 56 | 57 | - name: Set up Gogs 58 | include_role: 59 | name: ./roles/gogs-ocp 60 | vars: 61 | _gogs_namespace: "gogs" 62 | _gogs_name: "gogs" 63 | _gogs_ssl: "True| 64 | _gogs_volume_size: "4Gi" 65 | _gogs_postgresql_service_name: "postgresql-gogs" 66 | _gogs_postgresql_database_name: gogsdb 67 | _gogs_postgresql_user: gogsuser 68 | _gogs_postgresql_password: gogspassword 69 | ---- 70 | 71 | == License 72 | 73 | BSD 74 | -------------------------------------------------------------------------------- /roles/sonarqube-ocp/README.adoc: -------------------------------------------------------------------------------- 1 | = sonarqube-ocp 2 | 3 | == Requirements 4 | 5 | This role is designed to set up SonarQube on an OpenShift cluster. The intended use for this role is in an Operator. The role does not set up Kubernetes controllers (like Deployments or ReplicaSets) but creates the SonarQube Pod directly - this is the preferred approach to be used by an Operator. 6 | 7 | This role also requires a PostgreSQL Database to be running in the same OpenShift project as the SonarQube Server. This database needs to be set up first. 8 | 9 | == Role Variables 10 | 11 | [cols="2,1,1,4",options="header"] 12 | |==== 13 | |Variable Name|Default|Required|Description 14 | |_sonarqube_namespace|sonarqube|Yes|Project Name to install SonarQube into 15 | |_sonarqube_name|sonarqube|Yes|Name of the SonarQube service 16 | |_sonarqube_ssl|False|No|Set up HTTPS for the SonarQube Route 17 | |_sonarqube_postgresql_service_name|postgresql|Yes|Name of the PostgreSQL service to connect (should be listening on port 5432) 18 | |_sonarqube_postgresql_database_name|postgresql|Yes|Name of Database to connect to 19 | |_sonarqube_postgresql_user|postgresql|Yes|Database User Name 20 | |_sonarqube_postgresql_password|postgresql|Yes|Database Password 21 | |_sonarqube_volume_size|1Gi|No|Size of Persistent Volume to be created 22 | |_sonarqube_memory_request|512Mi|No|Minimum Memory Requirement 23 | |_sonarqube_memory_limit|512Mi|No|Maximum Memory Requirement 24 | |_sonarqube_cpu_request|200m|No|Minimum CPU Requirement 25 | |_sonarqube_cpu_limit|500m|No|Maximum CPU Requirement 26 | |_sonarqube_wait_for_init|true|No|Wait for the database pod to be running and ready 27 | |_sonarqube_image|docker.io/wkulhanek/sonarqube|Yes|Container image for Sonarqube 28 | |_sonarqube_image_tag|latest|Yes|Tag for Sonarqube container image 29 | |_sonarqube_state|present|No|`present` to install, `absent` to remove 30 | |==== 31 | 32 | == Dependencies 33 | 34 | * k8s module. 35 | * Running PostgreSQL database listening on port 5432 36 | * Working .kube/config configuration. 37 | * The Project/Namespace must exist 38 | 39 | == Example Playbook 40 | 41 | [source,yaml] 42 | ---- 43 | - hosts: localhost 44 | gather_facts: no 45 | tasks: 46 | - name: Set up PostgreSQL 47 | include_role: 48 | name: ./roles/postgresql-ocp 49 | vars: 50 | _postgresql_namespace: "sonarqube" 51 | _postgresql_name: "postgresql-sonarqube" 52 | _postgresql_database_name: "sonardb" 53 | _postgresql_user: "sonar" 54 | _postgresql_password: "sonar" 55 | _postgresql_volume_size: "4Gi" 56 | 57 | - name: Set up Sonarqube 58 | include_role: 59 | name: ./roles/sonarqube-ocp 60 | vars: 61 | _sonarqube_namespace: "sonarqube" 62 | _sonarqube_name: "sonarqube" 63 | _sonarqube_ssl: "True" 64 | _sonarqube_volume_size: "4Gi" 65 | _sonarqube_postgresql_service_name: "postgresql-sonarqube" 66 | _sonarqube_postgresql_database_name: sonardb 67 | _sonarqube_postgresql_user: sonar 68 | _sonarqube_postgresql_password: sonar 69 | ---- 70 | 71 | == License 72 | 73 | BSD 74 | -------------------------------------------------------------------------------- /roles/gitea-ocp/templates/config_map.yaml.j2: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: "{{ _gitea_name }}-config" 5 | namespace: "{{ _gitea_namespace }}" 6 | data: 7 | app.ini: | 8 | APP_NAME = {{ _gitea_name }} 9 | RUN_MODE = prod 10 | RUN_USER = gitea 11 | 12 | [security] 13 | INTERNAL_TOKEN = ${GITEA_INTERNAL_TOKEN} 14 | INSTALL_LOCK = true 15 | SECRET_KEY = ${GITEA_SECRET_KEY} 16 | PASSWORD_COMPLEXITY = off 17 | 18 | [oauth2] 19 | ENABLE = false 20 | 21 | [database] 22 | DB_TYPE = postgres 23 | HOST = {{ _gitea_postgresql_service_name }}:{{ _gitea_postgresql_port }} 24 | NAME = {{ _gitea_postgresql_database_name }} 25 | USER = {{ _gitea_postgresql_user }} 26 | PASSWD = {{ _gitea_postgresql_password }} 27 | SSL_MODE = disable 28 | 29 | [repository] 30 | ROOT = /gitea-repositories 31 | 32 | [server] 33 | ROOT_URL = {{ 'http' if not _gitea_ssl | default(false) | bool else 'https' }}://{{ _gitea_actual_route }}/ 34 | SSH_DOMAIN = {{ _gitea_actual_route }} 35 | DOMAIN = {{ _gitea_actual_route }} 36 | HTTP_PORT = {{ _gitea_http_port }} 37 | SSH_PORT = {{ _gitea_ssh_port }} 38 | DISABLE_SSH = {{ _gitea_disable_ssh | bool }} 39 | START_SSH_SERVER = {{ _gitea_start_ssh_server | bool }} 40 | LFS_START_SERVER = False 41 | OFFLINE_MODE = False 42 | 43 | {% if _gitea_allow_local_network_migration | bool %} 44 | [migrations] 45 | ALLOW_LOCALNETWORKS = true 46 | 47 | {% endif %} 48 | [mailer] 49 | ENABLED = {{ _gitea_mailer_enabled | bool }} 50 | {% if _gitea_mailer_enabled | bool %} 51 | FROM = {{ _gitea_mailer_from }} 52 | MAILER_TYPE = {{ _gitea_mailer_type }} 53 | HOST = {{ _gitea_mailer_host }} 54 | IS_TLS_ENABLED = {{ _gitea_mailer_tls | bool }} 55 | USER = {{ _gitea_mailer_user }} 56 | PASSWD = `{{ _gitea_mailer_password }}` 57 | {% if _gitea_mailer_helo_hostname | length > 0 %} 58 | HELO_HOSTNAME = {{ _gitea_mailer_helo_hostname }} 59 | {% endif %} 60 | {% endif %} 61 | 62 | [service] 63 | REGISTER_EMAIL_CONFIRM = {{ _gitea_register_email_confirm | bool }} 64 | ENABLE_NOTIFY_MAIL = {{ _gitea_enable_notify_mail | bool }} 65 | DISABLE_REGISTRATION = {{ _gitea_disable_registration | bool }} 66 | ENABLE_CAPTCHA = {{ _gitea_enable_captcha | bool }} 67 | REQUIRE_SIGNIN_VIEW = false 68 | DEFAULT_KEEP_EMAIL_PRIVATE = false 69 | DEFAULT_ALLOW_CREATE_ORGANIZATION = {{ _gitea_allow_create_organization | bool }} 70 | DEFAULT_ENABLE_TIMETRACKING = true 71 | NO_REPLY_ADDRESS = noreply.example.org 72 | 73 | [picture] 74 | DISABLE_GRAVATAR = false 75 | ENABLE_FEDERATED_AVATAR = true 76 | 77 | [openid] 78 | ENABLE_OPENID_SIGNIN = false 79 | ENABLE_OPENID_SIGNUP = false 80 | 81 | [session] 82 | PROVIDER = file 83 | 84 | [log] 85 | MODE = file 86 | LEVEL = Info 87 | ROOT_PATH = /home/gitea/log 88 | 89 | [markup.asciidoc] 90 | ENABLED = true 91 | FILE_EXTENSIONS = .adoc,.asciidoc 92 | RENDER_COMMAND = "asciidoc --out-file=- -" 93 | IS_INPUT_FILE = false 94 | -------------------------------------------------------------------------------- /playbooks/nexus.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Persistent Nexus deployment playbook. 3 | # 4 | # The Playbook expects the following variables to be set in the CR: 5 | # (Note that Camel case gets converted by the ansible-operator to Snake case) 6 | # - nexusVolumeSize 7 | # - nexusSsl 8 | # Optional Variables: 9 | # - nexusCpuRequest 10 | # - nexusCpuLimit 11 | # - nexusMemoryRequest 12 | # - nexusMemoryLimit 13 | # The following variables come from the ansible-operator 14 | # - ansible_operator_meta.namespace 15 | # - ansible_operator_meta.name (from the name of the CR) 16 | - hosts: localhost 17 | gather_facts: no 18 | tasks: 19 | - name: Set up Nexus 20 | include_role: 21 | name: ../roles/nexus-ocp 22 | vars: 23 | # Project to install Nexus into 24 | nexus_ocp_namespace: "{{ ansible_operator_meta.namespace }}" 25 | # Name of the Nexus instance 26 | nexus_ocp_name: "{{ ansible_operator_meta.name }}" 27 | 28 | # Persistent Volume Claim Size for Nexus 29 | nexus_ocp_volume_size: "{{ nexus_volume_size | default( '10Gi' ) }}" 30 | 31 | # Container image for the Nexus Repository Manager 32 | nexus_ocp_image: "{{ nexus_image | default( 'registry.connect.redhat.com/sonatype/nexus-repository-manager' ) }}" 33 | # Image tag for the Nexus Repository Manager 34 | nexus_ocp_image_tag: "{{ nexus_image_tag | default( 'latest' ) }}" 35 | 36 | # Use SSL Termination for the Nexus route 37 | nexus_ocp_ssl: "{{ nexus_ssl | default( 'False' ) }}" 38 | # Specify a route for the Nexus instance. When empty create default route 39 | nexus_ocp_route: "{{ nexus_route | default( '' ) }}" 40 | # Specify a route for the Nexus container regisgtry instance. When empty create default route 41 | nexus_ocp_registry_route: "{{ nexus_registry_route | default( '' ) }}" 42 | 43 | # Resource requests and limits 44 | nexus_ocp_cpu_request: "{{ nexus_cpu_request | default( '2' ) }}" 45 | nexus_ocp_cpu_limit: "{{ nexus_cpu_limit | default( '2' ) }}" 46 | nexus_ocp_memory_request: "{{ nexus_memory_request | default( '2Gi' ) }}" 47 | nexus_ocp_memory_limit: "{{ nexus_memory_limit | default( '2Gi' ) }}" 48 | 49 | # Current admin password (for old Nexus versions) 50 | nexus_ocp_admin_password: "{{ nexus_admin_password | default( 'admin123' ) }}" 51 | # Set a new admin password 52 | nexus_ocp_new_admin_password: "{{ nexus_new_admin_password | default( 'admin123' ) }}" 53 | 54 | # Enable anonymous read access 55 | nexus_ocp_enable_anonymous: "{{ nexus_enable_anonymous | default( 'True' ) }}" 56 | 57 | # Delete default repositories before creating any repositories specified below 58 | nexus_ocp_repos_delete_default: "{{ nexus_repos_delete_default | default ( 'True' ) }}" 59 | 60 | # Nexus repositories to create 61 | # See ansible-operator-roles/nexus-ocp/defaults/main.yml for examples 62 | nexus_ocp_repos_maven_proxy_setup: "{{ nexus_repos_maven_proxy_setup | default( 'True' ) }}" 63 | nexus_ocp_repos_maven_hosted_setup: "{{ nexus_repos_maven_hosted_setup | default( 'True' ) }}" 64 | nexus_ocp_repos_maven_group_setup: "{{ nexus_repos_maven_group_setup | default( 'True' ) }}" 65 | nexus_ocp_repos_docker_hosted_setup: "{{ nexus_repos_docker_hosted_setup | default( 'True' ) }}" 66 | nexus_ocp_repos_npm_proxy_setup: "{{ nexus_repos_npm_proxy_setup | default( 'True' ) }}" 67 | nexus_ocp_repos_npm_group_setup: "{{ nexus_repos_npm_group_setup | default( 'True' ) }}" 68 | nexus_ocp_repos_helm_hosted_setup: "{{ nexus_repos_helm_hosted_setup | default( 'False' ) }}" 69 | 70 | nexus_ocp_repos_maven_proxy: "{{ nexus_repos_maven_proxy | default( '' ) }}" 71 | nexus_ocp_repos_maven_hosted: "{{ nexus_repos_maven_hosted | default( '' ) }}" 72 | nexus_ocp_repos_maven_group: "{{ nexus_repos_maven_group | default( '' ) }}" 73 | nexus_ocp_repos_docker_hosted: "{{ nexus_repos_docker_hosted | default( '' ) }}" 74 | nexus_ocp_repos_npm_proxy: "{{ nexus_repos_npm_proxy | default( '' ) }}" 75 | nexus_ocp_repos_npm_group: "{{ nexus_repos_npm_group | default( '' ) }}" 76 | nexus_ocp_repos_helm_hosted: "{{ nexus_repos_helm_hosted | default( '' ) }}" 77 | -------------------------------------------------------------------------------- /roles/nexus-ocp/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for nexus-ocp 3 | nexus_ocp_state: present 4 | nexus_ocp_namespace: nexus 5 | nexus_ocp_name: nexus3 6 | 7 | nexus_ocp_ssl: False 8 | nexus_ocp_volume_size: 20Gi 9 | nexus_ocp_memory_request: 2Gi 10 | nexus_ocp_memory_limit: 2Gi 11 | nexus_ocp_cpu_request: 2 12 | nexus_ocp_cpu_limit: 2 13 | #nexus_ocp_image: docker.io/sonatype/nexus3 14 | nexus_ocp_image: registry.connect.redhat.com/sonatype/nexus-repository-manager 15 | # Latest is not a valid value for image tag 16 | nexus_ocp_image_tag: "3.30.0-ubi-1" 17 | # Set to a valid route for the cluster to make a nicer route than svc-project.apps. Leave empty for default route 18 | nexus_ocp_route: "" 19 | # Set to a valid route for the cluster to make a nicer registry route than svc-project.apps. Leave empty for default route 20 | nexus_ocp_registry_route: "" 21 | 22 | # Nexus Admin Password. Used for Nexus versions before 3.17 23 | nexus_ocp_admin_password: admin123 24 | 25 | # New Nexus Admin Password: Set the admin password for 26 | # Nexus Versions starting with 3.17 27 | # The Nexus Admin password will be changed to this value from the 28 | # default generated password 29 | nexus_ocp_new_admin_password: admin123 30 | 31 | # Enable anonymous read access 32 | nexus_ocp_enable_anonymous: True 33 | 34 | # --- Repositories --- 35 | 36 | # Delete all default Nexus repositories during initial setup 37 | nexus_ocp_repos_delete_default: True 38 | 39 | nexus_ocp_repos_maven_proxy_setup: True 40 | nexus_ocp_repos_maven_proxy_default: 41 | - name: maven-central 42 | remote_url: https://repo1.maven.org/maven2/ 43 | blob_store: default 44 | strict_content_validation: true 45 | version_policy: release # release, snapshot or mixed 46 | layout_policy: strict # strict or permissive 47 | # For some reason `redhat-ga` gets converted into `******-ga` when querying repositories 48 | # So don't user `redhat` in your repo names 49 | - name: rh-ga 50 | remote_url: https://maven.repository.redhat.com/ga/ 51 | blob_store: default 52 | strict_content_validation: true 53 | version_policy: release # release, snapshot or mixed 54 | layout_policy: strict # strict or permissive 55 | - name: jboss 56 | remote_url: https://repository.jboss.org/nexus/content/groups/public 57 | blob_store: default 58 | strict_content_validation: true 59 | version_policy: release # release, snapshot or mixed 60 | layout_policy: strict # strict or permissive 61 | 62 | nexus_ocp_repos_maven_hosted_setup: True 63 | nexus_ocp_repos_maven_hosted_default: 64 | - name: releases 65 | blob_store: default 66 | write_policy: allow_once # allow_once or allow 67 | strict_content_validation: true 68 | version_policy: release # release, snapshot or mixed 69 | layout_policy: strict # strict or permissive 70 | 71 | nexus_ocp_repos_maven_group_setup: True 72 | nexus_ocp_repos_maven_group_default: 73 | - name: maven-all-public 74 | blob_store: default 75 | strict_content_validation: true 76 | member_repos: 77 | - maven-central 78 | - rh-ga 79 | - jboss 80 | 81 | nexus_ocp_repos_docker_hosted_setup: True 82 | nexus_ocp_repos_docker_hosted_default: 83 | - name: docker 84 | http_port: 5000 85 | https_port: 5001 86 | v1_enabled: True 87 | blob_store: default 88 | strict_content_validation: true 89 | write_policy: allow_once # allow_once or allow 90 | 91 | nexus_ocp_repos_npm_proxy_setup: True 92 | nexus_ocp_repos_npm_proxy_default: 93 | - name: npm 94 | remote_url: https://registry.npmjs.org 95 | blob_store: default 96 | strict_content_validation: true 97 | 98 | nexus_ocp_repos_npm_group_setup: True 99 | nexus_ocp_repos_npm_group_default: 100 | - name: npm-all 101 | blob_store: default 102 | strict_content_validation: true 103 | member_repos: 104 | - npm 105 | 106 | # Helm Hosted Repositories 107 | ocp4_workload_nexus_operator_repos_helm_hosted_setup: false 108 | ocp4_workload_nexus_operator_repos_helm_hosted: 109 | - name: helm 110 | blob_store: default 111 | write_policy: allow # allow_once or allow 112 | strict_content_validation: true 113 | proprietaryComponents: true # true or false 114 | 115 | # --- Internal Variables --- 116 | # --- Do not change -------- 117 | nexus_ocp_actual_route: "" 118 | nexus_ocp_current_password: "" 119 | -------------------------------------------------------------------------------- /playbooks/gitea.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Persistent Gitea deployment playbook. 3 | # 4 | # The Playbook expects the following variables to be set in the CR: 5 | # (Note that Camel case gets converted by the ansible-operator to Snake case) 6 | # - PostgresqlVolumeSize 7 | # - GiteaVolumeSize 8 | # - GiteaSSL 9 | # The following variables come from the ansible-operator 10 | # - ansible_operator_meta.namespace 11 | # - ansible_operator_meta.name (from the name of the CR) 12 | 13 | - hosts: localhost 14 | gather_facts: no 15 | tasks: 16 | - name: Set up PostgreSQL 17 | when: postgresql_setup | default(true) | bool 18 | include_role: 19 | name: ../roles/postgresql-ocp 20 | vars: 21 | _postgresql_namespace: "{{ ansible_operator_meta.namespace }}" 22 | _postgresql_name: "{{ postgresql_service_name | default ('postgresql-'+ansible_operator_meta.name) }}" 23 | _postgresql_database_name: "{{ postgresql_database_name | default('giteadb') }}" 24 | _postgresql_user: "{{ postgresql_user | default ('giteauser') }}" 25 | _postgresql_password: "{{ postgresql_password | default('giteapassword') }}" 26 | _postgresql_volume_size: "{{ postgresql_volume_size|default('4Gi') }}" 27 | _postgresql_volume_storage_class: "{{ postgresql_volume_storage_class | default('') }}" 28 | _postgresql_image: "{{ postgresql_image | default('registry.redhat.io/rhel8/postgresql-12') }}" 29 | _postgresql_image_tag: "{{ postgresql_image_tag|default('latest') }}" 30 | _postgresql_memory_request: "{{ postgresql_memory_request | default('512Mi') }}" 31 | _postgresql_memory_limit: "{{ postgresql_memory_limit | default('512Mi') }}" 32 | _postgresql_cpu_request: "{{ postgresql_cpu_request | default('200m') }}" 33 | _postgresql_cpu_limit: "{{ postgresql_cpu_limit | default('500m') }}" 34 | 35 | - name: Set up Gitea 36 | include_role: 37 | name: ../roles/gitea-ocp 38 | vars: 39 | _gitea_namespace: "{{ ansible_operator_meta.namespace }}" 40 | _gitea_name: "{{ gitea_service_name | default( ansible_operator_meta.name ) }}" 41 | _gitea_ssl: "{{ gitea_ssl | default(false) | bool }}" 42 | _gitea_route: "{{ gitea_route | default('') }}" 43 | _gitea_image: "{{ gitea_image | default('quay.io/gpte-devops-automation/gitea') }}" 44 | _gitea_image_tag: "{{ gitea_image_tag | default('latest') }}" 45 | _gitea_volume_size: "{{ gitea_volume_size | default('4Gi') }}" 46 | _gitea_volume_storage_class: "{{ gitea_volume_storage_class | default('') }}" 47 | _gitea_memory_request: "{{ gitea_memory_request | default('1Gi') }}" 48 | _gitea_memory_limit: "{{ gitea_memory_limit | default('1Gi') }}" 49 | _gitea_cpu_request: "{{ gitea_cpu_request | default('200m') }}" 50 | _gitea_cpu_limit: "{{ gitea_cpu_limit | default('500m') }}" 51 | 52 | _gitea_postgresql_service_name: "{{ gitea_postgresql_service_name | default('postgresql-'+ansible_operator_meta.name) }}" 53 | _gitea_postgresql_database_name: "{{ gitea_postgresql_database_name | default('giteadb') }}" 54 | _gitea_postgresql_user: "{{ gitea_postgresql_user | default('giteauser') }}" 55 | _gitea_postgresql_password: "{{ gitea_postgresql_password | default('giteapassword') }}" 56 | 57 | _gitea_admin_user: "{{ gitea_admin_user | default('') }}" 58 | _gitea_admin_password: "{{ gitea_admin_password | default('') }}" 59 | _gitea_admin_password_length: "{{ gitea_admin_password_length | default(16) }}" 60 | _gitea_admin_email: "{{ gitea_admin_email | default('notset@notset.org') }}" 61 | 62 | _gitea_http_port: "{{ gitea_http_port | default(3000) }}" 63 | _gitea_ssh_port: "{{ gitea_ssh_port | default(2022) }}" 64 | _gitea_disable_ssh: "{{ gitea_disable_ssh | default(true) | bool }}" 65 | _gitea_start_ssh_server: "{{ gitea_start_ssh_server | default(false) | bool }}" 66 | _gitea_disable_registration: "{{ gitea_disable_registration | default(false) | bool }}" 67 | _gitea_enable_captcha: "{{ gitea_enable_captcha | default(false) | bool }}" 68 | _gitea_allow_create_organization: "{{ gitea_allow_create_organization | default(true) | bool }}" 69 | _gitea_allow_local_network_migration: "{{ gitea_allow_local_network_migration | default(false) | bool }}" 70 | 71 | _gitea_mailer_enabled: "{{ gitea_mailer_enabled | default(false) | bool }}" 72 | _gitea_mailer_from: "{{ gitea_mailer_from | default('') }}" 73 | _gitea_mailer_type: "{{ gitea_mailer_type | default('smtp') }}" 74 | _gitea_mailer_host: "{{ gitea_mailer_host | default('') }}" 75 | _gitea_mailer_tls: "{{ gitea_mailer_tls | default(true) | bool }}" 76 | _gitea_mailer_user: "{{ gitea_mailer_user | default('') }}" 77 | _gitea_mailer_password: "{{ gitea_mailer_password | default('') }}" 78 | _gitea_mailer_helo_hostname: "{{ gitea_mailer_helo_hostname | default ('') }}" 79 | _gitea_register_email_confirm: "{{ gitea_register_email_confirm | default(false) | bool }}" 80 | _gitea_enable_notify_mail: "{{ gitea_enable_notify_mail | default(false) | bool }}" 81 | -------------------------------------------------------------------------------- /roles/gitea-ocp/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for Gitea 3 | - name: Set OpenShift Objects for Gitea to {{ _gitea_state }} 4 | community.kubernetes.k8s: 5 | state: "{{ _gitea_state }}" 6 | merge_type: 7 | - strategic-merge 8 | - merge 9 | definition: "{{ lookup('template', item ) | from_yaml }}" 10 | loop: 11 | - service_account.yaml.j2 12 | - service.yaml.j2 13 | - route.yaml.j2 14 | - persistent_volume_claim.yaml.j2 15 | 16 | # Get Gitea Route 17 | - name: Get Gitea Route Hostname 18 | when: _gitea_state == "present" 19 | community.kubernetes.k8s_info: 20 | api_version: route.openshift.io/v1 21 | kind: Route 22 | name: "{{ _gitea_name }}" 23 | namespace: "{{ _gitea_namespace }}" 24 | register: r_route 25 | 26 | - name: Store Gitea Route Hostname 27 | when: _gitea_state == "present" 28 | set_fact: 29 | _gitea_actual_route: "{{ r_route.resources[0].status.ingress[0].host }}" 30 | 31 | - name: Set Route dependant OpenShift Objects for Gitea to {{ _gitea_state }} 32 | community.kubernetes.k8s: 33 | state: "{{ _gitea_state }}" 34 | merge_type: 35 | - strategic-merge 36 | - merge 37 | definition: "{{ lookup('template', item ) | from_yaml }}" 38 | loop: 39 | - config_map.yaml.j2 40 | - deployment.yaml.j2 41 | 42 | - name: Wait until application is available 43 | when: _gitea_state == "present" 44 | community.kubernetes.k8s_info: 45 | api_version: apps/v1 46 | kind: Deployment 47 | name: "{{ _gitea_name }}" 48 | namespace: "{{ _gitea_namespace }}" 49 | register: r_deployment 50 | until: 51 | - r_deployment.resources[0].status.availableReplicas is defined 52 | - r_deployment.resources[0].status.availableReplicas == 1 53 | retries: 50 54 | delay: 10 55 | ignore_errors: yes 56 | 57 | - name: Retrieve Gitea resource 58 | community.kubernetes.k8s_info: 59 | api_version: gpte.opentlc.com/v1 60 | kind: Gitea 61 | name: "{{ _gitea_name }}" 62 | namespace: "{{ _gitea_namespace }}" 63 | register: r_gitea 64 | 65 | - name: Save route in custom resource status 66 | operator_sdk.util.k8s_status: 67 | api_version: gpte.opentlc.com/v1 68 | kind: Gitea 69 | name: "{{ _gitea_name }}" 70 | namespace: "{{ _gitea_namespace }}" 71 | status: 72 | giteaRoute: "{{ 'http' if not _gitea_ssl | default(false) | bool else 'https' }}://{{ _gitea_actual_route }}" 73 | 74 | - name: Set up Admin User 75 | when: 76 | - r_gitea.resources[0].status.admin_password is not defined 77 | - _gitea_admin_user | default("") | length > 0 78 | block: 79 | - name: Retrieve Gitea pod 80 | community.kubernetes.k8s_info: 81 | kind: Pod 82 | namespace: "{{ _gitea_namespace }}" 83 | label_selectors: 84 | - app = {{ _gitea_name }} 85 | register: r_gitea_pod 86 | 87 | - name: Retrieve Gitea route 88 | community.kubernetes.k8s_info: 89 | kind: Route 90 | api_version: route.openshift.io/v1 91 | namespace: "{{ _gitea_namespace }}" 92 | label_selectors: 93 | - app = {{ _gitea_name }} 94 | register: r_gitea_route 95 | 96 | - name: Check if Gitea admin user already exists 97 | uri: 98 | url: "{{ 'http' if not _gitea_ssl | default(false) | bool else 'https' }}://{{ r_gitea_route.resources[0].spec.host }}/api/v1/users/{{ _gitea_admin_user }}" 99 | method: GET 100 | validate_certs: false 101 | status_code: 200, 404 102 | register: r_gitea_admin_user 103 | failed_when: r_gitea_admin_user.status != 200 and r_gitea_admin_user.status != 404 104 | 105 | - name: Set up Gitea admin user 106 | when: r_gitea_admin_user.status == 404 107 | block: 108 | - name: Set Admin password 109 | when: _gitea_admin_password | default("") | length > 0 110 | set_fact: 111 | _gitea_actual_admin_password: "{{ _gitea_admin_password }}" 112 | 113 | - name: Generate Admin password 114 | when: _gitea_admin_password | default("") | length == 0 115 | set_fact: 116 | _gitea_actual_admin_password: >- 117 | {{ lookup('password', '/dev/null length={{ _gitea_admin_password_length }} chars=ascii_letters,digits') }} 118 | 119 | - name: Create Gitea admin user 120 | community.kubernetes.k8s_exec: 121 | namespace: "{{ _gitea_namespace }}" 122 | pod: "{{ r_gitea_pod.resources[0].metadata.name }}" 123 | command: >- 124 | /usr/bin/giteacmd admin user create 125 | --username {{ _gitea_admin_user }} 126 | --password {{ _gitea_actual_admin_password }} 127 | --email {{ _gitea_admin_email }} 128 | --must-change-password=false 129 | --admin 130 | register: r_create_admin_user 131 | 132 | - name: Save password in custom resource status 133 | when: r_create_admin_user.return_code == 0 134 | operator_sdk.util.k8s_status: 135 | api_version: gpte.opentlc.com/v1 136 | kind: Gitea 137 | name: "{{ _gitea_name }}" 138 | namespace: "{{ _gitea_namespace }}" 139 | status: 140 | adminPassword: "{{ _gitea_actual_admin_password }}" 141 | -------------------------------------------------------------------------------- /roles/nexus-ocp/tasks/setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tasks to setup Nexus the first time 3 | 4 | # Retrieve temporary admin password from Nexus pod 5 | - name: Get name of Nexus pod 6 | community.kubernetes.k8s_info: 7 | api_version: v1 8 | kind: Pod 9 | namespace: "{{ nexus_ocp_namespace }}" 10 | label_selectors: "app={{ nexus_ocp_name }}" 11 | register: r_nexus_pod 12 | 13 | - name: Attempt to read nexus admin password file 14 | community.kubernetes.k8s_exec: 15 | namespace: "{{ nexus_ocp_namespace }}" 16 | pod: "{{ r_nexus_pod.resources[0].metadata.name }}" 17 | command: "cat /nexus-data/admin.password" 18 | ignore_errors: true 19 | register: r_admin_password 20 | 21 | - name: Set current password 22 | when: r_admin_password.return_code == 0 23 | set_fact: 24 | nexus_ocp_current_password: "{{ r_admin_password.stdout }}" 25 | 26 | - name: Set current password to admin password if password file does not exist 27 | when: not r_admin_password.return_code == 0 28 | set_fact: 29 | nexus_ocp_current_password: "{{ nexus_ocp_admin_password }}" 30 | 31 | - name: Delete all repositories 32 | when: nexus_ocp_repos_delete_default | bool 33 | block: 34 | - name: Get current repositories 35 | uri: 36 | url: "{{ nexus_url }}/service/rest/v1/repositories" 37 | user: admin 38 | password: "{{ nexus_ocp_current_password }}" 39 | method: GET 40 | status_code: 200 41 | force_basic_auth: yes 42 | validate_certs: no 43 | register: r_repos 44 | 45 | - name: Delete all existing repositories 46 | when: r_repos.json | length > 0 47 | uri: 48 | url: "{{ nexus_url }}/service/rest/v1/repositories/{{ item.name }}" 49 | user: admin 50 | password: "{{ nexus_ocp_current_password }}" 51 | method: DELETE 52 | status_code: 204 53 | force_basic_auth: yes 54 | validate_certs: no 55 | loop: "{{ r_repos.json }}" 56 | ignore_errors: true 57 | 58 | - name: Create Maven Proxy repositories 59 | when: nexus_ocp_repos_maven_proxy_setup | bool 60 | block: 61 | - name: Use Maven Proxy repos 62 | when: nexus_ocp_repos_maven_proxy | length > 0 63 | set_fact: 64 | _nexus_ocp_repos_maven_proxy: "{{ nexus_ocp_repos_maven_proxy }}" 65 | - name: Use default Maven Proxy repos 66 | when: nexus_ocp_repos_maven_proxy | length == 0 67 | set_fact: 68 | _nexus_ocp_repos_maven_proxy: "{{ nexus_ocp_repos_maven_proxy_default }}" 69 | - name: Create Maven Proxy Repositories 70 | uri: 71 | url: "{{ nexus_url }}/service/rest/v1/repositories/maven/proxy" 72 | user: admin 73 | password: "{{ nexus_ocp_current_password }}" 74 | body_format: json 75 | method: POST 76 | force_basic_auth: yes 77 | status_code: 201 78 | validate_certs: no 79 | body: 80 | name: "{{ item.name }}" 81 | online: true 82 | storage: 83 | blobStoreName: "{{ item.blob_store }}" 84 | strictContentTypeValidation: "{{ item.strict_content_validation }}" 85 | # cleanup: 86 | # policyNames: weekly-cleanup 87 | proxy: 88 | remoteUrl: "{{ item.remote_url }}" 89 | contentMaxAge: 1440 90 | metadataMaxAge: 1440 91 | negativeCache: 92 | enabled: false 93 | timeToLive: 1440 94 | httpClient: 95 | blocked: false 96 | autoBlock: false 97 | connection: 98 | retries: 0 99 | userAgentSuffix: string 100 | timeout: 60 101 | enableCircularRedirects: false 102 | enableCookies: false 103 | routingRule: string 104 | maven: 105 | versionPolicy: "{{ item.version_policy | upper }}" 106 | layoutPolicy: "{{ item.layout_policy | upper }}" 107 | ignore_errors: true 108 | loop: "{{ _nexus_ocp_repos_maven_proxy }}" 109 | 110 | - name: Create Maven Hosted repositories 111 | when: nexus_ocp_repos_maven_hosted_setup | bool 112 | block: 113 | - name: Use Maven Hosted repos 114 | when: nexus_ocp_repos_maven_hosted | length > 0 115 | set_fact: 116 | _nexus_ocp_repos_maven_hosted: "{{ nexus_ocp_repos_maven_hosted }}" 117 | - name: Use default Maven Hosted repos 118 | when: nexus_ocp_repos_maven_hosted | length == 0 119 | set_fact: 120 | _nexus_ocp_repos_maven_hosted: "{{ nexus_ocp_repos_maven_hosted_default }}" 121 | - name: Create Maven Hosted Repositories 122 | uri: 123 | url: "{{ nexus_url }}/service/rest/v1/repositories/maven/hosted" 124 | user: admin 125 | password: "{{ nexus_ocp_current_password }}" 126 | body_format: json 127 | method: POST 128 | force_basic_auth: yes 129 | status_code: 201 130 | validate_certs: no 131 | body: 132 | name: "{{ item.name }}" 133 | online: true 134 | storage: 135 | blobStoreName: "{{ item.blob_store }}" 136 | strictContentTypeValidation: "{{ item.strict_content_validation }}" 137 | writePolicy: "{{ item.write_policy | upper }}" 138 | # cleanup: 139 | # policyNames: weekly-cleanup 140 | maven: 141 | versionPolicy: "{{ item.version_policy | upper }}" 142 | layoutPolicy: "{{ item.layout_policy | upper }}" 143 | ignore_errors: true 144 | loop: "{{ _nexus_ocp_repos_maven_hosted }}" 145 | 146 | - name: Create Maven Groups 147 | when: nexus_ocp_repos_maven_group_setup | bool 148 | block: 149 | - name: Use Maven Group repos 150 | when: nexus_ocp_repos_maven_group | length > 0 151 | set_fact: 152 | _nexus_ocp_repos_maven_group: "{{ nexus_ocp_repos_maven_group }}" 153 | - name: Use default Maven Group repos 154 | when: nexus_ocp_repos_maven_group | length == 0 155 | set_fact: 156 | _nexus_ocp_repos_maven_group: "{{ nexus_ocp_repos_maven_group_default }}" 157 | - name: Create Maven Group Repositories 158 | uri: 159 | url: "{{ nexus_url }}/service/rest/v1/repositories/maven/group" 160 | user: admin 161 | password: "{{ nexus_ocp_current_password }}" 162 | body_format: json 163 | method: POST 164 | force_basic_auth: yes 165 | status_code: 201 166 | validate_certs: no 167 | body: 168 | name: "{{ item.name }}" 169 | online: true 170 | storage: 171 | blobStoreName: "{{ item.blob_store }}" 172 | strictContentTypeValidation: "{{ item.strict_content_validation }}" 173 | group: 174 | memberNames: "{{ item.member_repos }}" 175 | ignore_errors: true 176 | loop: "{{ _nexus_ocp_repos_maven_group }}" 177 | 178 | - name: Create Docker repositories 179 | when: nexus_ocp_repos_docker_hosted_setup | bool 180 | block: 181 | - name: Use Docker hosted repos 182 | when: nexus_ocp_repos_docker_hosted | length > 0 183 | set_fact: 184 | _nexus_ocp_repos_docker_hosted: "{{ nexus_ocp_repos_docker_hosted }}" 185 | - name: Use default Docker hosted repos 186 | when: nexus_ocp_repos_docker_hosted | length == 0 187 | set_fact: 188 | _nexus_ocp_repos_docker_hosted: "{{ nexus_ocp_repos_docker_hosted_default }}" 189 | - name: Create Docker hosted Repositories 190 | uri: 191 | url: "{{ nexus_url }}/service/rest/v1/repositories/docker/hosted" 192 | user: admin 193 | password: "{{ nexus_ocp_current_password }}" 194 | body_format: json 195 | method: POST 196 | force_basic_auth: yes 197 | status_code: 201 198 | validate_certs: no 199 | body: 200 | name: "{{ item.name }}" 201 | online: true 202 | storage: 203 | blobStoreName: "{{ item.blob_store }}" 204 | strictContentTypeValidation: "{{ item.strict_content_validation }}" 205 | writePolicy: "{{ item.write_policy | upper}}" 206 | # cleanup: 207 | # policyNames: weekly-cleanup 208 | docker: 209 | v1Enabled: "{{ item.v1_enabled }}" 210 | forceBasicAuth: true 211 | httpPort: "{{ item.http_port }}" 212 | httpsPort: "{{ item.https_port }}" 213 | ignore_errors: true 214 | loop: "{{ _nexus_ocp_repos_docker_hosted }}" 215 | 216 | - name: Create NPM Proxy repositories 217 | when: nexus_ocp_repos_npm_proxy_setup | bool 218 | block: 219 | - name: Use NPM Proxy repos 220 | when: nexus_ocp_repos_npm_proxy | length > 0 221 | set_fact: 222 | _nexus_ocp_repos_npm_proxy: "{{ nexus_ocp_repos_npm_proxy }}" 223 | - name: Use default NPM Proxy repos 224 | when: nexus_ocp_repos_npm_proxy | length == 0 225 | set_fact: 226 | _nexus_ocp_repos_npm_proxy: "{{ nexus_ocp_repos_npm_proxy_default }}" 227 | - name: Create NPM Proxy Repositories 228 | uri: 229 | url: "{{ nexus_url }}/service/rest/v1/repositories/npm/proxy" 230 | user: admin 231 | password: "{{ nexus_ocp_current_password }}" 232 | body_format: json 233 | method: POST 234 | force_basic_auth: yes 235 | status_code: 201 236 | validate_certs: no 237 | body: 238 | name: "{{ item.name }}" 239 | online: true 240 | storage: 241 | blobStoreName: "{{ item.blob_store }}" 242 | strictContentTypeValidation: "{{ item.strict_content_validation }}" 243 | # cleanup: 244 | # policyNames: weekly-cleanup 245 | proxy: 246 | remoteUrl: "{{ item.remote_url }}" 247 | contentMaxAge: 1440 248 | metadataMaxAge: 1440 249 | negativeCache: 250 | enabled: false 251 | timeToLive: 1440 252 | httpClient: 253 | blocked: false 254 | autoBlock: false 255 | connection: 256 | retries: 0 257 | userAgentSuffix: string 258 | timeout: 60 259 | enableCircularRedirects: false 260 | enableCookies: false 261 | routingRule: string 262 | ignore_errors: true 263 | loop: "{{ _nexus_ocp_repos_npm_proxy }}" 264 | 265 | - name: Create NPM Groups 266 | when: nexus_ocp_repos_npm_group_setup | bool 267 | block: 268 | - name: Use NPM group repos 269 | when: nexus_ocp_repos_npm_group | length > 0 270 | set_fact: 271 | _nexus_ocp_repos_npm_group: "{{ nexus_ocp_repos_npm_group }}" 272 | - name: Use default NPM group repos 273 | when: nexus_ocp_repos_npm_group | length == 0 274 | set_fact: 275 | _nexus_ocp_repos_npm_group: "{{ nexus_ocp_repos_npm_group_default }}" 276 | - name: Create NPM group Repositories 277 | uri: 278 | url: "{{ nexus_url }}/service/rest/v1/repositories/npm/group" 279 | user: admin 280 | password: "{{ nexus_ocp_current_password }}" 281 | body_format: json 282 | method: POST 283 | force_basic_auth: yes 284 | status_code: 201 285 | validate_certs: no 286 | body: 287 | name: "{{ item.name }}" 288 | online: true 289 | storage: 290 | blobStoreName: "{{ item.blob_store }}" 291 | strictContentTypeValidation: "{{ item.strict_content_validation }}" 292 | group: 293 | memberNames: "{{ item.member_repos }}" 294 | ignore_errors: true 295 | loop: "{{ _nexus_ocp_repos_npm_group }}" 296 | 297 | - name: Create Helm repositories 298 | when: nexus_ocp_repos_helm_hosted_setup | bool 299 | block: 300 | - name: Use Helm hosted repositories 301 | when: nexus_ocp_repos_helm_hosted | length > 0 302 | set_fact: 303 | _nexus_ocp_repos_helm_hosted: "{{ nexus_ocp_repos_helm_hosted }}" 304 | - name: Use default Helm hosted repos 305 | when: nexus_ocp_repos_helm_hosted | length == 0 306 | set_fact: 307 | _nexus_ocp_repos_helm_hosted: "{{ nexus_ocp_repos_helm_hosted_default }}" 308 | - name: Create Helm hosted repositories 309 | uri: 310 | url: "{{ nexus_url }}/service/rest/v1/repositories/helm/hosted" 311 | user: admin 312 | password: "{{ nexus_ocp_current_password }}" 313 | body_format: json 314 | method: POST 315 | force_basic_auth: yes 316 | status_code: 201 317 | validate_certs: no 318 | body: 319 | name: "{{ item.name }}" 320 | online: true 321 | storage: 322 | blobStoreName: "{{ item.blob_store }}" 323 | strictContentTypeValidation: "{{ item.strict_content_validation }}" 324 | writePolicy: "{{ item.write_policy | upper}}" 325 | # cleanup: 326 | # policyNames: weekly-cleanup 327 | component: 328 | proprietaryComponents: "{{ item.proprietaryComponents }}" 329 | ignore_errors: true 330 | loop: "{{ _nexus_ocp_repos_helm_hosted }}" 331 | 332 | - name: Turn on Anonymous Access 333 | when: nexus_ocp_enable_anonymous | bool 334 | uri: 335 | url: "{{ nexus_url }}/service/rest/v1/security/anonymous" 336 | user: admin 337 | password: "{{ nexus_ocp_current_password }}" 338 | body_format: json 339 | method: PUT 340 | force_basic_auth: yes 341 | status_code: 200 342 | validate_certs: no 343 | body: 344 | enabled: true 345 | userId: "anonymous" 346 | realmName: "NexusAuthorizingRealm" 347 | ignore_errors: true 348 | 349 | - name: Update Admin password 350 | when: nexus_ocp_new_admin_password | length > 0 351 | uri: 352 | url: "{{ nexus_url }}/service/rest/v1/security/users/admin/change-password" 353 | user: admin 354 | password: "{{ nexus_ocp_current_password }}" 355 | headers: 356 | Content-Type: "text/plain" 357 | method: PUT 358 | status_code: 200,204 359 | force_basic_auth: yes 360 | validate_certs: no 361 | body: "{{ nexus_ocp_new_admin_password }}" 362 | 363 | - name: Change default password 364 | when: nexus_ocp_new_admin_password | length > 0 365 | set_fact: 366 | nexus_ocp_admin_password: "{{ nexus_ocp_new_admin_password }}" 367 | 368 | # The following will fail unless this is running as an operator 369 | # Report current admin password in Nexus CR status 370 | - name: Save password in custom resource status 371 | operator_sdk.util.k8s_status: 372 | api_version: gpte.opentlc.com/v1 373 | kind: Nexus 374 | name: "{{ ansible_operator_meta.name }}" 375 | namespace: "{{ ansible_operator_meta.namespace }}" 376 | status: 377 | admin_password: "{{ nexus_ocp_new_admin_password }}" 378 | -------------------------------------------------------------------------------- /playbooks/library/k8s_status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import re 7 | import copy 8 | 9 | from ansible.module_utils.k8s.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC, KubernetesAnsibleModule 10 | 11 | try: 12 | from openshift.dynamic.exceptions import DynamicApiError 13 | except ImportError as exc: 14 | class KubernetesException(Exception): 15 | pass 16 | 17 | 18 | __metaclass__ = type 19 | 20 | ANSIBLE_METADATA = {'metadata_version': '1.1', 21 | 'status': ['preview'], 22 | 'supported_by': 'community'} 23 | 24 | DOCUMENTATION = ''' 25 | 26 | module: k8s_status 27 | 28 | short_description: Update the status for a Kubernetes API resource 29 | 30 | version_added: "2.7" 31 | 32 | author: "Fabian von Feilitzsch (@fabianvf)" 33 | 34 | description: 35 | - Sets the status field on a Kubernetes API resource. Only should be used if you are using Ansible to 36 | implement a controller for the resource being modified. 37 | 38 | options: 39 | status: 40 | type: dict 41 | description: 42 | - A object containing `key: value` pairs that will be set on the status object of the specified resource. 43 | - One of I(status) or I(conditions) is required. 44 | conditions: 45 | type: list 46 | description: 47 | - A list of condition objects that will be set on the status.conditions field of the specified resource. 48 | - Unless I(force) is C(true) the specified conditions will be merged with the conditions already set on the status field of the specified resource. 49 | - Each element in the list will be validated according to the conventions specified in the 50 | [Kubernetes API conventions document](https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status). 51 | - 'The fields supported for each condition are: 52 | `type` (required), 53 | `status` (required, one of "True", "False", "Unknown"), 54 | `reason` (single CamelCase word), 55 | `message`, 56 | `lastHeartbeatTime` (RFC3339 datetime string), and 57 | `lastTransitionTime` (RFC3339 datetime string).' 58 | - One of I(status) or I(conditions) is required.' 59 | api_version: 60 | description: 61 | - Use to specify the API version. Use in conjunction with I(kind), I(name), and I(namespace) to identify a 62 | specific object. 63 | required: yes 64 | aliases: 65 | - api 66 | - version 67 | kind: 68 | description: 69 | - Use to specify an object model. Use in conjunction with I(api_version), I(name), and I(namespace) to identify a 70 | specific object. 71 | required: yes 72 | name: 73 | description: 74 | - Use to specify an object name. Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a 75 | specific object. 76 | required: yes 77 | namespace: 78 | description: 79 | - Use to specify an object namespace. Use in conjunction with I(api_version), I(kind), and I(name) 80 | to identify a specific object. 81 | force: 82 | description: 83 | - If set to C(True), the status will be set using `PUT` rather than `PATCH`, replacing the full status object. 84 | default: false 85 | type: bool 86 | host: 87 | description: 88 | - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. 89 | api_key: 90 | description: 91 | - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable. 92 | kubeconfig: 93 | description: 94 | - Path to an instance Kubernetes config file. If not provided, and no other connection 95 | options are provided, the openshift client will attempt to load the default 96 | configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG environment 97 | variable. 98 | context: 99 | description: 100 | - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment variable. 101 | username: 102 | description: 103 | - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment 104 | variable. 105 | password: 106 | description: 107 | - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment 108 | variable. 109 | cert_file: 110 | description: 111 | - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE environment 112 | variable. 113 | key_file: 114 | description: 115 | - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE environment 116 | variable. 117 | ssl_ca_cert: 118 | description: 119 | - Path to a CA certificate used to authenticate with the API. Can also be specified via K8S_AUTH_SSL_CA_CERT 120 | environment variable. 121 | verify_ssl: 122 | description: 123 | - "Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL 124 | environment variable." 125 | type: bool 126 | 127 | requirements: 128 | - "python >= 2.7" 129 | - "openshift >= 0.8.1" 130 | - "PyYAML >= 3.11" 131 | ''' 132 | 133 | EXAMPLES = ''' 134 | - name: Set custom status fields on TestCR 135 | k8s_status: 136 | api_version: apps.example.com/v1alpha1 137 | kind: TestCR 138 | name: my-test 139 | namespace: testing 140 | status: 141 | hello: world 142 | custom: entries 143 | 144 | - name: Update the standard condition of an Ansible Operator 145 | k8s_status: 146 | api_version: apps.example.com/v1alpha1 147 | kind: TestCR 148 | name: my-test 149 | namespace: testing 150 | conditions: 151 | - type: Running 152 | status: "True" 153 | reason: MigrationStarted 154 | message: "Migration from v2 to v3 has begun" 155 | lastTransitionTime: "{{ ansible_date_time.iso8601 }}" 156 | 157 | - name: | 158 | Create custom conditions. WARNING: The default Ansible Operator status management 159 | will never overwrite custom conditions, so they will persist indefinitely. If you 160 | want the values to change or be removed, you will need to clean them up manually. 161 | k8s_status: 162 | conditions: 163 | - type: Available 164 | status: "False" 165 | reason: PingFailed 166 | message: "The service did not respond to a ping" 167 | 168 | ''' 169 | 170 | RETURN = ''' 171 | result: 172 | description: 173 | - If a change was made, will return the patched object, otherwise returns the instance object. 174 | returned: success 175 | type: complex 176 | contains: 177 | api_version: 178 | description: The versioned schema of this representation of an object. 179 | returned: success 180 | type: str 181 | kind: 182 | description: Represents the REST resource this object represents. 183 | returned: success 184 | type: str 185 | metadata: 186 | description: Standard object metadata. Includes name, namespace, annotations, labels, etc. 187 | returned: success 188 | type: complex 189 | spec: 190 | description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). 191 | returned: success 192 | type: complex 193 | status: 194 | description: Current status details for the object. 195 | returned: success 196 | type: complex 197 | ''' 198 | 199 | 200 | def condition_array(conditions): 201 | 202 | VALID_KEYS = ['type', 'status', 'reason', 'message', 'lastHeartbeatTime', 'lastTransitionTime'] 203 | REQUIRED = ['type', 'status'] 204 | CAMEL_CASE = re.compile(r'^(?:[A-Z]*[a-z]*)+$') 205 | RFC3339_datetime = re.compile(r'^\d{4}-\d\d-\d\dT\d\d:\d\d(:\d\d)?(\.\d+)?(([+-]\d\d:\d\d)|Z)$') 206 | 207 | def validate_condition(condition): 208 | if not isinstance(condition, dict): 209 | raise ValueError('`conditions` must be a list of objects') 210 | if isinstance(condition.get('status'), bool): 211 | condition['status'] = 'True' if condition['status'] else 'False' 212 | 213 | for key in condition.keys(): 214 | if key not in VALID_KEYS: 215 | raise ValueError('{} is not a valid field for a condition, accepted fields are {}'.format(key, VALID_KEYS)) 216 | for key in REQUIRED: 217 | if not condition.get(key): 218 | raise ValueError('Condition `{}` must be set'.format(key)) 219 | 220 | if condition['status'] not in ['True', 'False', 'Unknown']: 221 | raise ValueError('Condition `status` must be one of ["True", "False", "Unknown"], not {}'.format(condition['status'])) 222 | 223 | if condition.get('reason') and not re.match(CAMEL_CASE, condition['reason']): 224 | raise ValueError('Condition `reason` must be a single, CamelCase word') 225 | 226 | for key in ['lastHeartBeatTime', 'lastTransitionTime']: 227 | if condition.get(key) and not re.match(RFC3339_datetime, condition[key]): 228 | raise ValueError('`{}` must be a RFC3339 compliant datetime string'.format(key)) 229 | 230 | return condition 231 | 232 | return [validate_condition(c) for c in conditions] 233 | 234 | 235 | STATUS_ARG_SPEC = { 236 | 'status': { 237 | 'type': 'dict', 238 | 'required': False 239 | }, 240 | 'conditions': { 241 | 'type': condition_array, 242 | 'required': False 243 | } 244 | } 245 | 246 | 247 | def main(): 248 | KubernetesAnsibleStatusModule().execute_module() 249 | 250 | 251 | class KubernetesAnsibleStatusModule(KubernetesAnsibleModule): 252 | 253 | def __init__(self, *args, **kwargs): 254 | KubernetesAnsibleModule.__init__( 255 | self, *args, 256 | supports_check_mode=True, 257 | **kwargs 258 | ) 259 | self.kind = self.params.get('kind') 260 | self.api_version = self.params.get('api_version') 261 | self.name = self.params.get('name') 262 | self.namespace = self.params.get('namespace') 263 | self.force = self.params.get('force') 264 | 265 | self.status = self.params.get('status') or {} 266 | self.conditions = self.params.get('conditions') or [] 267 | 268 | if self.conditions and self.status and self.status.get('conditions'): 269 | raise ValueError("You cannot specify conditions in both the `status` and `conditions` parameters") 270 | 271 | if self.conditions: 272 | self.status['conditions'] = self.conditions 273 | 274 | def execute_module(self): 275 | self.client = self.get_api_client() 276 | 277 | resource = self.find_resource(self.kind, self.api_version, fail=True) 278 | if 'status' not in resource.subresources: 279 | self.fail_json(msg='Resource {}.{} does not support the status subresource'.format(resource.api_version, resource.kind)) 280 | 281 | try: 282 | instance = resource.get(name=self.name, namespace=self.namespace).to_dict() 283 | except DynamicApiError as exc: 284 | self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc), 285 | error=exc.summary()) 286 | # Make sure status is at least initialized to an empty dict 287 | instance['status'] = instance.get('status', {}) 288 | 289 | if self.force: 290 | self.exit_json(**self.replace(resource, instance)) 291 | else: 292 | self.exit_json(**self.patch(resource, instance)) 293 | 294 | def replace(self, resource, instance): 295 | if self.status == instance['status']: 296 | return {'result': instance, 'changed': False} 297 | instance['status'] = self.status 298 | try: 299 | result = resource.status.replace(body=instance).to_dict(), 300 | except DynamicApiError as exc: 301 | self.fail_json(msg='Failed to replace status: {}'.format(exc), error=exc.summary()) 302 | 303 | return { 304 | 'result': result, 305 | 'changed': True 306 | } 307 | 308 | def patch(self, resource, instance): 309 | if self.object_contains(instance['status'], self.status): 310 | return {'result': instance, 'changed': False} 311 | instance['status'] = self.merge_status(instance['status'], self.status) 312 | try: 313 | result = resource.status.patch(body=instance, content_type='application/merge-patch+json').to_dict() 314 | except DynamicApiError as exc: 315 | self.fail_json(msg='Failed to replace status: {}'.format(exc), error=exc.summary()) 316 | 317 | return { 318 | 'result': result, 319 | 'changed': True 320 | } 321 | 322 | def merge_status(self, old, new): 323 | old_conditions = old.get('conditions', []) 324 | new_conditions = new.get('conditions', []) 325 | if not (old_conditions and new_conditions): 326 | return new 327 | 328 | merged = copy.deepcopy(old_conditions) 329 | 330 | for condition in new_conditions: 331 | idx = self.get_condition_idx(merged, condition['type']) 332 | if idx: 333 | merged[idx] = condition 334 | else: 335 | merged.append(condition) 336 | new['conditions'] = merged 337 | return new 338 | 339 | def get_condition_idx(self, conditions, name): 340 | for i, condition in enumerate(conditions): 341 | if condition.get('type') == name: 342 | return i 343 | 344 | def object_contains(self, obj, subset): 345 | def dict_is_subset(obj, subset): 346 | return all([mapping.get(type(obj.get(k)), mapping['default'])(obj.get(k), v) for (k, v) in subset.items()]) 347 | 348 | def list_is_subset(obj, subset): 349 | return all(item in obj for item in subset) 350 | 351 | def values_match(obj, subset): 352 | return obj == subset 353 | 354 | mapping = { 355 | dict: dict_is_subset, 356 | list: list_is_subset, 357 | tuple: list_is_subset, 358 | 'default': values_match 359 | } 360 | 361 | return dict_is_subset(obj, subset) 362 | 363 | @property 364 | def argspec(self): 365 | args = copy.deepcopy(COMMON_ARG_SPEC) 366 | args.pop('state') 367 | args.pop('resource_definition') 368 | args.pop('src') 369 | args.update(AUTH_ARG_SPEC) 370 | args.update(STATUS_ARG_SPEC) 371 | return args 372 | 373 | 374 | if __name__ == '__main__': 375 | main() 376 | --------------------------------------------------------------------------------