Skip to content

Commit 4544d82

Browse files
author
Martin Jackson
committed
Merge commit 'f5d726bafeed0f30b39be6d13ed421e51f7e26d3'
2 parents dbaacc9 + f5d726b commit 4544d82

29 files changed

+1147
-183
lines changed

common/.github/workflows/linter.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
# Full git history is needed to get a proper list of changed files within `super-linter`
3535
fetch-depth: 0
3636
- name: Setup helm
37-
uses: azure/setup-helm@v1
37+
uses: azure/setup-helm@v3
3838
# with:
3939
# version: '<version>' # default is latest stable
4040
id: install

common/.gitleaks.toml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.github/linters/.gitleaks.toml

common/Changes.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changes
22

3+
## October 13, 2022
4+
5+
* Added global.clusterVersion as a new helm variable which represents the OCP
6+
Major.Minor cluster version. By default now a user can add a
7+
values-<ocpversion>-<clustergroup>.yaml file to have specific cluster version
8+
overrides (e.g. values-4.10-hub.yaml). Will need Validated Patterns Operator >= 0.0.6
9+
when deploying with the operator. Note: When using the ArgoCD Hub and spoke model,
10+
you cannot have spokes with a different version of OCP than the hub.
11+
312
## October 4, 2022
413

514
* Extended the values-secret.yaml file to support multiple vault paths and re-wrote

common/Makefile

+14-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ TARGET_REPO=$(shell git remote show $(TARGET_ORIGIN) | grep Push | sed -e 's/.*U
1010
# git branch --show-current is also available as of git 2.22, but we will use this for compatibility
1111
TARGET_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
1212
HUBCLUSTER_APPS_DOMAIN=$(shell oc get ingresses.config/cluster -o jsonpath={.spec.domain})
13+
HUBCLUSTER_VERSION=$(shell oc get OpenShiftControllerManager/cluster -o jsonpath='{.status.version}' | sed -n -E 's/([0-9]+).([0-9]+).*/\1.\2/p')
1314

1415
# --set values always take precedence over the contents of -f
1516
HELM_OPTS=-f values-global.yaml --set main.git.repoURL="$(TARGET_REPO)" --set main.git.revision=$(TARGET_BRANCH) \
16-
--set global.hubClusterDomain=$(HUBCLUSTER_APPS_DOMAIN) $(TARGET_SITE_OPT)
17+
--set global.hubClusterDomain=$(HUBCLUSTER_APPS_DOMAIN) --set global.clusterVersion="$(HUBCLUSTER_VERSION)" $(TARGET_SITE_OPT)
1718
TEST_OPTS= -f values-global.yaml --set global.repoURL="https://github.com/pattern-clone/mypattern" \
1819
--set main.git.repoURL="https://github.com/pattern-clone/mypattern" --set main.git.revision=main --set global.pattern="mypattern" \
1920
--set global.namespace="pattern-namespace" --set global.hubClusterDomain=apps.hub.example.com --set global.localClusterDomain=apps.region.example.com --set global.clusterDomain=region.example.com\
@@ -53,11 +54,17 @@ validate-prereq: ## verify pre-requisites
5354
@if ! ansible-galaxy collection list | grep kubernetes.core > /dev/null 2>&1; then echo "Not found"; exit 1; fi
5455
@echo "OK"
5556

57+
# We only check the remote ssh git branch's existance if we're not running inside a container
58+
# as getting ssh auth working inside a container seems a bit brittle
5659
validate-origin: ## verify the git origin is available
5760
@echo Checking repo $(TARGET_REPO) - branch $(TARGET_BRANCH)
58-
@git ls-remote --exit-code --heads $(TARGET_REPO) $(TARGET_BRANCH) >/dev/null && \
59-
echo "$(TARGET_REPO) - $(TARGET_BRANCH) exists" || \
60-
(echo "$(TARGET_BRANCH) not found in $(TARGET_REPO)"; exit 1)
61+
@if [ ! -f /run/.containerenv ]; then\
62+
git ls-remote --exit-code --heads $(TARGET_REPO) $(TARGET_BRANCH) >/dev/null &&\
63+
echo "$(TARGET_REPO) - $(TARGET_BRANCH) exists" ||\
64+
(echo "$(TARGET_BRANCH) not found in $(TARGET_REPO)"; exit 1);\
65+
else\
66+
echo "Running inside a container: Skipping git ssh checks";\
67+
fi
6168

6269
# Default targets are "deploy" and "upgrade"; they can "move" to whichever install mechanism should be default.
6370
# legacy-deploy and legacy-upgrade should be present so that patterns don't need to depend on "deploy" and "upgrade"
@@ -86,12 +93,15 @@ load-secrets: ## loads the secrets into the vault
8693
common/scripts/vault-utils.sh push_secrets common/pattern-vault.init
8794

8895
super-linter: ## Runs super linter locally
96+
rm -rf .mypy_cache
8997
podman run -e RUN_LOCAL=true -e USE_FIND_ALGORITHM=true \
9098
-e VALIDATE_BASH=false \
9199
-e VALIDATE_JSCPD=false \
92100
-e VALIDATE_KUBERNETES_KUBEVAL=false \
93101
-e VALIDATE_YAML=false \
94102
-e VALIDATE_ANSIBLE=false \
103+
-e VALIDATE_DOCKERFILE_HADOLINT=false \
104+
-e VALIDATE_TEKTON=false \
95105
$(DISABLE_LINTERS) \
96106
-v $(PWD):/tmp/lint:rw,z docker.io/github/super-linter:slim-v4
97107

common/acm/templates/policies/application-policies.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ spec:
4545
valueFiles:
4646
- "/values-global.yaml"
4747
- "/values-{{ .name }}.yaml"
48+
# We cannot use $.Values.global.clusterVersion because that gets resolved to the
49+
# hub's cluster version, whereas we want to include the spoke cluster version
50+
- '/values-{{ `{{ printf "%d.%d" ((semver (lookup "operator.openshift.io/v1" "OpenShiftControllerManager" "" "cluster").status.version).Major) ((semver (lookup "operator.openshift.io/v1" "OpenShiftControllerManager" "" "cluster").status.version).Minor) }}` }}-{{ .name }}.yaml'
4851
{{- range $valueFile := .extraValueFiles }}
4952
- {{ $valueFile | quote }}
5053
{{- end }}
@@ -64,6 +67,9 @@ spec:
6467
# Requires ACM 2.6 or higher
6568
- name: global.clusterDomain
6669
value: '{{ `{{ (lookup "config.openshift.io/v1" "Ingress" "" "cluster").spec.domain | replace "apps." "" }}` }}'
70+
# Requires ACM 2.6 or higher (I could not come up with something less terrible to get maj.min)
71+
- name: global.clusterVersion
72+
value: '{{ `{{ printf "%d.%d" ((semver (lookup "operator.openshift.io/v1" "OpenShiftControllerManager" "" "cluster").status.version).Major) ((semver (lookup "operator.openshift.io/v1" "OpenShiftControllerManager" "" "cluster").status.version).Minor) }}` }}'
6773
- name: clusterGroup.name
6874
value: {{ $group.name }}
6975
{{- range .helmOverrides }}

common/ansible/plugins/modules/vault_load_secrets.py

+23-13
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import base64
5454
import os
5555
import subprocess
56+
import time
5657
from collections.abc import MutableMapping
5758

5859
import yaml
@@ -151,27 +152,36 @@ def get_version(syaml):
151152
return syaml.get("version", "1.0")
152153

153154

154-
def run_command(command):
155+
def run_command(command, attempts=1, sleep=3):
155156
"""
156157
Runs a command on the host ansible is running on. A failing command
157158
will raise an exception in this function directly (due to check=True)
158159
159160
Parameters:
160161
command(str): The command to be run.
162+
attempts(int): Number of times to retry in case of Error (defaults to 1)
163+
sleep(int): Number of seconds to wait in between retry attempts (defaults to 3s)
161164
162165
Returns:
163166
ret(subprocess.CompletedProcess): The return value from run()
164167
"""
165-
ret = subprocess.run(
166-
command,
167-
shell=True,
168-
env=os.environ.copy(),
169-
stdout=subprocess.PIPE,
170-
stderr=subprocess.PIPE,
171-
universal_newlines=True,
172-
check=True,
173-
)
174-
return ret
168+
for attempt in range(attempts):
169+
try:
170+
ret = subprocess.run(
171+
command,
172+
shell=True,
173+
env=os.environ.copy(),
174+
stdout=subprocess.PIPE,
175+
stderr=subprocess.PIPE,
176+
universal_newlines=True,
177+
check=True,
178+
)
179+
return ret
180+
except subprocess.CalledProcessError as e:
181+
# We reached maximum nr of retries. Re-raise the last error
182+
if attempt >= attempts - 1:
183+
raise e
184+
time.sleep(sleep)
175185

176186

177187
def flatten(dictionary, parent_key=False, separator="."):
@@ -366,7 +376,7 @@ def inject_secrets(module, syaml, namespace, pod, basepath):
366376
f"oc exec -n {namespace} {pod} -i -- sh -c "
367377
f"\"vault kv put '{path}/{secret}' {properties}\""
368378
)
369-
run_command(cmd)
379+
run_command(cmd, attempts=3)
370380
counter += 1
371381

372382
for i in get_secrets_vault_paths(module, syaml, "files"):
@@ -380,7 +390,7 @@ def inject_secrets(module, syaml, namespace, pod, basepath):
380390
f"vault kv put {path}/{filekey} b64content=- content=@/tmp/vcontent; "
381391
f"rm /tmp/vcontent'"
382392
)
383-
run_command(cmd)
393+
run_command(cmd, attempts=3)
384394
counter += 1
385395
return counter
386396

common/ansible/roles/vault_utils/tasks/push_secrets.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
secret_template: "{{ pattern_dir }}/values-secret.yaml.template"
4040

4141
- name: Loads secrets file into the vault of a cluster
42+
no_log: true
4243
vault_load_secrets:
4344
values_secrets: ~/values-secret.yaml
4445
check_missing_secrets: false

common/ansible/roles/vault_utils/tasks/vault_init.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
# We need to retry here because the vault service might be starting
3838
# and can return a 500 internal server until its state is fully ready
3939
- name: Init vault operator
40+
no_log: true
4041
kubernetes.core.k8s_exec:
4142
namespace: "{{ vault_ns }}"
4243
pod: "{{ vault_pod }}"
@@ -48,6 +49,7 @@
4849
when: not vault_initialized
4950

5051
- name: Set vault init output json fact
52+
no_log: true
5153
ansible.builtin.set_fact:
5254
vault_init_json: "{{ vault_init_json_out.stdout | from_json }}"
5355
when: not vault_initialized
@@ -56,6 +58,7 @@
5658
# the vault was not already initialized *and* when unseal_from_cluster
5759
# is set to false
5860
- name: Save vault operator output (local file)
61+
no_log: true
5962
ansible.builtin.copy:
6063
follow: true
6164
dest: "{{ output_file_abs }}"
@@ -69,6 +72,7 @@
6972
# the cluster when the vault was not already initialized *and* when
7073
# unseal_from_cluster is set to true
7174
- name: Save vault operator output (into a secret inside the cluster)
75+
no_log: true
7276
kubernetes.core.k8s:
7377
state: present
7478
definition:

common/ansible/roles/vault_utils/tasks/vault_secrets_init.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
when: kubernetes_enabled.rc != 0
3535

3636
- name: Get token from service account secret {{ external_secrets_ns }}/{{ external_secrets_secret }}
37+
no_log: true
3738
kubernetes.core.k8s_info:
3839
kind: Secret
3940
namespace: "{{ external_secrets_ns }}"
@@ -43,10 +44,12 @@
4344
failed_when: token_data.resources | length == 0
4445

4546
- name: Set sa_token fact
47+
no_log: true
4648
ansible.builtin.set_fact:
4749
sa_token: "{{ token_data.resources[0].data.token | b64decode }}"
4850

4951
- name: Configure hub kubernetes backend
52+
no_log: true
5053
kubernetes.core.k8s_exec:
5154
namespace: "{{ vault_ns }}"
5255
pod: "{{ vault_pod }}"

common/ansible/tests/unit/test_vault_load_secrets.py

+26-13
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,12 @@ def test_ensure_empty_files_but_not_secrets_is_ok(self):
117117

118118
calls = [
119119
call(
120-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/config-demo' secret='VALUE'\"" # noqa: E501
120+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/config-demo' secret='VALUE'\"", # noqa: E501
121+
attempts=3,
121122
),
122123
call(
123-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/aws' access_key_id='VALUE' secret_access_key='VALUE'\"" # noqa: E501
124+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/aws' access_key_id='VALUE' secret_access_key='VALUE'\"", # noqa: E501
125+
attempts=3,
124126
),
125127
]
126128
mock_run_command.assert_has_calls(calls)
@@ -163,7 +165,8 @@ def test_ensure_empty_secrets_but_not_files_is_ok(self):
163165

164166
calls = [
165167
call(
166-
"cat '/home/michele/.ssh/id_rsa.pub' | oc exec -n vault vault-0 -i -- sh -c 'cat - > /tmp/vcontent'; oc exec -n vault vault-0 -i -- sh -c 'base64 --wrap=0 /tmp/vcontent | vault kv put secret/hub/publickey b64content=- content=@/tmp/vcontent; rm /tmp/vcontent'" # noqa: E501
168+
"cat '/home/michele/.ssh/id_rsa.pub' | oc exec -n vault vault-0 -i -- sh -c 'cat - > /tmp/vcontent'; oc exec -n vault vault-0 -i -- sh -c 'base64 --wrap=0 /tmp/vcontent | vault kv put secret/hub/publickey b64content=- content=@/tmp/vcontent; rm /tmp/vcontent'", # noqa: E501
169+
attempts=3,
167170
),
168171
]
169172
mock_run_command.assert_has_calls(calls)
@@ -188,31 +191,40 @@ def test_ensure_command_called(self):
188191

189192
calls = [
190193
call(
191-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/config-demo' secret='demo123'\"" # noqa: E501
194+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/config-demo' secret='demo123'\"", # noqa: E501
195+
attempts=3,
192196
),
193197
call(
194-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/googleapi' key='lskdjflskjdflsdjflsdkjfldsjkfldsj'\"" # noqa: E501
198+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/googleapi' key='lskdjflskjdflsdjflsdkjfldsjkfldsj'\"", # noqa: E501
199+
attempts=3,
195200
),
196201
call(
197-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/cluster_alejandro' name='alejandro' bearerToken='sha256~bumxi-012345678901233455675678678098-abcdef'\"" # noqa: E501
202+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/cluster_alejandro' name='alejandro' bearerToken='sha256~bumxi-012345678901233455675678678098-abcdef'\"", # noqa: E501
203+
attempts=3,
198204
),
199205
call(
200-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/test' s3.accessKey='1234' s3.secretKey='4321' s3Secret='czMuYWNjZXNzS2V5OiAxMjM0CnMzLnNlY3JldEtleTogNDMyMQ=='\"" # noqa: E501
206+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/test' s3.accessKey='1234' s3.secretKey='4321' s3Secret='czMuYWNjZXNzS2V5OiAxMjM0CnMzLnNlY3JldEtleTogNDMyMQ=='\"", # noqa: E501
207+
attempts=3,
201208
),
202209
call(
203-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/test2' s3.accessKey='accessKey' s3.secretKey='secretKey' s3Secret='fooo'\"" # noqa: E501
210+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/test2' s3.accessKey='accessKey' s3.secretKey='secretKey' s3Secret='fooo'\"", # noqa: E501
211+
attempts=3,
204212
),
205213
call(
206-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/test3' s3.accessKey='aaaaa' s3.secretKey='bbbbbbbb' s3Secret='czMuYWNjZXNzS2V5OiBhYWFhYQpzMy5zZWNyZXRLZXk6IGJiYmJiYmJi'\"" # noqa: E501
214+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/test3' s3.accessKey='aaaaa' s3.secretKey='bbbbbbbb' s3Secret='czMuYWNjZXNzS2V5OiBhYWFhYQpzMy5zZWNyZXRLZXk6IGJiYmJiYmJi'\"", # noqa: E501
215+
attempts=3,
207216
),
208217
call(
209-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/region-one/config-demo' secret='region123'\"" # noqa: E501
218+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/region-one/config-demo' secret='region123'\"", # noqa: E501
219+
attempts=3,
210220
),
211221
call(
212-
"cat '/home/michele/ca.crt' | oc exec -n vault vault-0 -i -- sh -c 'cat - > /tmp/vcontent'; oc exec -n vault vault-0 -i -- sh -c 'base64 --wrap=0 /tmp/vcontent | vault kv put secret/hub/cluster_alejandro_ca b64content=- content=@/tmp/vcontent; rm /tmp/vcontent'" # noqa: E501
222+
"cat '/home/michele/ca.crt' | oc exec -n vault vault-0 -i -- sh -c 'cat - > /tmp/vcontent'; oc exec -n vault vault-0 -i -- sh -c 'base64 --wrap=0 /tmp/vcontent | vault kv put secret/hub/cluster_alejandro_ca b64content=- content=@/tmp/vcontent; rm /tmp/vcontent'", # noqa: E501
223+
attempts=3,
213224
),
214225
call(
215-
"cat '/home/michele/ca.crt' | oc exec -n vault vault-0 -i -- sh -c 'cat - > /tmp/vcontent'; oc exec -n vault vault-0 -i -- sh -c 'base64 --wrap=0 /tmp/vcontent | vault kv put secret/region-one/ca b64content=- content=@/tmp/vcontent; rm /tmp/vcontent'" # noqa: E501
226+
"cat '/home/michele/ca.crt' | oc exec -n vault vault-0 -i -- sh -c 'cat - > /tmp/vcontent'; oc exec -n vault vault-0 -i -- sh -c 'base64 --wrap=0 /tmp/vcontent | vault kv put secret/region-one/ca b64content=- content=@/tmp/vcontent; rm /tmp/vcontent'", # noqa: E501
227+
attempts=3,
216228
),
217229
]
218230
mock_run_command.assert_has_calls(calls)
@@ -242,7 +254,8 @@ def test_ensure_good_template_checking(self):
242254

243255
calls = [
244256
call(
245-
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/config-demo' secret='VALUE' additionalsecret='test'\"" # noqa: E501
257+
"oc exec -n vault vault-0 -i -- sh -c \"vault kv put 'secret/hub/config-demo' secret='VALUE' additionalsecret='test'\"", # noqa: E501
258+
attempts=3,
246259
),
247260
]
248261
mock_run_command.assert_has_calls(calls)

common/clustergroup/templates/core/namespaces.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ apiVersion: v1
44
kind: Namespace
55
metadata:
66
labels:
7-
name: {{ default "pattern" $.Release.name }}
87
argocd.argoproj.io/managed-by: {{ $.Values.global.pattern }}-{{ $.Values.clusterGroup.name }}
98
name: {{ . }}
109
spec:

common/clustergroup/templates/plumbing/applications.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ spec:
5656
ignoreMissingValueFiles: true
5757
valueFiles:
5858
- "values.yaml"
59+
{{- if $.Values.global.clusterVersion }}
60+
- "/values-{{ $.Values.global.clusterVersion }}-{{ $.Values.clusterGroup.name }}.yaml"
61+
{{- end }}
5962
{{- range .extraValueFiles }}
6063
- {{ . | quote }}
6164
{{- end }}
@@ -66,6 +69,8 @@ spec:
6669
parameters:
6770
- name: global.clusterDomain
6871
value: {{ $.Values.global.clusterDomain }}
72+
- name: global.clusterVersion
73+
value: "{{ $.Values.global.clusterVersion }}"
6974
- name: global.hubClusterDomain
7075
value: {{ $.Values.global.hubClusterDomain }}
7176
- name: global.localClusterDomain
@@ -144,6 +149,9 @@ spec:
144149
valueFiles:
145150
- "/values-global.yaml"
146151
- "/values-{{ $.Values.clusterGroup.name }}.yaml"
152+
{{- if $.Values.global.clusterVersion }}
153+
- "/values-{{ $.Values.global.clusterVersion }}-{{ $.Values.clusterGroup.name }}.yaml"
154+
{{- end }}
147155
{{- range $valueFile := .extraValueFiles }}
148156
- {{ $valueFile | quote }}
149157
{{- end }}
@@ -159,6 +167,8 @@ spec:
159167
value: {{ $.Values.global.pattern }}
160168
- name: global.clusterDomain
161169
value: {{ $.Values.global.clusterDomain }}
170+
- name: global.clusterVersion
171+
value: "{{ $.Values.global.clusterVersion }}"
162172
- name: global.hubClusterDomain
163173
value: {{ $.Values.global.hubClusterDomain }}
164174
- name: global.localClusterDomain

common/examples/values-example.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ clusterGroup:
7676
aws:
7777
region: ap-southeast-2
7878
clusters:
79-
- One
79+
- one
8080
exampleAzurePool:
8181
name: azure-us
8282
openshiftVersion: 4.10.18
@@ -86,8 +86,8 @@ clusterGroup:
8686
baseDomainResourceGroupName: dojo-dns-zones
8787
region: eastus
8888
clusters:
89-
- Two
90-
- Three
89+
- two
90+
- three
9191
acmlabels:
9292
- name: clusterGroup
9393
value: region

common/golang-external-secrets/Chart.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ name: golang-external-secrets
66
version: 0.0.1
77
dependencies:
88
- name: external-secrets
9-
version: "0.5.9"
9+
version: "0.6.0"
1010
repository: "https://charts.external-secrets.io"
1111
#"https://external-secrets.github.io/kubernetes-external-secrets"
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Subchart Update
2+
3+
When updating this sub-chart, please remember to tweak the image tag in values.yaml.
4+
That is because we want to use -ubi images if possible and there is no suffix option, so
5+
we just override the tag with the version + "-ubi"
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)