diff --git a/.zuul.yaml b/.zuul.yaml index 7f8fec0..d349687 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -28,6 +28,7 @@ allowed-projects: zuul/zuul-operator requires: - zuul-container-image + - zuul-registry-container-image - nodepool-container-image provides: - zuul-operator-container-image diff --git a/conf/zuul/input.dhall b/conf/zuul/input.dhall index 0bd9510..cf93d59 100644 --- a/conf/zuul/input.dhall +++ b/conf/zuul/input.dhall @@ -83,6 +83,20 @@ let Schemas = } , default = { image = None Text, count = None Natural } } + , Registry = + { Type = + { image : Optional Text + , count : Optional Natural + , storage-size : Optional Natural + , public-url : Optional Text + } + , default = + { image = None Text + , count = None Natural + , storage-size = None Natural + , public-url = None Text + } + } , Launcher = { Type = { image : Optional Text, config : UserSecret } , default.image = None Text @@ -128,6 +142,7 @@ let Input = , executor : Schemas.Executor.Type , web : Schemas.Web.Type , scheduler : Schemas.Scheduler.Type + , registry : Schemas.Registry.Type , launcher : Schemas.Launcher.Type , database : Optional UserSecret , zookeeper : Optional UserSecret @@ -142,6 +157,7 @@ let Input = , merger = Schemas.Merger.default , web = Schemas.Web.default , scheduler = Schemas.Scheduler.default + , registry = Schemas.Registry.default , executor = Schemas.Executor.default , launcher = Schemas.Launcher.default , connections = Schemas.Connections.default diff --git a/conf/zuul/resources.dhall b/conf/zuul/resources.dhall index 6343069..c9f882a 100644 --- a/conf/zuul/resources.dhall +++ b/conf/zuul/resources.dhall @@ -12,6 +12,14 @@ The resources expect secrets to be created by the zuul ansible role: * `client.pem` * `client.key` +* `${name}-registry-tls` with: + + * `cert.pem` + * `cert.key` + * `secret` a password + * `username` the user name with write access + * `password` the user password + * `${name}-database-password` with a `password` key, (unless an input.database db uri is provided). -} let Prelude = ../Prelude.dhall @@ -72,6 +80,11 @@ let {- The Kubernetes resources of a Component } } +let DefaultNat = + \(value : Optional Natural) + -> \(default : Natural) + -> merge { None = default, Some = \(some : Natural) -> some } value + let DefaultText = \(value : Optional Text) -> \(default : Text) @@ -568,6 +581,38 @@ in \(input : Input) [ { path = "zuul.conf", content = mkZuulConf input zk-hosts } ] } + let etc-zuul-registry = + Volume::{ + , name = input.name ++ "-secret-registry" + , dir = "/etc/zuul" + , files = + [ { path = "registry.yaml" + , content = + let public-url = + DefaultText + input.registry.public-url + "https://registry:9000" + + in '' + registry: + address: '0.0.0.0' + port: 9000 + public-url: ${public-url} + tls-cert: /etc/zuul-registry/cert.pem + tls-key: /etc/zuul-registry/cert.key + secret: "%(ZUUL_REGISTRY_secret)" + storage: + driver: filesystem + root: /var/lib/zuul + users: + - name: "%(ZUUL_REGISTRY_username)" + pass: "%(ZUUL_REGISTRY_password)" + access: write + '' + } + ] + } + let etc-nodepool = Volume::{ , name = input.name ++ "-secret-nodepool" @@ -876,6 +921,72 @@ in \(input : Input) } ) } + , Registry = + let registry-volumes = + [ etc-zuul-registry + , Volume::{ + , name = input.name ++ "-registry-tls" + , dir = "/etc/zuul-registry" + } + ] + + let registry-env = + mkEnvVarSecret + ( Prelude.List.map + Text + EnvSecret + ( \(key : Text) + -> { name = "ZUUL_REGISTRY_${key}" + , key = key + , secret = + input.name ++ "-registry-tls" + } + ) + [ "secret", "username", "password" ] + ) + + in KubernetesComponent::{ + , Service = Some + (mkService "registry" "registry" 9000) + , StatefulSet = Some + ( mkStatefulSet + Component::{ + , name = "registry" + , count = + DefaultNat input.registry.count 0 + , data-dir = zuul-data-dir + , volumes = registry-volumes + , claim-size = + DefaultNat + input.registry.storage-size + 20 + , container = Kubernetes.Container::{ + , name = "registry" + , image = zuul-image "registry" + , args = Some + [ "zuul-registry" + , "-c" + , "/etc/zuul/registry.yaml" + , "serve" + ] + , imagePullPolicy = Some "IfNotPresent" + , ports = Some + [ Kubernetes.ContainerPort::{ + , name = Some "registry" + , containerPort = 9000 + } + ] + , env = Some registry-env + , volumeMounts = Some + ( mkVolumeMount + ( registry-volumes + # zuul-data-dir + ) + ) + } + } + ) + } } , Nodepool = let nodepool-image = @@ -1008,13 +1119,17 @@ in \(input : Input) { apiVersion = "v1" , kind = "List" , items = - [ mkSecret etc-zuul, mkSecret etc-nodepool ] + [ mkSecret etc-zuul + , mkSecret etc-nodepool + , mkSecret etc-zuul-registry + ] # mkUnion Components.Backend.Database # mkUnion Components.Backend.ZooKeeper # mkUnion Components.Zuul.Scheduler # mkUnion Components.Zuul.Executor # mkUnion Components.Zuul.Web # mkUnion Components.Zuul.Merger + # mkUnion Components.Zuul.Registry # mkUnion Components.Nodepool.Launcher } } diff --git a/deploy/crds/zuul-ci_v1alpha1_zuul_cr.yaml b/deploy/crds/zuul-ci_v1alpha1_zuul_cr.yaml index f4e7c09..7b38640 100644 --- a/deploy/crds/zuul-ci_v1alpha1_zuul_cr.yaml +++ b/deploy/crds/zuul-ci_v1alpha1_zuul_cr.yaml @@ -12,6 +12,8 @@ spec: scheduler: config: secretName: zuul-yaml-conf + registry: + count: 1 launcher: config: secretName: nodepool-yaml-conf diff --git a/playbooks/files/cr_spec.yaml b/playbooks/files/cr_spec.yaml index ecc37a6..31ef297 100644 --- a/playbooks/files/cr_spec.yaml +++ b/playbooks/files/cr_spec.yaml @@ -13,6 +13,8 @@ merger: scheduler: config: secretName: zuul-yaml-conf +registry: + count: 0 launcher: config: secretName: nodepool-yaml-conf diff --git a/playbooks/zuul-operator-functional/pre-k8s.yaml b/playbooks/zuul-operator-functional/pre-k8s.yaml index 3a21e48..facd90a 100644 --- a/playbooks/zuul-operator-functional/pre-k8s.yaml +++ b/playbooks/zuul-operator-functional/pre-k8s.yaml @@ -5,6 +5,7 @@ command: python3 -m pip install --user openshift roles: - role: clear-firewall + - role: install-podman - role: install-kubernetes vars: minikube_dns_resolvers: diff --git a/playbooks/zuul-operator-functional/run.yaml b/playbooks/zuul-operator-functional/run.yaml index cbc8886..904bf42 100644 --- a/playbooks/zuul-operator-functional/run.yaml +++ b/playbooks/zuul-operator-functional/run.yaml @@ -132,6 +132,8 @@ kubernetes: secretName: nodepool-kube-config key: kube.config + registry: + count: 1 - name: Wait maximum 4 minutes for the scheduler deployment shell: | @@ -164,3 +166,27 @@ - name: Wait an extra 2 minutes for the services to settle pause: minutes: 2 + + - name: Test the registry + block: + - name: Get registry service ip + command: kubectl get svc registry -o "jsonpath={.spec.clusterIP}" + register: _registry_ip + + - name: Add registry to /etc/hosts + become: yes + lineinfile: + path: /etc/hosts + regexp: "^.* registry$" + line: "{{ _registry_ip.stdout_lines[0] }} registry" + + - name: Get registry password + command: kubectl get secret zuul-registry-tls -o "jsonpath={.data.password}" + register: _registry_password + + - name: Test registry login + command: > + podman login + --tls-verify=false registry:9000 + -u zuul + -p "{{ _registry_password.stdout_lines[0] | b64decode }}" diff --git a/roles/zuul-ensure-registry-tls/tasks/main.yaml b/roles/zuul-ensure-registry-tls/tasks/main.yaml new file mode 100644 index 0000000..e99f041 --- /dev/null +++ b/roles/zuul-ensure-registry-tls/tasks/main.yaml @@ -0,0 +1,29 @@ +- name: Check if registry tls cert exists + set_fact: + registry_certs: "{{ lookup('k8s', api_version='v1', kind='Secret', namespace=namespace, resource_name=zuul_name + '-registry-tls') }}" + +- name: Generate and store certs + when: registry_certs.data is not defined + block: + - name: Generate certs + command: "{{ item }}" + loop: + # Server + - "openssl req -new -newkey rsa:2048 -nodes -keyout registry-{{ zuul_name }}.key -out registry-{{ zuul_name }}.csr -subj '/C=US/ST=Texas/L=Austin/O=Zuul/CN=server-{{ zuul_name }}'" + - "openssl x509 -req -days 3650 -in registry-{{ zuul_name }}.csr -out registry-{{ zuul_name }}.pem -CA ca-{{ zuul_name }}.pem -CAkey ca-{{ zuul_name }}.key -CAcreateserial" + + - name: Create k8s secret + k8s: + state: "{{ state }}" + namespace: "{{ namespace }}" + definition: + apiVersion: v1 + kind: Secret + metadata: + name: "{{ zuul_name }}-registry-tls" + stringData: + username: "zuul" + password: "{{ lookup('password', '/dev/null') }}" + secret: "{{ lookup('password', '/dev/null') }}" + cert.key: "{{ lookup('file', 'registry-' + zuul_name + '.key') }}" + cert.pem: "{{ lookup('file', 'registry-' + zuul_name + '.pem') }}" diff --git a/roles/zuul/defaults/main.yaml b/roles/zuul/defaults/main.yaml index a6cc677..84c5fab 100644 --- a/roles/zuul/defaults/main.yaml +++ b/roles/zuul/defaults/main.yaml @@ -10,4 +10,5 @@ raw_spec: "{{ vars['_operator_zuul-ci_org_zuul_spec'] | default(spec) }}" # Provide sensible default for non optional attributes: spec_defaults: web: {} + registry: {} externalConfig: {} diff --git a/roles/zuul/tasks/main.yaml b/roles/zuul/tasks/main.yaml index 8eb6353..974ec36 100644 --- a/roles/zuul/tasks/main.yaml +++ b/roles/zuul/tasks/main.yaml @@ -4,6 +4,10 @@ - zuul-lookup-conf - zuul-ensure-gearman-tls +- include_role: + name: zuul-ensure-registry-tls + when: (raw_spec['registry']['count'] | default(0)) | int > 0 + - include_role: name: zuul-ensure-database-password # when the user does not provide a db_uri