Skip to content

Commit 5ca1998

Browse files
authored
Merge pull request #43 from kudulab/dc-fixes
Fixes for the latest docker-compose versions
2 parents c97e309 + 3b2b9d2 commit 5ca1998

19 files changed

+106
-94
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
- run:
4848
name: Install dojo
4949
command: |
50-
version="0.10.5"
50+
version="0.13.0"
5151
wget -O /tmp/dojo https://github.com/kudulab/dojo/releases/download/${version}/dojo_linux_amd64
5252
chmod +x /tmp/dojo
5353
sudo mv /tmp/dojo /usr/bin/dojo
@@ -69,7 +69,7 @@ jobs:
6969
- run:
7070
name: Install dojo
7171
command: |
72-
version="0.10.5"
72+
version="0.13.0"
7373
wget -O /tmp/dojo https://github.com/kudulab/dojo/releases/download/${version}/dojo_linux_amd64
7474
chmod +x /tmp/dojo
7575
sudo mv /tmp/dojo /usr/bin/dojo
@@ -87,7 +87,7 @@ jobs:
8787
- run:
8888
name: Install dojo
8989
command: |
90-
version="0.10.5"
90+
version="0.13.0"
9191
wget -O /tmp/dojo https://github.com/kudulab/dojo/releases/download/${version}/dojo_linux_amd64
9292
chmod +x /tmp/dojo
9393
sudo mv /tmp/dojo /usr/bin/dojo

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
### 0.13.1 (2024-Dec-29)
2+
3+
* docker-compose: support it when there is no version statement in the top of the yaml file; tested with docker-compose v2.24.5 and v2.31.0
4+
* docker-compose: fix json decoding on mac - the Publishers field was resulting in decoding errors
5+
* bump dojo image scripts base images
6+
* alpine:3.21
7+
* ubuntu:24.10
8+
19
### 0.13.0 (2024-Feb-05)
210

311
* Support multiple levels of log output produced by Dojo: `silent`, `error`, `warn`, `info`, `debug`

README.md

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ brew install kudulab/homebrew-dojo-osx/dojo
6161
```
6262
* a manual install:
6363
```sh
64-
version="0.13.0"
64+
version="0.13.1"
6565
# on Linux:
6666
wget -O /tmp/dojo https://github.com/kudulab/dojo/releases/download/${version}/dojo_linux_amd64
6767
# or on Mac:
@@ -91,7 +91,7 @@ dojo "gradle test jar"
9191
The beginning of the output shows the docker command executed by Dojo:
9292
```console
9393
/tmp/gocd-yaml-config-plugin$ dojo "gradle test jar"
94-
2020/12/09 07:40:28 [ 1] INFO: (main.main) Dojo version 0.10.5
94+
2020/12/09 07:40:28 [ 1] INFO: (main.main) Dojo version 0.13.1
9595
2020/12/09 07:40:28 [ 4] INFO: (main.DockerDriver.HandleRun) docker command will be:
9696
docker run --rm -v /tmp/gocd-yaml-config-plugin:/dojo/work -v /home/tomzo:/dojo/identity:ro --env-file=/tmp/dojo-environment-dojo-gocd-yaml-config-plugin-2020-12-09_07-40-50-60121947 -v /tmp/.X11-unix:/tmp/.X11-unix -ti --name=dojo-gocd-yaml-config-plugin-2020-12-09_07-40-50-60121947 kudulab/openjdk-dojo:1.4.1 "gradle test jar"
9797
Unable to find image 'kudulab/openjdk-dojo:1.4.1' locally
@@ -192,13 +192,13 @@ Docker version 19.03.5, build 633a0ea838
192192
$ docker info | grep "Server Version"
193193
Server Version: 19.03.5
194194
195-
$ docker run --rm alpine:3.15 whoami
196-
Unable to find image 'alpine:3.15' locally
195+
$ docker run --rm alpine:3.21 whoami
196+
Unable to find image 'alpine:3.21' locally
197197
# pulling image messages
198198
root
199199
dojo@ebc220b9655f(inception-dojo):/dojo/work$ docker images
200200
REPOSITORY TAG IMAGE ID CREATED SIZE
201-
alpine 3.9 78a2ce922f86 7 months ago 5.55MB
201+
alpine 3.21 78a2ce922f86 7 months ago 5.55MB
202202
```
203203

204204
There is also an Alpine dind Docker image. Use it in the following way:
@@ -216,7 +216,7 @@ You can use Dojo with any Docker image, it does not have to be a Dojo Docker ima
216216
```
217217
$ nano Dojofile
218218
$ cat Dojofile
219-
DOJO_DOCKER_IMAGE="alpine:3.16"
219+
DOJO_DOCKER_IMAGE="alpine:3.21"
220220
$ dojo
221221
# now we run interactively in the Dojo Docker container
222222
/ # whoami
@@ -322,7 +322,7 @@ We have also established several **best practices** for dojo image development:
322322
Dojo provides [several scripts](image_scripts/src) to be used inside dojo images to meet most of above requirements. Scripts can be installed in a `Dockerfile` with:
323323

324324
```dockerfile
325-
ENV DOJO_VERSION=0.10.5
325+
ENV DOJO_VERSION=0.13.1
326326
RUN git clone --depth 1 -b ${DOJO_VERSION} https://github.com/kudulab/dojo.git /tmp/dojo_git &&\
327327
/tmp/dojo_git/image_scripts/src/install.sh && \
328328
rm -r /tmp/dojo_git
@@ -352,7 +352,7 @@ ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
352352
RUN chmod +x /tini
353353

354354
# Install common Dojo scripts
355-
ENV DOJO_VERSION=0.10.5
355+
ENV DOJO_VERSION=0.13.1
356356
RUN apt-get update && \
357357
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
358358
sudo git ca-certificates && \
@@ -397,10 +397,10 @@ cd "${dojo_work}"
397397
For alpine images a typical dockerfile has following structure:
398398

399399
```dockerfile
400-
FROM alpine:3.15
400+
FROM alpine:3.21
401401

402402
# Install common Dojo scripts
403-
ENV DOJO_VERSION=0.10.5
403+
ENV DOJO_VERSION=0.13.1
404404
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \
405405
apk add --no-cache tini bash shadow sudo git && \
406406
git clone --depth 1 -b ${DOJO_VERSION} https://github.com/kudulab/dojo.git /tmp/dojo_git &&\
@@ -663,7 +663,6 @@ DOJO_DOCKER_COMPOSE_FILE="docker-compose.yml"
663663
Next to the `Dojofile`, you must create a docker-compose.yml according the [official reference](https://docs.docker.com/compose/compose-file/).
664664
Example docker-compose file:
665665
```yaml
666-
version: '2.2'
667666
services:
668667
default:
669668
links:
@@ -684,7 +683,7 @@ You can try creating above 2 files in any directory and run `dojo`. The output s
684683

685684
```console
686685
tomzo@073c1c477b1f:/tmp/compose-example$ dojo
687-
2019/04/28 18:38:17 [ 1] INFO: (main.main) Dojo version 0.3.2
686+
2019/04/28 18:38:17 [ 1] INFO: (main.main) Dojo version 0.13.1
688687
2019/04/28 18:38:18 [20] INFO: (main.DockerComposeDriver.HandleRun) docker-compose run command will be:
689688
docker-compose -f docker-compose.yml -f docker-compose.yml.dojo -p dojo-compose-example-2019-04-28_18-38-17-33362956 run --rm default
690689
Creating network "dojo-compose-example-2019-04-28_18-38-17-33362956_default" with the default driver
@@ -750,7 +749,7 @@ Usage of dojo <flags> [--] <CMD>:
750749
-identity-dir-outer string
751750
Directory on host, to be mounted into a docker container to /dojo/identity. Default: $HOME
752751
-image string
753-
Docker image name and tag, e.g. alpine:3.15
752+
Docker image name and tag, e.g. alpine:3.21
754753
-interactive string
755754
Set to false if you want to force not interactive docker run
756755
-ll string
@@ -818,15 +817,15 @@ is printed, press Ctrl+C. You may also test pressing Ctrl+C more times.
818817
819818
1. driver: docker, container's PID 1 process **not** preserving signals:
820819
```
821-
dojo --image=alpine:3.15 -i=false sh -c "echo 'will sleep' && sleep 1d"
820+
dojo --image=alpine:3.21 -i=false sh -c "echo 'will sleep' && sleep 1d"
822821
```
823822
2. driver: docker, container's PID 1 process preserving signals:
824823
```
825-
dojo --docker-options="--init" --image=alpine:3.15 -i=false sh -c "echo 'will sleep' && sleep 1d"
824+
dojo --docker-options="--init" --image=alpine:3.21 -i=false sh -c "echo 'will sleep' && sleep 1d"
826825
```
827826
3. driver: docker-compose:
828827
```
829-
dojo --driver=docker-compose --dcf=./test/test-files/itest-dc.yaml -i=false --image=alpine:3.15 sh -c "echo 'will sleep' && sleep 1d"
828+
dojo --driver=docker-compose --dcf=./test/test-files/itest-dc.yaml -i=false --image=alpine:3.21 sh -c "echo 'will sleep' && sleep 1d"
830829
```
831830
832831
### Preserving exported Bash functions [#17](https://github.com/kudulab/dojo/issues/17)
@@ -972,7 +971,7 @@ $ dojo -c Dojofile.build
972971

973972
4. Run end to end tests:
974973
```
975-
./tasks e2e ubuntu18
974+
./tasks e2e ubuntu
976975
./tasks e2e alpine
977976
```
978977

@@ -996,7 +995,7 @@ $ dojo -c Dojofile.build
996995

997996
## License
998997

999-
Copyright 2019-2022 Ava Czechowska, Tom Setkowski
998+
Copyright 2019-2025 Ava Czechowska, Tom Setkowski
1000999

10011000
Licensed under the Apache License, Version 2.0 (the "License");
10021001
you may not use this file except in compliance with the License.

config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func getCLIConfig() Config {
9191
flagSet.StringVar(&driver, "d", "", usageDriver+" (shorthand)")
9292

9393
var image string
94-
const usageImage = "Docker image name and tag, e.g. alpine:3.15"
94+
const usageImage = "Docker image name and tag, e.g. alpine:3.21"
9595
flagSet.StringVar(&image, "image", "", usageImage)
9696

9797
var logLevel string

docker_compose_driver.go

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func isDCVersionLaterThan2(dcVersion string) bool {
6060
func (dc DockerComposeDriver) parseDCFileVersion(contents string) (float64, error) {
6161
firstLine := strings.Split(contents, "\n")[0]
6262
if !strings.HasPrefix(firstLine, "version") {
63-
return 0, fmt.Errorf("First line of docker-compose file did not start with: version")
63+
return -1, nil
6464
}
6565
versionQuoted := strings.Split(firstLine, ":")[1]
6666
versionQuoted = strings.Trim(versionQuoted, " ")
@@ -78,7 +78,7 @@ func (dc DockerComposeDriver) verifyDCFile(fileContents string, filePath string)
7878
if err != nil {
7979
return 0, err
8080
}
81-
if version < 2 || version >= 3 {
81+
if version != -1 && (version < 2 || version >= 3) {
8282
return 0, fmt.Errorf("docker-compose file: %s should contain version number >=2 and <3, current version: %v", filePath, version)
8383
}
8484
requiredStr := "default:"
@@ -129,12 +129,18 @@ func (dc DockerComposeDriver) generateDCFileContentsWithEnv(expContainers []stri
129129
}
130130

131131
func (dc DockerComposeDriver) generateInitialDCFile(config Config, version float64) string {
132-
contents := fmt.Sprintf(
132+
if version == -1 {
133+
// version was not set
134+
return fmt.Sprintf(
135+
`services:
136+
default:
137+
image: "%s"`, config.DockerImage)
138+
}
139+
return fmt.Sprintf(
133140
`version: '%v'
134141
services:
135142
default:
136-
image: %s`, version, config.DockerImage)
137-
return contents
143+
image: "%s"`, version, config.DockerImage)
138144
}
139145

140146
func (dc DockerComposeDriver) ConstructDockerComposeCommandPart1(config Config, projectName string) string {
@@ -701,24 +707,24 @@ func (dc DockerComposeDriver) getDCContainersNames(mergedConfig Config, projectN
701707
}
702708

703709
type DC2PSOutput struct {
704-
Command string `json:"Command"`
705-
CreatedAt string `json:"CreatedAt"`
706-
ExitCode int `json:"ExitCode"`
707-
Health string `json:"Health"`
708-
ID string `json:"ID"`
709-
Image string `json:"Image"`
710-
Labels string `json:"Labels"`
711-
Name string `json:"Name"`
712-
Names string `json:"Names"`
713-
Networks string `json:"Networks"`
714-
Ports string `json:"Ports"`
715-
Project string `json:"Project"`
716-
Publishers string `json:"Publishers"`
717-
RunningFor string `json:"RunningFor"`
718-
Service string `json:"Service"`
719-
Size string `json:"Size"`
720-
State string `json:"State"`
721-
Status string `json:"Status"`
710+
Command string `json:"Command"`
711+
CreatedAt string `json:"CreatedAt"`
712+
ExitCode int `json:"ExitCode"`
713+
Health string `json:"Health"`
714+
ID string `json:"ID"`
715+
Image string `json:"Image"`
716+
Labels string `json:"Labels"`
717+
Name string `json:"Name"`
718+
Names string `json:"Names"`
719+
Networks string `json:"Networks"`
720+
Ports string `json:"Ports"`
721+
Project string `json:"Project"`
722+
Publishers []string `json:"Publishers"`
723+
RunningFor string `json:"RunningFor"`
724+
Service string `json:"Service"`
725+
Size string `json:"Size"`
726+
State string `json:"State"`
727+
Status string `json:"Status"`
722728
}
723729

724730
// Parses the output of the `docker-compose ps --format json --all` command, into json.
@@ -728,6 +734,9 @@ type DC2PSOutput struct {
728734
// {"Command":"\"/bin/sh -c 'while t…\"","CreatedAt":"2024-02-03 21:03:46 +0000 UTC","ExitCode":0,"Health":"","ID":"2d5c5b0343d0","Image":"alpine:3.19","Labels":"com.docker.compose.depends_on=,com.docker.compose.image=sha256:05455a08881ea9cf0e752bc48e61bbd71a34c029bb13df01e40e3e70e0d007bd,com.docker.compose.version=2.24.5,com.docker.compose.service=abc,com.docker.compose.config-hash=270e27422cb1e6a4c1713ae22a3ffca0e8aa50ec0f06fe493fa4f83a17bd29e9,com.docker.compose.container-number=1,com.docker.compose.oneoff=False,com.docker.compose.project=testdojorunid,com.docker.compose.project.config_files=/dojo/work/test/test-files/itest-dc.yaml,/dojo/work/test/test-files/itest-dc.yaml.dojo,com.docker.compose.project.working_dir=/dojo/work/test/test-files","LocalVolumes":"0","Mounts":"/tmp/test-dojo…,/tmp/test-dojo…","Name":"testdojorunid-abc-1","Names":"testdojorunid-abc-1","Networks":"testdojorunid_default","Ports":"","Project":"testdojorunid","Publishers":null,"RunningFor":"3 seconds ago","Service":"abc","Size":"0B","State":"running","Status":"Up 2 seconds"}
729735
// {"Command":"\"/bin/sh -c 'while t…\"","CreatedAt":"2024-02-03 21:03:46 +0000 UTC","ExitCode":0,"Health":"","ID":"b2ed210567c3","Image":"alpine:3.19","Labels":"com.docker.compose.project=testdojorunid,com.docker.compose.project.config_files=/dojo/work/test/test-files/itest-dc.yaml,/dojo/work/test/test-files/itest-dc.yaml.dojo,com.docker.compose.project.working_dir=/dojo/work/test/test-files,com.docker.compose.depends_on=,com.docker.compose.container-number=1,com.docker.compose.image=sha256:05455a08881ea9cf0e752bc48e61bbd71a34c029bb13df01e40e3e70e0d007bd,com.docker.compose.oneoff=False,com.docker.compose.service=def,com.docker.compose.version=2.24.5,com.docker.compose.config-hash=270e27422cb1e6a4c1713ae22a3ffca0e8aa50ec0f06fe493fa4f83a17bd29e9","LocalVolumes":"0","Mounts":"/tmp/test-dojo…,/tmp/test-dojo…","Name":"testdojorunid-def-1","Names":"testdojorunid-def-1","Networks":"testdojorunid_default","Ports":"","Project":"testdojorunid","Publishers":null,"RunningFor":"3 seconds ago","Service":"def","Size":"0B","State":"running","Status":"Up 2 seconds"}
730736
// {"Command":"\"sh -c 'sleep 10'\"","CreatedAt":"2024-02-03 21:03:47 +0000 UTC","ExitCode":0,"Health":"","ID":"af4817fede41","Image":"alpine:3.15","Labels":"com.docker.compose.version=2.24.5,com.docker.compose.container-number=1,com.docker.compose.depends_on=abc:service_started:true,def:service_started:true,com.docker.compose.oneoff=True,com.docker.compose.project=testdojorunid,com.docker.compose.slug=742bcbb0e4bc05b21928a8d17be4ea9bb12a6775fd40692dd59c74a460279eb8,com.docker.compose.config-hash=462afacb4521d13580c2096c7b00b98970f07fe841e408c4c5a95a4a46839eaa,com.docker.compose.image=sha256:32b91e3161c8fc2e3baf2732a594305ca5093c82ff4e0c9f6ebbd2a879468e1d,com.docker.compose.project.config_files=/dojo/work/test/test-files/itest-dc.yaml,/dojo/work/test/test-files/itest-dc.yaml.dojo,com.docker.compose.project.working_dir=/dojo/work/test/test-files,com.docker.compose.service=default","LocalVolumes":"0","Mounts":"/home/dojo,/dojo/work,/tmp/test-dojo…,/tmp/test-dojo…,/tmp/.X11-unix,/tmp/dojo-ites…","Name":"testdojorunid-default-run-742bcbb0e4bc","Names":"testdojorunid-default-run-742bcbb0e4bc","Networks":"testdojorunid_default","Ports":"","Project":"testdojorunid","Publishers":null,"RunningFor":"2 seconds ago","Service":"default","Size":"0B","State":"running","Status":"Up 1 second"}
737+
//
738+
// when using docker-compose: 2.31m the field with Publishers is set to []:
739+
// {"Command":"\"/bin/sh -c 'while t…\"","CreatedAt":"2024-12-20 15:58:00 +1300 NZDT","ExitCode":143,"Health":"","ID":"f543828473a7","Image":"alpine:3.19","Labels":"com.docker.compose.image=sha256:7a85bf5dc56c949be827f84f9185161265c58f589bb8b2a6b6bb6d3076c1be21,desktop.docker.io/binds/0/Source=/tmp/dojo-environment-multiline-dojo-test-files-2024-12-2015-58-00-6660523964295751458,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/etc/dojo.d/variables/00-multiline-vars.sh,desktop.docker.io/binds/1/Target=/etc/dojo.d/variables/01-bash-functions.sh,com.docker.compose.oneoff=False,com.docker.compose.project.config_files=/Users/ava.czechowska/code/dojo/test/test-files/itest-dc.yaml,/Users/ava.czechowska/code/dojo/test/test-files/itest-dc.yaml.dojo,com.docker.compose.service=abc,desktop.docker.io/binds/1/SourceKind=hostFile,com.docker.compose.container-number=1,com.docker.compose.project.working_dir=/Users/ava.czechowska/code/dojo/test/test-files,com.docker.compose.config-hash=4439e404114c9d0f183d756084f3e0e80c56b731e1f1e14e6db1844134294969,com.docker.compose.depends_on=,com.docker.compose.project=dojo-test-files-2024-12-2015-58-00-6660523964295751458,com.docker.compose.version=2.31.0,desktop.docker.io/binds/1/Source=/tmp/dojo-environment-bash-functions-dojo-test-files-2024-12-2015-58-00-6660523964295751458","LocalVolumes":"0","Mounts":"/host_mnt/priv…,/host_mnt/priv…","Name":"dojo-test-files-2024-12-2015-58-00-6660523964295751458-abc-1","Names":"dojo-test-files-2024-12-2015-58-00-6660523964295751458-abc-1","Networks":"dojo-test-files-2024-12-2015-58-00-6660523964295751458_default","Ports":"","Project":"dojo-test-files-2024-12-2015-58-00-6660523964295751458","Publishers":[],"RunningFor":"36 seconds ago","Service":"abc","Size":"0B","State":"exited","Status":"Exited (143) 10 seconds ago"}
731740
func ParseDCPSOutPut_DCVersion2(output string) ([]DC2PSOutput, string) {
732741
var output_as_jsons []DC2PSOutput
733742

@@ -740,7 +749,7 @@ func ParseDCPSOutPut_DCVersion2(output string) ([]DC2PSOutput, string) {
740749
err := json.Unmarshal([]byte(line), &one_output_as_json)
741750
if err != nil {
742751
return []DC2PSOutput{},
743-
fmt.Errorf("Error when decoding the JSON response from docker-compose ps command: %s", err).Error()
752+
fmt.Errorf("Error when decoding the JSON response from docker-compose ps command: %s; line %s", err, line).Error()
744753
}
745754
if one_output_as_json.State == "" {
746755
// This means that something went wrong, e.g. docker-compose ps output is now different

docker_compose_driver_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func Test_parseDCFileVersion(t *testing.T) {
1717
mytestsObj := []mytests{
1818
mytests{"version: '4.55'", 4.55, ""},
1919
mytests{"version: \"4.55\"", 4.55, ""},
20-
mytests{"", 0, "First line of docker-compose file did not start with: version"},
20+
mytests{"", -1, ""},
2121
}
2222
logger := NewLogger("debug")
2323
dc := NewDockerComposeDriver(NewMockedShellServiceNotInteractive(logger), NewMockedFileService(logger), logger, "")

image_scripts/DockerfileAlpine

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM alpine:3.19
1+
FROM alpine:3.21
22

33
# For Dojo:
44
# * entrypoint requires sudo and bash

image_scripts/DockerfileUbuntu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM ubuntu:23.10
1+
FROM ubuntu:24.10
22

33
# the Dojo scripts create a user with ID==1000,
44
# previous ubuntu images (e.g. 20.04) seem to not have the ubuntu user,

image_scripts/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The `src/install.sh` script:
4747
## Development
4848
Build and test Dojo docker images:
4949
```
50-
cd dojo_image_scripts
50+
cd image_scripts
5151
./tasks build
5252
./tasks test_scripts
5353
./tasks e2e

test/signal/signals-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ set -x; rm -f "${test_file}"; touch "${test_file}"; set +x;
118118

119119
wait_for_the_docker_daemon_to_be_running
120120
this_test_exit_status=0
121-
run_test_process_and_send_signal "./bin/dojo --debug=true --test=true --image=alpine:3.19 -i=false sh -c \"echo 'will sleep' && sleep 1d\"" "will sleep"
121+
run_test_process_and_send_signal "./bin/dojo --debug=true --test=true --image=alpine:3.21 -i=false sh -c \"echo 'will sleep' && sleep 1d\"" "will sleep"
122122

123123
## Test checks
124124
log_test "----------------------------------------------------------"

0 commit comments

Comments
 (0)