Add preflight check;Refactor readychecks;Quote input variables;Fix kustomization template;Apply kustomization;Generate new cluster-api manifest
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
31a91d826f
commit
d343b84b30
@ -8,6 +8,7 @@
|
|||||||
roles:
|
roles:
|
||||||
- vapp
|
- vapp
|
||||||
- network
|
- network
|
||||||
|
- preflight
|
||||||
- users
|
- users
|
||||||
- disks
|
- disks
|
||||||
- metacluster
|
- metacluster
|
||||||
|
@ -1,122 +1,141 @@
|
|||||||
- name: Install step-ca chart
|
- block:
|
||||||
kubernetes.core.helm:
|
|
||||||
name: step-certificates
|
|
||||||
chart_ref: /opt/metacluster/helm-charts/step-certificates
|
|
||||||
release_namespace: step-ca
|
|
||||||
create_namespace: yes
|
|
||||||
wait: yes
|
|
||||||
kubeconfig: "{{ kubeconfig.path }}"
|
|
||||||
values: "{{ components.stepcertificates.chart_values }}"
|
|
||||||
|
|
||||||
- name: Retrieve configmap w/ root certificate
|
- name: Install step-ca chart
|
||||||
kubernetes.core.k8s_info:
|
kubernetes.core.helm:
|
||||||
kind: ConfigMap
|
name: step-certificates
|
||||||
name: step-certificates-certs
|
chart_ref: /opt/metacluster/helm-charts/step-certificates
|
||||||
namespace: step-ca
|
release_namespace: step-ca
|
||||||
kubeconfig: "{{ kubeconfig.path }}"
|
create_namespace: yes
|
||||||
register: stepca_cm_certs
|
wait: no
|
||||||
|
kubeconfig: "{{ kubeconfig.path }}"
|
||||||
|
values: "{{ components.stepcertificates.chart_values }}"
|
||||||
|
|
||||||
- name: Create target namespaces
|
- name: Ensure step-ca API availability
|
||||||
kubernetes.core.k8s:
|
ansible.builtin.uri:
|
||||||
kind: Namespace
|
url: https://ca.{{ vapp['metacluster.fqdn'] }}/health
|
||||||
name: "{{ item }}"
|
method: GET
|
||||||
state: present
|
register: api_readycheck
|
||||||
kubeconfig: "{{ kubeconfig.path }}"
|
until:
|
||||||
loop:
|
- api_readycheck.json.status is defined
|
||||||
- argo-cd
|
- api_readycheck.json.status == 'ok'
|
||||||
# - kube-system
|
retries: 5
|
||||||
|
delay: 60
|
||||||
|
|
||||||
- name: Store root certificate in namespaced configmaps/secrets
|
- name: Retrieve configmap w/ root certificate
|
||||||
kubernetes.core.k8s:
|
kubernetes.core.k8s_info:
|
||||||
state: present
|
kind: ConfigMap
|
||||||
template: "{{ item.kind }}.j2"
|
name: step-certificates-certs
|
||||||
kubeconfig: "{{ kubeconfig.path }}"
|
namespace: step-ca
|
||||||
vars:
|
kubeconfig: "{{ kubeconfig.path }}"
|
||||||
_template:
|
register: stepca_cm_certs
|
||||||
name: "{{ item.name }}"
|
|
||||||
namespace: "{{ item.namespace }}"
|
|
||||||
annotations: "{{ item.annotations | default('{}') | indent(width=4, first=True) }}"
|
|
||||||
labels: "{{ item.labels | default('{}') | indent(width=4, first=True) }}"
|
|
||||||
data: "{{ item.data }}"
|
|
||||||
loop:
|
|
||||||
- name: argocd-tls-certs-cm
|
|
||||||
namespace: argo-cd
|
|
||||||
kind: configmap
|
|
||||||
annotations: |
|
|
||||||
meta.helm.sh/release-name: argo-cd
|
|
||||||
meta.helm.sh/release-namespace: argo-cd
|
|
||||||
labels: |
|
|
||||||
app.kubernetes.io/managed-by: Helm
|
|
||||||
app.kubernetes.io/name: argocd-cm
|
|
||||||
app.kubernetes.io/part-of: argocd
|
|
||||||
data:
|
|
||||||
- key: git.{{ vapp['metacluster.fqdn'] }}
|
|
||||||
value: "{{ stepca_cm_certs.resources[0].data['root_ca.crt'] }}"
|
|
||||||
- name: step-certificates-certs
|
|
||||||
namespace: kube-system
|
|
||||||
kind: secret
|
|
||||||
data:
|
|
||||||
- key: root_ca.crt
|
|
||||||
value: "{{ stepca_cm_certs.resources[0].data['root_ca.crt'] | b64encode }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.kind + '/' + item.name + ' (' + item.namespace + ')' }}"
|
|
||||||
|
|
||||||
- name: Configure step-ca passthrough ingress
|
- name: Create target namespaces
|
||||||
ansible.builtin.template:
|
kubernetes.core.k8s:
|
||||||
src: ingressroutetcp.j2
|
kind: Namespace
|
||||||
dest: /var/lib/rancher/k3s/server/manifests/{{ _template.name }}-manifest.yaml
|
name: "{{ item }}"
|
||||||
owner: root
|
state: present
|
||||||
group: root
|
kubeconfig: "{{ kubeconfig.path }}"
|
||||||
mode: 0600
|
loop:
|
||||||
vars:
|
- argo-cd
|
||||||
_template:
|
# - kube-system
|
||||||
name: step-ca
|
|
||||||
namespace: step-ca
|
|
||||||
config: |2
|
|
||||||
entryPoints:
|
|
||||||
- websecure
|
|
||||||
routes:
|
|
||||||
- match: HostSNI(`ca.{{ vapp['metacluster.fqdn'] }}`)
|
|
||||||
services:
|
|
||||||
- name: step-certificates
|
|
||||||
port: 443
|
|
||||||
tls:
|
|
||||||
passthrough: true
|
|
||||||
notify:
|
|
||||||
- Apply manifests
|
|
||||||
|
|
||||||
- name: Inject step-ca certificate into traefik container
|
- name: Store root certificate in namespaced configmaps/secrets
|
||||||
ansible.builtin.blockinfile:
|
kubernetes.core.k8s:
|
||||||
path: /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
|
state: present
|
||||||
block: |2
|
template: "{{ item.kind }}.j2"
|
||||||
volumes:
|
kubeconfig: "{{ kubeconfig.path }}"
|
||||||
- name: step-certificates-certs
|
vars:
|
||||||
mountPath: /step-ca
|
_template:
|
||||||
type: secret
|
name: "{{ item.name }}"
|
||||||
env:
|
namespace: "{{ item.namespace }}"
|
||||||
- name: LEGO_CA_CERTIFICATES
|
annotations: "{{ item.annotations | default('{}') | indent(width=4, first=True) }}"
|
||||||
value: /step-ca/root_ca.crt
|
labels: "{{ item.labels | default('{}') | indent(width=4, first=True) }}"
|
||||||
marker: ' # {mark} ANSIBLE MANAGED BLOCK'
|
data: "{{ item.data }}"
|
||||||
notify:
|
loop:
|
||||||
- Apply manifests
|
- name: argocd-tls-certs-cm
|
||||||
|
namespace: argo-cd
|
||||||
|
kind: configmap
|
||||||
|
annotations: |
|
||||||
|
meta.helm.sh/release-name: argo-cd
|
||||||
|
meta.helm.sh/release-namespace: argo-cd
|
||||||
|
labels: |
|
||||||
|
app.kubernetes.io/managed-by: Helm
|
||||||
|
app.kubernetes.io/name: argocd-cm
|
||||||
|
app.kubernetes.io/part-of: argocd
|
||||||
|
data:
|
||||||
|
- key: git.{{ vapp['metacluster.fqdn'] }}
|
||||||
|
value: "{{ stepca_cm_certs.resources[0].data['root_ca.crt'] }}"
|
||||||
|
- name: step-certificates-certs
|
||||||
|
namespace: kube-system
|
||||||
|
kind: secret
|
||||||
|
data:
|
||||||
|
- key: root_ca.crt
|
||||||
|
value: "{{ stepca_cm_certs.resources[0].data['root_ca.crt'] | b64encode }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.kind + '/' + item.name + ' (' + item.namespace + ')' }}"
|
||||||
|
|
||||||
- name: Trigger handlers
|
- name: Configure step-ca passthrough ingress
|
||||||
ansible.builtin.meta: flush_handlers
|
ansible.builtin.template:
|
||||||
|
src: ingressroutetcp.j2
|
||||||
|
dest: /var/lib/rancher/k3s/server/manifests/{{ _template.name }}-manifest.yaml
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: 0600
|
||||||
|
vars:
|
||||||
|
_template:
|
||||||
|
name: step-ca
|
||||||
|
namespace: step-ca
|
||||||
|
config: |2
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`ca.{{ vapp['metacluster.fqdn'] }}`)
|
||||||
|
services:
|
||||||
|
- name: step-certificates
|
||||||
|
port: 443
|
||||||
|
tls:
|
||||||
|
passthrough: true
|
||||||
|
notify:
|
||||||
|
- Apply manifests
|
||||||
|
|
||||||
- name: Retrieve step-ca configuration
|
- name: Inject step-ca certificate into traefik container
|
||||||
kubernetes.core.k8s_info:
|
ansible.builtin.blockinfile:
|
||||||
kind: ConfigMap
|
path: /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
|
||||||
name: step-certificates-config
|
block: |2
|
||||||
namespace: step-ca
|
volumes:
|
||||||
kubeconfig: "{{ kubeconfig.path }}"
|
- name: step-certificates-certs
|
||||||
register: stepca_cm_config
|
mountPath: /step-ca
|
||||||
|
type: secret
|
||||||
|
env:
|
||||||
|
- name: LEGO_CA_CERTIFICATES
|
||||||
|
value: /step-ca/root_ca.crt
|
||||||
|
marker: ' # {mark} ANSIBLE MANAGED BLOCK'
|
||||||
|
notify:
|
||||||
|
- Apply manifests
|
||||||
|
|
||||||
- name: Install root CA in system truststore
|
- name: Trigger handlers
|
||||||
ansible.builtin.shell:
|
ansible.builtin.meta: flush_handlers
|
||||||
cmd: >-
|
|
||||||
step ca bootstrap \
|
- name: Retrieve step-ca configuration
|
||||||
--ca-url=https://ca.{{ vapp['metacluster.fqdn'] }} \
|
kubernetes.core.k8s_info:
|
||||||
--fingerprint={{ stepca_cm_config.resources[0].data['defaults.json'] | from_json | json_query('fingerprint') }} \
|
kind: ConfigMap
|
||||||
--install \
|
name: step-certificates-config
|
||||||
--force
|
namespace: step-ca
|
||||||
update-ca-certificates
|
kubeconfig: "{{ kubeconfig.path }}"
|
||||||
|
register: stepca_cm_config
|
||||||
|
|
||||||
|
- name: Install root CA in system truststore
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: >-
|
||||||
|
step ca bootstrap \
|
||||||
|
--ca-url=https://ca.{{ vapp['metacluster.fqdn'] }} \
|
||||||
|
--fingerprint={{ stepca_cm_config.resources[0].data['defaults.json'] | from_json | json_query('fingerprint') }} \
|
||||||
|
--install \
|
||||||
|
--force
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
|
module_defaults:
|
||||||
|
ansible.builtin.uri:
|
||||||
|
validate_certs: no
|
||||||
|
status_code: [200, 201]
|
||||||
|
body_format: json
|
||||||
|
@ -15,9 +15,11 @@
|
|||||||
url: https://git.{{ vapp['metacluster.fqdn'] }}/api/healthz
|
url: https://git.{{ vapp['metacluster.fqdn'] }}/api/healthz
|
||||||
method: GET
|
method: GET
|
||||||
register: api_readycheck
|
register: api_readycheck
|
||||||
until: api_readycheck.json.status is defined
|
until:
|
||||||
|
- api_readycheck.json.status is defined
|
||||||
|
- api_readycheck.json.status == 'pass'
|
||||||
retries: 5
|
retries: 5
|
||||||
delay: 30
|
delay: 60
|
||||||
|
|
||||||
- name: Configure additional SSH ingress
|
- name: Configure additional SSH ingress
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
chart_ref: /opt/metacluster/helm-charts/argo-cd
|
chart_ref: /opt/metacluster/helm-charts/argo-cd
|
||||||
release_namespace: argo-cd
|
release_namespace: argo-cd
|
||||||
create_namespace: yes
|
create_namespace: yes
|
||||||
wait: yes
|
wait: no
|
||||||
kubeconfig: "{{ kubeconfig.path }}"
|
kubeconfig: "{{ kubeconfig.path }}"
|
||||||
values: "{{ components.argocd.chart_values }}"
|
values: "{{ components.argocd.chart_values }}"
|
||||||
|
|
||||||
@ -15,9 +15,10 @@
|
|||||||
url: https://gitops.{{ vapp['metacluster.fqdn'] }}/api/version
|
url: https://gitops.{{ vapp['metacluster.fqdn'] }}/api/version
|
||||||
method: GET
|
method: GET
|
||||||
register: api_readycheck
|
register: api_readycheck
|
||||||
until: api_readycheck.json.Version is defined
|
until:
|
||||||
|
- api_readycheck.json.Version is defined
|
||||||
retries: 5
|
retries: 5
|
||||||
delay: 30
|
delay: 60
|
||||||
|
|
||||||
- name: Generate argo-cd API token
|
- name: Generate argo-cd API token
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
chart_ref: /opt/metacluster/helm-charts/harbor
|
chart_ref: /opt/metacluster/helm-charts/harbor
|
||||||
release_namespace: harbor
|
release_namespace: harbor
|
||||||
create_namespace: yes
|
create_namespace: yes
|
||||||
wait: yes
|
wait: no
|
||||||
kubeconfig: "{{ kubeconfig.path }}"
|
kubeconfig: "{{ kubeconfig.path }}"
|
||||||
values: "{{ components.harbor.chart_values }}"
|
values: "{{ components.harbor.chart_values }}"
|
||||||
|
|
||||||
@ -19,7 +19,7 @@
|
|||||||
- api_readycheck.json.status is defined
|
- api_readycheck.json.status is defined
|
||||||
- api_readycheck.json.status == 'healthy'
|
- api_readycheck.json.status == 'healthy'
|
||||||
retries: 5
|
retries: 5
|
||||||
delay: 30
|
delay: 60
|
||||||
|
|
||||||
- name: Push images to registry
|
- name: Push images to registry
|
||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
until:
|
until:
|
||||||
- api_readycheck is not failed
|
- api_readycheck is not failed
|
||||||
retries: 5
|
retries: 5
|
||||||
delay: 30
|
delay: 60
|
||||||
|
|
||||||
module_defaults:
|
module_defaults:
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
- name: Check for vCenter connectivity
|
||||||
|
community.vmware.vmware_vcenter_settings_info:
|
||||||
|
hostname: "{{ vapp['hv.fqdn'] }}"
|
||||||
|
username: "{{ vapp['hv.username'] }}"
|
||||||
|
password: "{{ vapp['hv.password'] }}"
|
||||||
|
schema: vsphere
|
||||||
|
register: vcenter_info
|
@ -55,6 +55,11 @@
|
|||||||
- update-ca-certificates
|
- update-ca-certificates
|
||||||
- bash /root/network.sh
|
- bash /root/network.sh
|
||||||
|
|
||||||
|
- name: Store custom cluster-template
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /opt/metacluster/cluster-api/custom-cluster-template.yaml
|
||||||
|
content: "{{ lookup('kubernetes.core.kustomize', dir='/opt/metacluster/cluster-api/infrastructure-vsphere/' + {{ components.clusterapi.management.version.infrastructure_vsphere }}) }}"
|
||||||
|
|
||||||
- name: Initialize Cluster API management cluster
|
- name: Initialize Cluster API management cluster
|
||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
cmd: >-
|
cmd: >-
|
||||||
@ -65,3 +70,29 @@
|
|||||||
--config ./clusterctl.yaml \
|
--config ./clusterctl.yaml \
|
||||||
--kubeconfig {{ kubeconfig.path }}
|
--kubeconfig {{ kubeconfig.path }}
|
||||||
chdir: /opt/metacluster/cluster-api
|
chdir: /opt/metacluster/cluster-api
|
||||||
|
|
||||||
|
- name: Parse vApp for workload cluster sizing
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
clustersize: >-
|
||||||
|
{{ {
|
||||||
|
'controlplane': vapp['deployment.type'] | regex_findall('^cp(\d)+') | first,
|
||||||
|
'workers': vapp['deployment.type'] | regex_findall('w(\d)+$') | first
|
||||||
|
} }}
|
||||||
|
|
||||||
|
- name: Generate workload cluster manifest
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: >-
|
||||||
|
clusterctl generate cluster \
|
||||||
|
vapp['workloadcluster.name'] \
|
||||||
|
--control-plane-machine-count {{ clustersize.controlplane }} \
|
||||||
|
--worker-machine-count {{ clustersize.workers }} \
|
||||||
|
--from ./custom-cluster-template.yaml \
|
||||||
|
--config ./clusterctl.yaml \
|
||||||
|
--kubeconfig {{ kubeconfig.path }}
|
||||||
|
chdir: /opt/metacluster/cluster-api
|
||||||
|
register: clusterctl_newcluster
|
||||||
|
|
||||||
|
- name: Save workload cluster manifest
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /opt/metacluster/cluster-api/new-cluster.yaml
|
||||||
|
content: "{{ clusterctl_newcluster.stdout }}"
|
||||||
|
@ -60,12 +60,12 @@
|
|||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
cmd: >-
|
cmd: >-
|
||||||
npp-prepper \
|
npp-prepper \
|
||||||
--server {{ vapp['hv.fqdn'] }} \
|
--server "{{ vapp['hv.fqdn'] }}" \
|
||||||
--username {{ vapp['hv.username'] }} \
|
--username "{{ vapp['hv.username'] }}" \
|
||||||
--password {{ vapp['hv.password'] }} \
|
--password "{{ vapp['hv.password'] }}" \
|
||||||
dc \
|
dc \
|
||||||
--name {{ vcenter_info.datacenter }} \
|
--name "{{ vcenter_info.datacenter }}" \
|
||||||
--portgroup {{ vcenter_info.network }} \
|
--portgroup "{{ vcenter_info.network }}" \
|
||||||
--startaddress {{ vapp['ippool.startip'] }} \
|
--startaddress {{ vapp['ippool.startip'] }} \
|
||||||
--endaddress {{ vapp['ippool.endip'] }} \
|
--endaddress {{ vapp['ippool.endip'] }} \
|
||||||
--netmask {{ (vapp['guestinfo.ipaddress'] + '/' + vapp['guestinfo.prefixlength']) | ansible.utils.ipaddr('netmask') }} \
|
--netmask {{ (vapp['guestinfo.ipaddress'] + '/' + vapp['guestinfo.prefixlength']) | ansible.utils.ipaddr('netmask') }} \
|
||||||
|
@ -41,13 +41,13 @@
|
|||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
cmd: >-
|
cmd: >-
|
||||||
npp-prepper \
|
npp-prepper \
|
||||||
--server {{ vapp['hv.fqdn'] }} \
|
--server "{{ vapp['hv.fqdn'] }}" \
|
||||||
--username {{ vapp['hv.username'] }} \
|
--username "{{ vapp['hv.username'] }}" \
|
||||||
--password {{ vapp['hv.password'] }} \
|
--password "{{ vapp['hv.password'] }}" \
|
||||||
vm \
|
vm \
|
||||||
--datacenter {{ vcenter_info.datacenter }} \
|
--datacenter "{{ vcenter_info.datacenter }}" \
|
||||||
--portgroup {{ vcenter_info.network }} \
|
--portgroup "{{ vcenter_info.network }}" \
|
||||||
--name {{ item.instance.hw_name }}
|
--name "{{ item.instance.hw_name }}"
|
||||||
when: existing_ova.results[index] is failed
|
when: existing_ova.results[index] is failed
|
||||||
loop: "{{ ova_deploy.results }}"
|
loop: "{{ ova_deploy.results }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
|
@ -39,12 +39,14 @@ patchesStrategicMerge:
|
|||||||
content: |
|
content: |
|
||||||
{{ _template.script.encoded }}
|
{{ _template.script.encoded }}
|
||||||
permissions: '0744'
|
permissions: '0744'
|
||||||
|
owner: root:root
|
||||||
|
path: /root/network.sh
|
||||||
- content: |
|
- content: |
|
||||||
network: {config: disabled}
|
network: {config: disabled}
|
||||||
owner: root:root
|
owner: root:root
|
||||||
path: /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
|
path: /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
|
||||||
- content: |
|
- content: |
|
||||||
{{ _template.rootca | indent(width=6, first=True) }}
|
{{ _template.rootca | indent(width=14, first=False) | trim }}
|
||||||
owner: root:root
|
owner: root:root
|
||||||
path: /usr/local/share/ca-certificates/root_ca.crt
|
path: /usr/local/share/ca-certificates/root_ca.crt
|
||||||
|
|
||||||
@ -61,6 +63,8 @@ patchesJson6902:
|
|||||||
encoding: base64
|
encoding: base64
|
||||||
content: |
|
content: |
|
||||||
{{ _template.script.encoded }}
|
{{ _template.script.encoded }}
|
||||||
|
owner: root:root
|
||||||
|
path: /root/network.sh
|
||||||
permissions: '0744'
|
permissions: '0744'
|
||||||
- op: add
|
- op: add
|
||||||
path: /spec/kubeadmConfigSpec/files/-
|
path: /spec/kubeadmConfigSpec/files/-
|
||||||
@ -73,7 +77,7 @@ patchesJson6902:
|
|||||||
path: /spec/kubeadmConfigSpec/files/-
|
path: /spec/kubeadmConfigSpec/files/-
|
||||||
value:
|
value:
|
||||||
content: |
|
content: |
|
||||||
{{ _template.rootca | indent(width=8, first=True) }}
|
{{ _template.rootca | indent(width=12, first=False) | trim }}
|
||||||
owner: root:root
|
owner: root:root
|
||||||
path: /usr/local/share/ca-certificates/root_ca.crt
|
path: /usr/local/share/ca-certificates/root_ca.crt
|
||||||
- target:
|
- target:
|
||||||
@ -95,6 +99,6 @@ patchesJson6902:
|
|||||||
patch: |-
|
patch: |-
|
||||||
{% for cmd in _template.runcmds %}
|
{% for cmd in _template.runcmds %}
|
||||||
- op: add
|
- op: add
|
||||||
path: /spec/template/spec/preKubeadmCommands/-
|
path: /spec/kubeadmConfigSpec/preKubeadmCommands/-
|
||||||
value: {{ cmd }}
|
value: {{ cmd }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
Loading…
Reference in New Issue
Block a user