diff --git a/roles/harbor/defaults/main.yml b/roles/harbor/defaults/main.yml index 89d1030..10d56c0 100644 --- a/roles/harbor/defaults/main.yml +++ b/roles/harbor/defaults/main.yml @@ -1,3 +1,59 @@ --- harbor_version: v2.4.1 + +harbor_hostname: '{{ stage_server_domain }}' +harbor_external_url: 'https://{{ stage_server_domain }}' + +harbor_admin_username: '{{ harbor_admin_username_vault }}' +harbor_admin_password: '{{ harbor_admin_password_vault }}' + +traefik_id: '{{ inventory_hostname }}-harbor' + +harbor_dockercompose_customized: + services: + core: + extra_hosts: + - '{{ shared_service_keycloak_hostname }}:{{ shared_service_keycloak_ip }}' + - '{{ shared_service_mail_hostname }}:{{ shared_service_mail_ip }}' + proxy: + networks: + - harbor + - front-tier + ports: [] # not exposing ports - already used by traefik + labels: + - "traefik.enable=true" + - "traefik.http.middlewares.{{ traefik_id }}.headers.customrequestheaders.X-Forwarded-Proto=https" + - "traefik.http.routers.{{ traefik_id }}.service={{ traefik_id }}" + - "traefik.http.routers.{{ traefik_id }}.rule=Host(`{{ harbor_hostname }}`)" + - "traefik.http.routers.{{ traefik_id }}.entrypoints=websecure" + - "traefik.http.routers.{{ traefik_id }}.tls=true" + - "traefik.http.routers.{{ traefik_id }}.tls.certresolver=letsencrypt" + - "traefik.http.services.{{ traefik_id }}.loadbalancer.server.port=8080" + - "traefik.http.middlewares.{{ traefik_id }}-monitor.headers.customrequestheaders.X-Forwarded-Proto=https" + - "traefik.http.routers.{{ traefik_id }}-monitor.service={{ traefik_id }}-monitor" + - "traefik.http.routers.{{ traefik_id }}-monitor.rule=Host(`{{ harbor_hostname }}`)" + - "traefik.http.routers.{{ traefik_id }}-monitor.entrypoints=monitoring-harbor" + - "traefik.http.routers.{{ traefik_id }}-monitor.tls=true" + - "traefik.http.routers.{{ traefik_id }}-monitor.tls.certresolver=letsencrypt" + - "traefik.http.services.{{ traefik_id }}-monitor.loadbalancer.server.port=9090" + networks: + front-tier: + external: true + +harbor_base_configuration: + email_host: '{{ shared_service_mail_hostname }}' + email_port: 25 + email_from: '{{ ansible_fqdn }}@{{ shared_service_mail_hostname }}' + email_password: '' + email_username: '' + email_insecure: true + auth_mode: oidc_auth + oidc_name: docker + oidc_endpoint: 'https://{{ shared_service_keycloak_hostname }}/auth/realms/docker' + oidc_client_id: docker-registry + oidc_groups_claim: groups + oidc_scope: openid + oidc_verify_cert: true + oidc_auto_onboard: true + oidc_admin_group: '/admin' diff --git a/roles/harbor/tasks/configure_base_config.yml b/roles/harbor/tasks/configure_base_config.yml new file mode 100644 index 0000000..a12f8bb --- /dev/null +++ b/roles/harbor/tasks/configure_base_config.yml @@ -0,0 +1,20 @@ +--- + +- name: "Add harbor base configuration via API" + delegate_to: 127.0.0.1 + become: false + uri: + url: "{{ harbor_external_url }}/api/v2.0/configurations" + user: '{{ harbor_admin_username }}' + password: '{{ harbor_admin_password }}' + method: PUT + body_format: json + force_basic_auth: yes + body: "{{ base_configuration }}" + headers: + Content-Type: application/json + status_code: [200] + register: base_setting + delay: 10 + retries: 10 + until: base_setting.status in [200] diff --git a/roles/harbor/tasks/install.yml b/roles/harbor/tasks/install.yml new file mode 100644 index 0000000..691f6c6 --- /dev/null +++ b/roles/harbor/tasks/install.yml @@ -0,0 +1,154 @@ +--- + +### tags: + +- name: "Setup DNS configuration for {{ inventory_hostname }} harbor" + include_role: + name: _digitalocean + tasks_from: domain + vars: + record_data: "{{ stage_server_ip }}" + record_name: "{{ inventory_hostname }}" + +- name: 'Ensures {{ service_base_path }}/{{ inventory_hostname }} directory exists' + file: + state: directory + path: '{{ service_base_path }}/{{ inventory_hostname }}' + tags: + - update_deployment + - update_config + +- name: Install pip dependencies + ansible.builtin.pip: + name: "{{ item }}" + loop: + - docker-compose + +- name: 'Copy hacky upgrade script' + template: + src: 'hacky_harbor_upgrade.sh.j2' + dest: '/root/hacky_harbor_upgrade.sh' + owner: 'root' + group: 'root' + mode: '0744' + tags: + - upgrade-helper + +# work around for DEV-271("container start failure after reboot") +- name: Ensure systemd file + template: + src: harbor-systemd.service.j2 + dest: /etc/systemd/system/harbor.service + owner: root + group: root + mode: 0755 + +- name: "Check if harbor tarball exists" + stat: + path: '{{ service_base_path }}/{{ inventory_hostname }}/harbor-offline-installer-{{ harbor_version }}.tgz' + register: harbor_tarball + +- name: Download harbor offline installer + ansible.builtin.get_url: + url: https://github.com/goharbor/harbor/releases/download/{{ harbor_version }}/harbor-offline-installer-{{ harbor_version }}.tgz + dest: "{{ service_base_path }}/{{ inventory_hostname }}/harbor-offline-installer-{{ harbor_version }}.tgz" + when: + - not harbor_tarball.stat.exists + +- set_fact: + remote_docker_compose_file_path: '{{ service_base_path }}/{{ inventory_hostname }}/harbor/docker-compose.yml' + +- name: "Check if {{ inventory_hostname }}/harbor/docker-compose.yml exists" + stat: + path: '{{ remote_docker_compose_file_path }}' + register: harbor_installation + +- name: Extract harbor-offline-installer-{{ harbor_version }}.tgz into {{ service_base_path }}/{{ inventory_hostname }} + ansible.builtin.unarchive: + src: "{{ service_base_path }}/{{ inventory_hostname }}/harbor-offline-installer-{{ harbor_version }}.tgz" + dest: "{{ service_base_path }}/{{ inventory_hostname }}" + remote_src: yes + when: + - not harbor_installation.stat.exists + +- name: Ensure config template files are populated from templates/harbor + template: + src: "harbor.yml.j2" + dest: "{{ service_base_path }}/{{ inventory_hostname }}/harbor/harbor.yml" + owner: 'root' + group: 'root' + mode: 0644 + +- name: "Exec harbor install.sh " + ansible.builtin.shell: + cmd: './install.sh {{ harbor_install_opts | default("--with-trivy --with-chartmuseum") }}' + chdir: '{{ service_base_path }}/{{ inventory_hostname }}/harbor/' + ignore_errors: yes + when: + - not harbor_installation.stat.exists + +- name: "Stopping harbor" + community.docker.docker_compose: + project_src: '{{ service_base_path }}/{{ inventory_hostname }}/harbor/' + stopped: yes + when: + - not harbor_installation.stat.exists + +- name: "ensure harbor systemd service also stopped" + systemd: + name: harbor + state: stopped + daemon_reload: yes + when: + - not harbor_installation.stat.exists + +# create backup in case just sth weird had happened +- name: "Create backup of generated docker-compose.yml by install.sh" + copy: + src: '{{ remote_docker_compose_file_path }}' + dest: '{{ remote_docker_compose_file_path}}_from_installsh' + remote_src: yes + when: + - not harbor_installation.stat.exists + +- name: "Create backup of common/config/nginx/nginx.conf" + copy: + src: '{{ service_base_path }}/{{ inventory_hostname }}/harbor/common/config/nginx/nginx.conf' + dest: '{{ service_base_path }}/{{ inventory_hostname }}/harbor/common/config/nginx/nginx.conf_orig' + remote_src: yes + when: + - not harbor_installation.stat.exists + +- name: + ansible.builtin.lineinfile: + path: '{{ service_base_path }}/{{ inventory_hostname }}/harbor/common/config/nginx/nginx.conf' + state: absent + regexp: 'proxy_set_header' + +- name: "Read remote docker-compose.yml from harbor DIR" + ansible.builtin.slurp: + src: '{{ remote_docker_compose_file_path }}' + register: docker_compose_file_remote_encoded + +- set_fact: + harbor_dockercompose_merged: '{{ docker_compose_file_remote_encoded.content | b64decode | from_yaml | combine(harbor_dockercompose_customized, recursive=True) }}' + +- name: "Create docker-compose.yml with merged VARs" + copy: + content: "{{ harbor_dockercompose_merged | to_nice_yaml(indent=2) }}" + dest: '{{ remote_docker_compose_file_path }}' + owner: 'root' + group: 'root' + mode: '0644' + register: docker_compose_change + +- name: "Ensure harbor systemd service restarted" + systemd: + name: harbor + state: restarted + when: docker_compose_change.changed + +- name: "Ensure harbor systemd service started" + systemd: + name: harbor + state: started diff --git a/roles/harbor/tasks/main.yml b/roles/harbor/tasks/main.yml index 6ab5174..c9a4c13 100644 --- a/roles/harbor/tasks/main.yml +++ b/roles/harbor/tasks/main.yml @@ -1,117 +1,60 @@ --- -### tags: - -- name: "Setup DNS configuration for {{ inventory_hostname }} harbor" - include_role: - name: _digitalocean - tasks_from: domain - vars: - record_data: "{{ stage_server_ip }}" - record_name: "{{ inventory_hostname }}" - -- name: 'Ensures {{ service_base_path }}/{{ inventory_hostname }} directory exists' - file: - state: directory - path: '{{ service_base_path }}/{{ inventory_hostname }}' - tags: - - update_deployment - - update_config - -- name: 'Ensure directory structure for harbor exists' - file: - path: "{{ service_base_path }}/{{ inventory_hostname }}/{{ item.path }}" - state: directory - owner: "{{ docker_owner }}" - group: "{{ docker_group }}" - mode: 0755 - with_filetree: "templates/harbor" - when: item.state == "directory" - tags: - - update_config - -- name: 'Copy hacky upgrade script' - template: - src: 'hacky_harbor_upgrade.sh.j2' - dest: '/root/hacky_harbor_upgrade.sh' - owner: 'root' - group: 'root' - mode: '0744' - tags: - - upgrade-helper - -- name: Ensure config template files are populated from templates/harbor - template: - src: "{{ item.src }}" - dest: "{{ service_base_path }}/{{ inventory_hostname }}/{{ item.path | regex_replace('\\.j2$', '') }}" - owner: "{{ docker_owner }}" - group: "{{ docker_group }}" - mode: 0644 - with_filetree: "templates/harbor" - when: item.state == 'file' and item.src is match('.*\.j2$') - tags: - - update_config - -# work around for DEV-271("container start failure after reboot") -- name: Ensure systemd file - template: - src: harbor-systemd.service.j2 - dest: /etc/systemd/system/harbor.service - owner: root - group: root - mode: 0755 - -- name: Ensure config files are populated from from templates/harbor - copy: - src: "{{ item.src }}" - dest: "{{ service_base_path }}/{{ inventory_hostname }}/{{ item.path }}" - owner: "{{ docker_owner }}" - group: "{{ docker_group }}" - mode: 0644 - with_filetree: "templates/harbor" - when: item.state == 'file' and item.src is not match('.*\.j2$') - tags: - - update_config - - - -- name: "Check if harbor tarball exists" - stat: - path: '{{ service_base_path }}/{{ inventory_hostname }}/harbor-offline-installer-{{ harbor_version }}.tgz' - register: harbor_tarball - - - -- name: Download harbor offline installer - ansible.builtin.get_url: - url: https://github.com/goharbor/harbor/releases/download/{{ harbor_version }}/harbor-offline-installer-{{ harbor_version }}.tgz - dest: "{{ service_base_path }}/{{ inventory_hostname }}/harbor-offline-installer-{{ harbor_version }}.tgz" - when: - - not harbor_tarball.stat.exists - -- name: "Check if {{ inventory_hostname }}/harbor/docker-compose.yml exists" - stat: - path: '{{ service_base_path }}/{{ inventory_hostname }}/harbor/docker-compose.yml' - register: harbor_installation - -- name: Extract harbor-offline-installer-{{ harbor_version }}.tgz into {{ service_base_path }}/{{ inventory_hostname }} - ansible.builtin.unarchive: - src: "{{ service_base_path }}/{{ inventory_hostname }}/harbor-offline-installer-{{ harbor_version }}.tgz" - dest: "{{ service_base_path }}/{{ inventory_hostname }}" - remote_src: yes - when: - - not harbor_installation.stat.exists - -- name: "Check if {{ inventory_hostname }}/harbor/docker-compose.yml exists" - stat: - path: '{{ service_base_path }}/harbor/{{ inventory_hostname }}/docker-compose.yml' - register: check_docker_compose_file - tags: - - update_deployment - -- name: "Ensure harbor is running" - systemd: - name: harbor - enabled: yes - state: started - daemon_reload: yes +- name: "Install harbor" + include_tasks: install.yml + args: + apply: + tags: + - harbor-install + +- name: "harbor BASE settings" + block: + - name: "BLOCK: Login with keycloak-admin" + include_role: + name: keycloak + tasks_from: _authenticate + + - name: "GET available clients from <<{{ harbor_base_configuration.oidc_name }}>>-realm" + delegate_to: localhost + become: False + uri: + url: "{{ keycloak_server_url }}/auth/admin/realms/{{ harbor_base_configuration.oidc_name }}/clients" + method: GET + headers: + Content-Type: "application/json" + Authorization: "Bearer {{ access_token }}" + status_code: [200] + register: realm_clients + + # available clients: get needed ID + - set_fact: + id_of_client: '{{ ( realm_clients.json | selectattr("clientId","equalto", harbor_base_configuration.oidc_client_id ) | first ).id }}' + + - name: "BLOCK: GET client-secret for client <<{{ harbor_base_configuration.oidc_client_id }}>> in realm <<{{ harbor_base_configuration.oidc_name }}>>" + delegate_to: localhost + become: False + uri: + url: "{{ keycloak_server_url }}/auth/admin/realms/{{ harbor_base_configuration.oidc_name }}/clients/{{ id_of_client }}/client-secret" + method: GET + headers: + Content-Type: "application/json" + Authorization: "Bearer {{ access_token }}" + status_code: [200] + register: client_secret + + - set_fact: + dict: + oidc_client_secret: '{{ client_secret.json.value }}' + + - set_fact: + harbor_base_configuration_merged: '{{ harbor_base_configuration | combine( dict ,recursive=True ) }}' + + - name: "BLOCK: Configure harbor BASE settings" + include_tasks: configure_base_config.yml + vars: + base_configuration: '{{ harbor_base_configuration_merged }}' + args: + apply: + tags: + - harbor-configure-base +# end of block for base settings diff --git a/templates/harbor/harbor/harbor.yml.j2 b/roles/harbor/templates/harbor.yml.j2 similarity index 98% rename from templates/harbor/harbor/harbor.yml.j2 rename to roles/harbor/templates/harbor.yml.j2 index 269e4d2..ab71ece 100644 --- a/templates/harbor/harbor/harbor.yml.j2 +++ b/roles/harbor/templates/harbor.yml.j2 @@ -2,7 +2,7 @@ # The IP address or hostname to access admin UI and registry service. # DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. -hostname: {{ stage }}-docker-registry-01.{{ domain }} +hostname: {{ harbor_hostname }} # http related config http: @@ -26,7 +26,7 @@ https: # Uncomment external_url if you want to enable external proxy # And when it enabled the hostname will no longer used -external_url: https://{{ stage }}-docker-registry-01.{{ domain }} +external_url: {{ harbor_external_url }} # The initial password of Harbor admin # It only works in first time to install harbor