479 |
480 |
481 |
--------------------------------------------------------------------------------
/.bulldozer.yml:
--------------------------------------------------------------------------------
1 | # Excavator auto-updates this file. Please contribute improvements to the central template.
2 |
3 | version: 1
4 | merge:
5 | trigger:
6 | labels: ["merge when ready"]
7 | ignore:
8 | labels: ["do not merge"]
9 | method: squash
10 | options:
11 | squash:
12 | body: pull_request_body
13 | message_delimiter: ==COMMIT_MSG==
14 | delete_after_merge: true
15 | update:
16 | trigger:
17 | labels: ["update me"]
18 |
--------------------------------------------------------------------------------
/.changelog.yml:
--------------------------------------------------------------------------------
1 | # Excavator auto-updates this file. Please contribute improvements to the central template.
2 |
3 | # This file is intentionally empty. The file's existence enables changelog-app and is empty to use the default configuration.
4 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # This file was generated by the excavator check 'excavator/manage-circleci' as specified in .circleci/template.sh.
2 | # To request a modification to the general template, file an issue on Excavator.
3 | # To manually manage the CircleCI configuration for this project, remove the .circleci/template.sh file.
4 |
5 | version: 2.1
6 | jobs:
7 | compile:
8 | docker: [{ image: 'cimg/openjdk:11.0.10-node' }]
9 | resource_class: large
10 | environment:
11 | CIRCLE_TEST_REPORTS: /home/circleci/junit
12 | CIRCLE_ARTIFACTS: /home/circleci/artifacts
13 | GRADLE_OPTS: -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs='-Xmx2g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
14 | _JAVA_OPTIONS: -XX:ActiveProcessorCount=4 -XX:MaxRAM=8g -XX:ErrorFile=/home/circleci/artifacts/hs_err_pid%p.log -XX:HeapDumpPath=/home/circleci/artifacts
15 | steps:
16 | - checkout
17 | - run:
18 | name: delete_unrelated_tags
19 | command: |
20 | ALL_TAGS=$(git tag --points-at HEAD)
21 |
22 | if [ -z "$ALL_TAGS" ]; then
23 | echo "No-op as there are no tags on the current commit ($(git rev-parse HEAD))"
24 | exit 0
25 | fi
26 |
27 | if [ -z "${CIRCLE_TAG:+x}" ]; then
28 | echo "Non-tag build, deleting all tags which point to HEAD: [${ALL_TAGS/$'\n'/,}]"
29 | echo "$ALL_TAGS" | while read -r TAG; do git tag -d "$TAG" 1>/dev/null; done
30 | exit 0
31 | fi
32 |
33 | TAGS_TO_DELETE=$(echo "$ALL_TAGS" | grep -v "^$CIRCLE_TAG$" || :)
34 | if [ -z "$TAGS_TO_DELETE" ]; then
35 | echo "No-op as exactly one tag ($CIRCLE_TAG) points to HEAD"
36 | exit 0
37 | fi
38 |
39 | echo "Detected tag build, deleting all tags except '$CIRCLE_TAG' which point to HEAD: [${TAGS_TO_DELETE/$'\n'/,}]"
40 | echo "$TAGS_TO_DELETE" | while read -r TAG; do git tag -d "$TAG" 1>/dev/null; done
41 | - restore_cache: { key: 'gradle-wrapper-v2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}' }
42 | - restore_cache: { key: 'compile-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}' }
43 | - run: ./gradlew --parallel --stacktrace classes testClasses -Porg.gradle.java.installations.fromEnv=JAVA_8_HOME,JAVA_11_HOME,JAVA_15_HOME,JAVA_17_HOME,JAVA_HOME
44 | - save_cache:
45 | key: 'gradle-wrapper-v2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}'
46 | paths: [ ~/.gradle/wrapper ]
47 | - save_cache:
48 | key: 'compile-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}'
49 | paths: [ ~/.gradle/caches ]
50 | - store_test_results: { path: ~/junit }
51 | - store_artifacts: { path: ~/artifacts }
52 | - persist_to_workspace:
53 | root: /home/circleci
54 | paths: [ project, .gradle/init.gradle ]
55 |
56 | check:
57 | docker: [{ image: 'cimg/openjdk:11.0.10-node' }]
58 | resource_class: medium
59 | environment:
60 | CIRCLE_TEST_REPORTS: /home/circleci/junit
61 | CIRCLE_ARTIFACTS: /home/circleci/artifacts
62 | GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.jvmargs='-Xmx2g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
63 | _JAVA_OPTIONS: -XX:ActiveProcessorCount=2 -XX:MaxRAM=4g -XX:ErrorFile=/home/circleci/artifacts/hs_err_pid%p.log -XX:HeapDumpPath=/home/circleci/artifacts
64 | steps:
65 | - attach_workspace: { at: /home/circleci }
66 | - restore_cache: { key: 'gradle-wrapper-v2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}' }
67 | - restore_cache: { key: 'check-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}' }
68 | - run: ./gradlew --parallel --stacktrace --continue check idea -x test -Porg.gradle.java.installations.fromEnv=JAVA_8_HOME,JAVA_11_HOME,JAVA_15_HOME,JAVA_17_HOME,JAVA_HOME
69 | - save_cache:
70 | key: 'check-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}'
71 | paths: [ ~/.gradle/caches ]
72 | - run:
73 | command: mkdir -p ~/junit && find . -type f -regex ".*/build/.*TEST.*xml" -exec cp --parents {} ~/junit/ \;
74 | when: always
75 | - store_test_results: { path: ~/junit }
76 | - store_artifacts: { path: ~/artifacts }
77 |
78 | unit-test:
79 | docker: [{ image: 'cimg/openjdk:11.0.10-node' }]
80 | resource_class: large
81 | environment:
82 | CIRCLE_TEST_REPORTS: /home/circleci/junit
83 | CIRCLE_ARTIFACTS: /home/circleci/artifacts
84 | GRADLE_OPTS: -Dorg.gradle.workers.max=2 -Dorg.gradle.jvmargs='-Xmx2g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
85 | _JAVA_OPTIONS: -XX:ActiveProcessorCount=4 -XX:MaxRAM=8g -XX:ErrorFile=/home/circleci/artifacts/hs_err_pid%p.log -XX:HeapDumpPath=/home/circleci/artifacts
86 | steps:
87 | - attach_workspace: { at: /home/circleci }
88 | - restore_cache: { key: 'gradle-wrapper-v2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}' }
89 | - restore_cache: { key: 'unit-test-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}' }
90 | - run: ./gradlew --parallel --stacktrace --continue --max-workers=2 test -Porg.gradle.java.installations.fromEnv=JAVA_8_HOME,JAVA_11_HOME,JAVA_15_HOME,JAVA_17_HOME,JAVA_HOME
91 | - save_cache:
92 | key: 'unit-test-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}'
93 | paths: [ ~/.gradle/caches ]
94 | - run:
95 | command: mkdir -p ~/junit && find . -type f -regex ".*/build/.*TEST.*xml" -exec cp --parents {} ~/junit/ \;
96 | when: always
97 | - store_test_results: { path: ~/junit }
98 | - store_artifacts: { path: ~/artifacts }
99 |
100 |
101 | build:
102 | machine: { docker_layer_caching: true }
103 | environment:
104 | CIRCLE_TEST_REPORTS: /home/circleci/junit
105 | CIRCLE_ARTIFACTS: /home/circleci/artifacts
106 | _JAVA_OPTIONS: -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Xmx8192m
107 | JAVA_HOME: /opt/java11
108 | steps:
109 | - attach_workspace: { at: /home/circleci }
110 | - restore_cache: { key: 'gradle-wrapper-v2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}' }
111 | - restore_cache: { key: 'build-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}' }
112 | - run:
113 | name: Install Java 11
114 | command: |
115 | sudo mkdir -p /opt/java && cd /opt/java && sudo chown -R circleci:circleci .
116 | curl https://cdn.azul.com/zulu/bin/zulu11.54.23-ca-jdk11.0.14-linux_x64.tar.gz | tar -xzf - -C /opt/java
117 | sudo ln -s /opt/java/zulu*/ /opt/java11
118 | - run: ./gradlew --parallel --stacktrace build -x test -x check -Porg.gradle.java.installations.fromEnv=JAVA_8_HOME,JAVA_11_HOME,JAVA_15_HOME,JAVA_17_HOME,JAVA_HOME
119 | - save_cache:
120 | key: 'build-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}'
121 | paths: [ ~/.gradle/caches ]
122 | - run:
123 | command: mkdir -p ~/junit && find . -type f -regex ".*/build/.*TEST.*xml" -exec cp --parents {} ~/junit/ \; || true
124 | when: always
125 | - store_test_results: { path: ~/junit }
126 | - store_artifacts: { path: ~/artifacts }
127 |
128 | trial-publish:
129 | docker: [{ image: 'cimg/openjdk:11.0.10-node' }]
130 | resource_class: medium
131 | environment:
132 | CIRCLE_TEST_REPORTS: /home/circleci/junit
133 | CIRCLE_ARTIFACTS: /home/circleci/artifacts
134 | GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.jvmargs='-Xmx2g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
135 | _JAVA_OPTIONS: -XX:ActiveProcessorCount=2 -XX:MaxRAM=4g -XX:ErrorFile=/home/circleci/artifacts/hs_err_pid%p.log -XX:HeapDumpPath=/home/circleci/artifacts
136 | steps:
137 | - attach_workspace: { at: /home/circleci }
138 | - restore_cache: { key: 'gradle-wrapper-v2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}' }
139 | - restore_cache: { key: 'trial-publish-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}' }
140 | - run: ./gradlew --stacktrace publishToMavenLocal -Porg.gradle.java.installations.fromEnv=JAVA_8_HOME,JAVA_11_HOME,JAVA_15_HOME,JAVA_17_HOME,JAVA_HOME
141 | - run:
142 | command: git status --porcelain
143 | when: always
144 | - save_cache:
145 | key: 'trial-publish-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}'
146 | paths: [ ~/.gradle/caches ]
147 | - store_test_results: { path: ~/junit }
148 | - store_artifacts: { path: ~/artifacts }
149 |
150 | publish:
151 | docker: [{ image: 'cimg/openjdk:11.0.10-node' }]
152 | resource_class: medium
153 | environment:
154 | CIRCLE_TEST_REPORTS: /home/circleci/junit
155 | CIRCLE_ARTIFACTS: /home/circleci/artifacts
156 | GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.jvmargs='-Xmx2g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
157 | _JAVA_OPTIONS: -XX:ActiveProcessorCount=2 -XX:MaxRAM=4g -XX:ErrorFile=/home/circleci/artifacts/hs_err_pid%p.log -XX:HeapDumpPath=/home/circleci/artifacts
158 | steps:
159 | - attach_workspace: { at: /home/circleci }
160 | - restore_cache: { key: 'gradle-wrapper-v2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}' }
161 | - restore_cache: { key: 'publish-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}' }
162 | - deploy:
163 | command: ./gradlew --parallel --stacktrace --continue publish -Porg.gradle.java.installations.fromEnv=JAVA_8_HOME,JAVA_11_HOME,JAVA_15_HOME,JAVA_17_HOME,JAVA_HOME
164 | - run:
165 | command: git status --porcelain
166 | when: always
167 | - save_cache:
168 | key: 'publish-gradle-cache-v2-{{ checksum "versions.props" }}-{{ checksum "build.gradle" }}'
169 | paths: [ ~/.gradle/caches ]
170 | - store_test_results: { path: ~/junit }
171 | - store_artifacts: { path: ~/artifacts }
172 |
173 |
174 | workflows:
175 | version: 2
176 | build:
177 | jobs:
178 | - compile:
179 | filters: { tags: { only: /.*/ } }
180 |
181 | - unit-test:
182 | requires: [ compile ]
183 | filters: { tags: { only: /.*/ } }
184 |
185 | - check:
186 | requires: [ compile ]
187 | filters: { tags: { only: /.*/ } }
188 |
189 | - build:
190 | requires: [ compile ]
191 | filters: { tags: { only: /.*/ } }
192 |
193 | - trial-publish:
194 | requires: [ compile ]
195 | filters: { branches: { ignore: develop } }
196 |
197 | - publish:
198 | requires: [ unit-test, check, build, trial-publish ]
199 | filters: { tags: { only: /.*/ }, branches: { only: develop } }
200 |
--------------------------------------------------------------------------------
/.circleci/template.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | export CIRCLECI_TEMPLATE=java-library-oss
3 | export DOCKER_TESTS=true
4 | export JDK=11
5 |
--------------------------------------------------------------------------------
/.excavator.yml:
--------------------------------------------------------------------------------
1 | # Excavator auto-updates this file. Please contribute improvements to the central template.
2 |
3 | auto-label:
4 | names:
5 | versions-props/upgrade-all: [ "merge when ready" ]
6 | circleci/manage-circleci: [ "merge when ready" ]
7 | tags:
8 | donotmerge: [ "do not merge" ]
9 | roomba: [ "merge when ready" ]
10 | automerge: [ "merge when ready" ]
11 | autorelease: [ "autorelease" ]
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## What happened?
2 |
3 |
7 |
8 | ## What did you want to happen?
9 |
10 |
13 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Before this PR
2 |
3 |
4 | ## After this PR
5 |
6 | ==COMMIT_MSG==
7 | ==COMMIT_MSG==
8 |
9 | ## Possible downsides?
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #IntelliJ
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .idea/
6 |
7 | # Eclipse
8 | .classpath
9 | .checkstyle
10 | .factorypath
11 | .project
12 | .settings
13 |
14 | # Codegen
15 | .generated
16 | .apt_generated
17 | generated_src/
18 | generated_testSrc/
19 | **/src/generated/
20 |
21 | # OS X
22 | .DS_Store
23 |
24 | # Java
25 | *.class
26 |
27 | # Build Systems
28 | .gradle/
29 | bin/
30 | build/
31 | out/
32 |
33 | # Gradle JDKs setup
34 | !gradle/*
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://circleci.com/gh/palantir/docker-proxy-rule)
6 |
7 | Docker Proxy JUnit Rule
8 | =======================
9 |
10 | This is a small library for executing JUnit tests that interact with Docker containers. It supports the following:
11 |
12 | - Hitting the docker containers according the their hostnames when using interfaces that are not backed by Java NIO
13 | - Auto-mapping the hostnames when using docker-compose-rule
14 | - Auto-mapping the hostnames when specifying the name of the network they are on
15 |
16 | Why should I use this
17 | ---------------------
18 |
19 | This code allows you to avoid having to map internal docker ports to external ports so you don't have to map them to ports that may be in-use, or map them to random ports then have logic to construct clients based on which random port is being used.
20 |
21 | Simple Use
22 | ----------
23 |
24 | Add a dependency to your project. For example, in gradle:
25 |
26 | ```groovy
27 | repositories {
28 | mavenCentral() // docker-proxy-rule is published on maven central
29 | }
30 | dependencies {
31 | testImplementation 'com.palantir.docker.proxy:docker-proxy-rule:'
32 | }
33 | ```
34 |
35 | For the most basic use (with [docker-compose-rule](https://github.com/palantir/docker-compose-rule)), simply add an `@ClassRule` as follows:
36 |
37 | ```java
38 | public class MyIntegrationTest {
39 | private static DockerComposeRule docker = ...;
40 | private static DockerProxyRule proxy = DockerProxyRule.fromProjectName(docker.projectName());
41 |
42 | @ClassRule
43 | public static RuleChain ruleChain = RuleChain.outerRule(docker)
44 | .around(proxy);
45 | }
46 | ```
47 |
48 | You can then communicate with the hosts within your tests. For example:
49 | ```java
50 | URLConnection urlConnection = new URL(TARGET).openConnection();
51 | urlConnection.connect();
52 | ```
53 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } }
4 | gradlePluginPortal() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } }
5 | }
6 | dependencies {
7 | classpath 'com.palantir.gradle.jdks:gradle-jdks:0.67.0'
8 | classpath 'com.palantir.gradle.jdkslatest:gradle-jdks-latest:0.18.0'
9 | classpath 'com.palantir.jakartapackagealignment:jakarta-package-alignment:0.6.0'
10 | classpath 'com.palantir.gradle.externalpublish:gradle-external-publish-plugin:1.19.0'
11 | classpath 'com.palantir.gradle.failure-reports:gradle-failure-reports:1.14.0'
12 | classpath 'com.palantir.javaformat:gradle-palantir-java-format:2.67.0'
13 | classpath 'com.palantir.suppressible-error-prone:gradle-suppressible-error-prone:2.9.0'
14 | classpath 'com.palantir.gradle.consistentversions:gradle-consistent-versions:2.34.0'
15 | classpath 'com.palantir.baseline:gradle-baseline-java:6.16.0'
16 | classpath 'com.palantir.gradle.gitversion:gradle-git-version:3.3.0'
17 | classpath 'com.palantir.gradle.idea-configuration:gradle-idea-configuration:0.4.0'
18 | classpath 'gradle.plugin.org.inferred:gradle-processors:3.7.0'
19 | classpath 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:4.1.0'
20 | }
21 | }
22 |
23 | apply plugin: 'com.palantir.external-publish'
24 | apply plugin: 'com.palantir.failure-reports'
25 | apply plugin: 'com.palantir.consistent-versions'
26 | apply plugin: 'com.palantir.git-version'
27 | apply plugin: 'com.palantir.jdks'
28 | apply plugin: 'com.palantir.baseline'
29 | apply plugin: 'com.palantir.baseline-java-versions'
30 | apply plugin: 'com.palantir.jdks.latest'
31 |
32 | javaVersions {
33 | libraryTarget = 17
34 | }
35 |
36 | version gitVersion()
37 |
38 | allprojects {
39 | apply plugin: 'com.palantir.java-format'
40 | apply plugin: 'com.palantir.jakarta-package-alignment'
41 | version rootProject.version
42 | group = 'com.palantir.docker.proxy'
43 |
44 | repositories {
45 | mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } }
46 | }
47 | }
48 |
49 | configure(subprojects) {
50 | apply plugin: 'java-library'
51 | apply plugin: 'org.inferred.processors'
52 | }
53 |
54 | jdks {
55 | daemonTarget = 21
56 | }
57 |
--------------------------------------------------------------------------------
/changelog/1.1.1/pr-298.v2.yml:
--------------------------------------------------------------------------------
1 | type: fix
2 | fix:
3 | description: Update usage to work with builds of Dante after 410c297862615aff77f27b99ff7151dcf20c8f71,
4 | generated on the 30th of October.
5 | links:
6 | - https://github.com/palantir/docker-proxy-rule/pull/298
7 |
--------------------------------------------------------------------------------
/changelog/1.1.2/pr-300.v2.yml:
--------------------------------------------------------------------------------
1 | type: fix
2 | fix:
3 | description: Now try both special paths for the vimagick/dante container, handling both old and latest containers.
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/300
6 |
--------------------------------------------------------------------------------
/changelog/1.10.0/pr-574.v2.yml:
--------------------------------------------------------------------------------
1 | type: improvement
2 | improvement:
3 | description: InetAddressResolver works compiled against jdk21
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/574
6 |
--------------------------------------------------------------------------------
/changelog/1.11.0/pr-576.v2.yml:
--------------------------------------------------------------------------------
1 | type: fix
2 | fix:
3 | description: Jdk21 support (round 2)
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/576
6 |
--------------------------------------------------------------------------------
/changelog/1.12.0/pr-607.v2.yml:
--------------------------------------------------------------------------------
1 | type: fix
2 | fix:
3 | description: Update dependencies so docker compose v2 is used by default, and remove
4 | old unused scripts.
5 | links:
6 | - https://github.com/palantir/docker-proxy-rule/pull/607
7 |
--------------------------------------------------------------------------------
/changelog/1.2.0/pr-301.v2.yml:
--------------------------------------------------------------------------------
1 | type: improvement
2 | improvement:
3 | description: The legacy setup path for `vimagick/dante` now works correctly.
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/301
6 |
--------------------------------------------------------------------------------
/changelog/1.3.0/pr-464.v2.yml:
--------------------------------------------------------------------------------
1 | type: fix
2 | fix:
3 | description: Add module options to support name resolution on Java 16+
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/464
6 |
--------------------------------------------------------------------------------
/changelog/1.4.0/pr-471.v2.yml:
--------------------------------------------------------------------------------
1 | type: fix
2 | fix:
3 | description: Produce java8 bytecode
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/471
6 |
--------------------------------------------------------------------------------
/changelog/1.6.0/pr-543.v2.yml:
--------------------------------------------------------------------------------
1 | type: fix
2 | fix:
3 | description: Use non-deprecated format for specifying an external network name
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/543
6 |
--------------------------------------------------------------------------------
/changelog/1.7.0/pr-552.v2.yml:
--------------------------------------------------------------------------------
1 | type: feature
2 | feature:
3 | description: Projects using DockerProxyExtension can now override the docker image
4 | name used by the proxy.
5 | links:
6 | - https://github.com/palantir/docker-proxy-rule/pull/552
7 |
--------------------------------------------------------------------------------
/changelog/1.8.0/pr-564.v2.yml:
--------------------------------------------------------------------------------
1 | type: improvement
2 | improvement:
3 | description: Remove retryAttempts override for DockerProxyExtension
4 | links:
5 | - https://github.com/palantir/docker-proxy-rule/pull/564
6 |
--------------------------------------------------------------------------------
/changelog/1.9.0/pr-561.v2.yml:
--------------------------------------------------------------------------------
1 | type: feature
2 | feature:
3 | description: Projects using DockerProxyExtension for project-based containers can
4 | now override the docker network name used by the proxy
5 | links:
6 | - https://github.com/palantir/docker-proxy-rule/pull/561
7 |
--------------------------------------------------------------------------------
/changelog/@unreleased/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/palantir/docker-proxy-rule/db87814dbc46636080aefc0958823e9818e74e6a/changelog/@unreleased/.gitkeep
--------------------------------------------------------------------------------
/docker-proxy-junit-jupiter/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.palantir.external-publish-jar'
2 | apply plugin: 'org.unbroken-dome.test-sets'
3 |
4 | testSets {
5 | integrationTest
6 | }
7 |
8 | build.dependsOn integrationTest
9 |
10 | dependencies {
11 | api project(":docker-proxy-rule-core")
12 | api group: 'com.palantir.docker.compose', name: 'docker-compose-junit-jupiter'
13 |
14 | testImplementation group: 'org.assertj', name: 'assertj-core'
15 | testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter'
16 | testImplementation group: 'org.mockito', name: 'mockito-core'
17 | testRuntimeOnly group: 'org.mockito', name: 'mockito-inline'
18 | }
19 |
--------------------------------------------------------------------------------
/docker-proxy-junit-jupiter/src/integrationTest/java/com/palantir/docker/proxy/DockerProxyExtensionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20 |
21 | import com.palantir.docker.compose.DockerComposeExtension;
22 | import com.palantir.docker.compose.connection.Container;
23 | import com.palantir.docker.compose.logging.LogDirectory;
24 | import java.io.IOException;
25 | import java.net.URL;
26 | import java.net.URLConnection;
27 | import org.junit.jupiter.api.Test;
28 | import org.junit.jupiter.api.extension.RegisterExtension;
29 |
30 | class DockerProxyExtensionTest {
31 | @RegisterExtension
32 | static final DockerComposeExtension DOCKER_COMPOSE_EXTENSION = DockerComposeExtension.builder()
33 | .file("src/integrationTest/resources/DockerProxyExtensionTest-services.yml")
34 | .saveLogsTo(LogDirectory.circleAwareLogDirectory(DockerProxyExtensionTest.class))
35 | .waitingForService("webserver", Container::areAllPortsOpen)
36 | .build();
37 |
38 | @Test
39 | void canReachDockerContainerByContainerNameWithProjectSpecified() throws IOException, InterruptedException {
40 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
41 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionTest.class);
42 | try {
43 | dockerProxyExtension.before();
44 | URLConnection urlConnection = new URL("http://webserver").openConnection();
45 | urlConnection.connect();
46 | } finally {
47 | dockerProxyExtension.after();
48 | }
49 | }
50 |
51 | @Test
52 | void canReachDockerContainerByHostnameWithProjectSpecified() throws IOException, InterruptedException {
53 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
54 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionTest.class);
55 | try {
56 | dockerProxyExtension.before();
57 | URLConnection urlConnection = new URL("http://web").openConnection();
58 | urlConnection.connect();
59 | } finally {
60 | dockerProxyExtension.after();
61 | }
62 | }
63 |
64 | @Test
65 | void canReachDockerContainerByHostnameAndDomainNameWithProjectSpecified() throws IOException, InterruptedException {
66 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
67 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionTest.class);
68 | try {
69 | dockerProxyExtension.before();
70 | URLConnection urlConnection = new URL("http://web.server.here").openConnection();
71 | urlConnection.connect();
72 | } finally {
73 | dockerProxyExtension.after();
74 | }
75 | }
76 |
77 | @Test
78 | void canReachDockerContainerByContainerNameWithNetworkSpecified() throws IOException, InterruptedException {
79 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromNetworkName(
80 | DOCKER_COMPOSE_EXTENSION.projectName().asString() + "_default", DockerProxyExtensionTest.class);
81 | try {
82 | dockerProxyExtension.before();
83 | URLConnection urlConnection = new URL("http://webserver").openConnection();
84 | urlConnection.connect();
85 | } finally {
86 | dockerProxyExtension.after();
87 | }
88 | }
89 |
90 | @Test
91 | void canReachDockerContainerByHostnameWithNetworkSpecified() throws IOException, InterruptedException {
92 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromNetworkName(
93 | DOCKER_COMPOSE_EXTENSION.projectName().asString() + "_default", DockerProxyExtensionTest.class);
94 | try {
95 | dockerProxyExtension.before();
96 | URLConnection urlConnection = new URL("http://web").openConnection();
97 | urlConnection.connect();
98 | } finally {
99 | dockerProxyExtension.after();
100 | }
101 | }
102 |
103 | @Test
104 | void canReachDockerContainerByHostnameAndDomainNameWithNetworkSpecified() throws IOException, InterruptedException {
105 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromNetworkName(
106 | DOCKER_COMPOSE_EXTENSION.projectName().asString() + "_default", DockerProxyExtensionTest.class);
107 | try {
108 | dockerProxyExtension.before();
109 | URLConnection urlConnection = new URL("http://web.server.here").openConnection();
110 | urlConnection.connect();
111 | } finally {
112 | dockerProxyExtension.after();
113 | }
114 | }
115 |
116 | @Test
117 | void otherHostnamesStillResolve() throws IOException, InterruptedException {
118 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
119 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionTest.class);
120 | try {
121 | dockerProxyExtension.before();
122 | URLConnection urlConnection = new URL("http://www.palantir.com").openConnection();
123 | urlConnection.connect();
124 | } finally {
125 | dockerProxyExtension.after();
126 | }
127 | }
128 |
129 | @Test
130 | void runningProxyRuleBeforeDockerComposeRuleFails() {
131 | assertThatExceptionOfType(IllegalStateException.class)
132 | .isThrownBy(() -> DockerProxyExtension.fromNetworkName("doesnotexist", DockerProxyExtensionTest.class)
133 | .before());
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/docker-proxy-junit-jupiter/src/integrationTest/resources/DockerProxyExtensionTest-services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | webserver:
3 | hostname: web
4 | domainname: server.here
5 | image: nginx
6 |
--------------------------------------------------------------------------------
/docker-proxy-junit-jupiter/src/main/java/com/palantir/docker/proxy/DockerProxyExtension.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import com.palantir.docker.compose.DockerComposeExtension;
20 | import com.palantir.docker.compose.configuration.ProjectName;
21 | import com.palantir.docker.compose.execution.DockerExecutable;
22 | import java.io.IOException;
23 | import java.util.Optional;
24 | import java.util.function.Function;
25 | import org.junit.jupiter.api.extension.AfterAllCallback;
26 | import org.junit.jupiter.api.extension.BeforeAllCallback;
27 | import org.junit.jupiter.api.extension.ExtensionContext;
28 |
29 | public final class DockerProxyExtension extends DockerProxyManager
30 | implements BeforeAllCallback, AfterAllCallback {
31 | /**
32 | * Creates a {@link DockerProxyExtension} which will create a proxy and DNS so that tests can interface with docker
33 | * containers directly.
34 | *
35 | * @param dockerContainerInfoCreator A {@link Function} that creates the DockerContainerInfo to use
36 | * @param classToLogFor The class using {@link DockerProxyExtension}
37 | */
38 | DockerProxyExtension(
39 | Function dockerContainerInfoCreator, Class> classToLogFor) {
40 | super(
41 | customizer -> customizer.apply(DockerComposeExtension.builder()).build(),
42 | dockerContainerInfoCreator,
43 | classToLogFor);
44 | }
45 |
46 | /**
47 | * Creates a {@link DockerProxyExtension} using a {@link ProjectBasedDockerContainerInfo}.
48 | *
49 | * @param projectName The docker-compose-rule ProjectName to use to find the containers
50 | * @param classToLogFor The class using {@link DockerProxyExtension}
51 | */
52 | public static DockerProxyExtension fromProjectName(ProjectName projectName, Class> classToLogFor) {
53 | return new DockerProxyExtension(
54 | docker -> new ProjectBasedDockerContainerInfo(docker, projectName), classToLogFor);
55 | }
56 |
57 | /**
58 | * Creates a {@link DockerProxyExtension} using a {@link ProjectBasedDockerContainerInfo}.
59 | *
60 | * @param projectName The docker-compose-rule ProjectName to use to find the containers
61 | * @param imageNameOverride The docker image name to use instead of the default
62 | * @param classToLogFor The class using {@link DockerProxyExtension}
63 | */
64 | public static DockerProxyExtension fromProjectName(
65 | ProjectName projectName, Class> classToLogFor, String imageNameOverride) {
66 | return new DockerProxyExtension(
67 | docker -> new ProjectBasedDockerContainerInfo(
68 | docker, projectName, Optional.of(imageNameOverride), Optional.empty()),
69 | classToLogFor);
70 | }
71 |
72 | /**
73 | * Creates a {@link DockerProxyExtension} using a {@link ProjectBasedDockerContainerInfo}.
74 | *
75 | * @param projectName The docker-compose-rule ProjectName to use to find the containers
76 | * @param networkNameOverride The docker network name to use instead of the default
77 | * @param imageNameOverride The docker image name to use instead of the default
78 | * @param classToLogFor The class using {@link DockerProxyExtension}
79 | */
80 | public static DockerProxyExtension fromProjectName(
81 | ProjectName projectName, Class> classToLogFor, String imageNameOverride, String networkNameOverride) {
82 | return new DockerProxyExtension(
83 | docker -> new ProjectBasedDockerContainerInfo(
84 | docker, projectName, Optional.of(imageNameOverride), Optional.of(networkNameOverride)),
85 | classToLogFor);
86 | }
87 |
88 | /**
89 | * Creates a {@link DockerProxyExtension} using a {@link NetworkBasedDockerContainerInfo}.
90 | *
91 | * @param networkName The network name to use to find the containers
92 | * @param classToLogFor The class using {@link DockerProxyExtension}
93 | */
94 | public static DockerProxyExtension fromNetworkName(String networkName, Class> classToLogFor) {
95 | return new DockerProxyExtension(
96 | docker -> new NetworkBasedDockerContainerInfo(docker, networkName), classToLogFor);
97 | }
98 |
99 | /**
100 | * Creates a {@link DockerProxyExtension} using a {@link NetworkBasedDockerContainerInfo}.
101 | *
102 | * @param networkName The network name to use to find the containers
103 | * @param imageNameOverride The docker image name to use instead of the default
104 | * @param classToLogFor The class using {@link DockerProxyExtension}
105 | */
106 | public static DockerProxyExtension fromNetworkName(
107 | String networkName, Class> classToLogFor, String imageNameOverride) {
108 | return new DockerProxyExtension(
109 | docker -> new NetworkBasedDockerContainerInfo(docker, networkName, Optional.of(imageNameOverride)),
110 | classToLogFor);
111 | }
112 |
113 | @Override
114 | public void beforeAll(ExtensionContext _context) throws IOException, InterruptedException {
115 | before();
116 | }
117 |
118 | @Override
119 | public void afterAll(ExtensionContext _context) {
120 | after();
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core-jdk21/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.palantir.external-publish-jar'
2 | apply plugin: 'org.unbroken-dome.test-sets'
3 |
4 | testSets {
5 | integrationTest
6 | }
7 |
8 | build.dependsOn integrationTest
9 |
10 | dependencies {
11 | api project(':docker-proxy-rule-core')
12 |
13 | compileOnly group: 'com.google.auto.service', name: 'auto-service-annotations'
14 | annotationProcessor group: 'com.google.auto.service', name: 'auto-service'
15 |
16 | integrationTestImplementation project(':docker-proxy-junit-jupiter')
17 | integrationTestImplementation group: 'com.palantir.docker.compose', name: 'docker-compose-junit-jupiter'
18 |
19 | integrationTestImplementation group: 'junit', name: 'junit'
20 | integrationTestImplementation group: 'org.assertj', name: 'assertj-core'
21 | integrationTestImplementation group: 'org.junit.jupiter', name: 'junit-jupiter'
22 | }
23 |
24 | javaVersion {
25 | target = 21
26 | runtime = 21
27 | }
28 |
29 | moduleJvmArgs {
30 | opens 'java.base/java.net'
31 | }
32 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core-jdk21/src/integrationTest/java/com/palantir/docker/proxy/DockerProxyExtensionJdk21Test.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20 |
21 | import com.palantir.docker.compose.DockerComposeExtension;
22 | import com.palantir.docker.compose.connection.Container;
23 | import com.palantir.docker.compose.logging.LogDirectory;
24 | import java.io.IOException;
25 | import java.net.URL;
26 | import java.net.URLConnection;
27 | import org.junit.jupiter.api.Test;
28 | import org.junit.jupiter.api.extension.RegisterExtension;
29 |
30 | class DockerProxyExtensionJdk21Test {
31 | @RegisterExtension
32 | static final DockerComposeExtension DOCKER_COMPOSE_EXTENSION = DockerComposeExtension.builder()
33 | .file("src/integrationTest/resources/DockerProxyExtensionJdk21Test-services.yml")
34 | .saveLogsTo(LogDirectory.circleAwareLogDirectory(DockerProxyExtensionJdk21Test.class))
35 | .waitingForService("webserver", Container::areAllPortsOpen)
36 | .build();
37 |
38 | @Test
39 | void canReachDockerContainerByContainerNameWithProjectSpecified() throws IOException, InterruptedException {
40 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
41 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionJdk21Test.class);
42 | try {
43 | dockerProxyExtension.before();
44 | URLConnection urlConnection = new URL("http://webserver").openConnection();
45 | urlConnection.connect();
46 | } finally {
47 | dockerProxyExtension.after();
48 | }
49 | }
50 |
51 | @Test
52 | void canReachDockerContainerByHostnameWithProjectSpecified() throws IOException, InterruptedException {
53 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
54 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionJdk21Test.class);
55 | try {
56 | dockerProxyExtension.before();
57 | URLConnection urlConnection = new URL("http://web").openConnection();
58 | urlConnection.connect();
59 | } finally {
60 | dockerProxyExtension.after();
61 | }
62 | }
63 |
64 | @Test
65 | void canReachDockerContainerByHostnameAndDomainNameWithProjectSpecified() throws IOException, InterruptedException {
66 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
67 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionJdk21Test.class);
68 | try {
69 | dockerProxyExtension.before();
70 | URLConnection urlConnection = new URL("http://web.server.here").openConnection();
71 | urlConnection.connect();
72 | } finally {
73 | dockerProxyExtension.after();
74 | }
75 | }
76 |
77 | @Test
78 | void canReachDockerContainerByContainerNameWithNetworkSpecified() throws IOException, InterruptedException {
79 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromNetworkName(
80 | DOCKER_COMPOSE_EXTENSION.projectName().asString() + "_default", DockerProxyExtensionJdk21Test.class);
81 | try {
82 | dockerProxyExtension.before();
83 | URLConnection urlConnection = new URL("http://webserver").openConnection();
84 | urlConnection.connect();
85 | } finally {
86 | dockerProxyExtension.after();
87 | }
88 | }
89 |
90 | @Test
91 | void canReachDockerContainerByHostnameWithNetworkSpecified() throws IOException, InterruptedException {
92 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromNetworkName(
93 | DOCKER_COMPOSE_EXTENSION.projectName().asString() + "_default", DockerProxyExtensionJdk21Test.class);
94 | try {
95 | dockerProxyExtension.before();
96 | URLConnection urlConnection = new URL("http://web").openConnection();
97 | urlConnection.connect();
98 | } finally {
99 | dockerProxyExtension.after();
100 | }
101 | }
102 |
103 | @Test
104 | void canReachDockerContainerByHostnameAndDomainNameWithNetworkSpecified() throws IOException, InterruptedException {
105 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromNetworkName(
106 | DOCKER_COMPOSE_EXTENSION.projectName().asString() + "_default", DockerProxyExtensionJdk21Test.class);
107 | try {
108 | dockerProxyExtension.before();
109 | URLConnection urlConnection = new URL("http://web.server.here").openConnection();
110 | urlConnection.connect();
111 | } finally {
112 | dockerProxyExtension.after();
113 | }
114 | }
115 |
116 | @Test
117 | void otherHostnamesStillResolve() throws IOException, InterruptedException {
118 | DockerProxyExtension dockerProxyExtension = DockerProxyExtension.fromProjectName(
119 | DOCKER_COMPOSE_EXTENSION.projectName(), DockerProxyExtensionJdk21Test.class);
120 | try {
121 | dockerProxyExtension.before();
122 | URLConnection urlConnection = new URL("http://www.palantir.com").openConnection();
123 | urlConnection.connect();
124 | } finally {
125 | dockerProxyExtension.after();
126 | }
127 | }
128 |
129 | @Test
130 | void runningProxyRuleBeforeDockerComposeRuleFails() {
131 | assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> DockerProxyExtension.fromNetworkName(
132 | "doesnotexist", DockerProxyExtensionJdk21Test.class)
133 | .before());
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core-jdk21/src/integrationTest/resources/DockerProxyExtensionJdk21Test-services.yml:
--------------------------------------------------------------------------------
1 | services:
2 | webserver:
3 | hostname: web
4 | domainname: server.here
5 | image: nginx
6 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import java.net.InetAddress;
20 | import java.net.UnknownHostException;
21 | import java.net.spi.InetAddressResolver;
22 | import java.util.Arrays;
23 | import java.util.function.Supplier;
24 | import java.util.stream.Stream;
25 |
26 | public final class DockerProxyInetAddressResolver implements InetAddressResolver {
27 | private final Supplier dockerNameService;
28 |
29 | public DockerProxyInetAddressResolver(Supplier dockerNameService) {
30 | this.dockerNameService = dockerNameService;
31 | }
32 |
33 | @Override
34 | public Stream lookupByName(String host, LookupPolicy _lookupPolicy) throws UnknownHostException {
35 | return Arrays.stream(dockerNameService.get().lookupAllHostAddr(host));
36 | }
37 |
38 | @Override
39 | public String lookupByAddress(byte[] addr) throws UnknownHostException {
40 | return dockerNameService.get().getHostByAddr(addr);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/DockerProxyInetAddressResolverProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import com.google.auto.service.AutoService;
20 | import java.net.spi.InetAddressResolver;
21 | import java.net.spi.InetAddressResolverProvider;
22 |
23 | @AutoService(InetAddressResolverProvider.class)
24 | public final class DockerProxyInetAddressResolverProvider extends InetAddressResolverProvider {
25 |
26 | @Override
27 | public InetAddressResolver get(Configuration configuration) {
28 | return new ForwardingInetAddressResolver(
29 | new DockerProxyInetAddressResolver(DockerProxyManager::getDockerNameService),
30 | configuration.builtinResolver(),
31 | () -> DockerProxyManager.getDockerNameService() != null);
32 | }
33 |
34 | @Override
35 | public String name() {
36 | return "DockerProxyInetAddressResolverProvider";
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core-jdk21/src/main/java/com/palantir/docker/proxy/ForwardingInetAddressResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import java.net.InetAddress;
20 | import java.net.UnknownHostException;
21 | import java.net.spi.InetAddressResolver;
22 | import java.util.function.BooleanSupplier;
23 | import java.util.stream.Stream;
24 |
25 | class ForwardingInetAddressResolver implements InetAddressResolver {
26 | private final InetAddressResolver delegate;
27 | private final InetAddressResolver fallback;
28 | private final BooleanSupplier delegateEnabled;
29 |
30 | ForwardingInetAddressResolver(
31 | InetAddressResolver delegate, InetAddressResolver fallback, BooleanSupplier delegateEnabled) {
32 | this.delegate = delegate;
33 | this.fallback = fallback;
34 | this.delegateEnabled = delegateEnabled;
35 | }
36 |
37 | @Override
38 | public Stream lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException {
39 | if (!delegateEnabled.getAsBoolean()) {
40 | return fallback.lookupByName(host, lookupPolicy);
41 | }
42 |
43 | try {
44 | return delegate.lookupByName(host, lookupPolicy);
45 | } catch (UnknownHostException e) {
46 | if (fallback != null) {
47 | return fallback.lookupByName(host, lookupPolicy);
48 | }
49 | throw e;
50 | }
51 | }
52 |
53 | @Override
54 | public String lookupByAddress(byte[] addr) throws UnknownHostException {
55 | if (!delegateEnabled.getAsBoolean()) {
56 | return fallback.lookupByAddress(addr);
57 | }
58 |
59 | try {
60 | return delegate.lookupByAddress(addr);
61 | } catch (UnknownHostException e) {
62 | if (fallback != null) {
63 | return fallback.lookupByAddress(addr);
64 | }
65 | throw e;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.palantir.external-publish-jar'
2 |
3 | dependencies {
4 | api group: 'com.palantir.docker.compose', name: 'docker-compose-rule-core'
5 | implementation group: 'one.util', name: 'streamex'
6 |
7 | runtimeOnly project(":docker-proxy-rule-core-jdk21")
8 |
9 | testImplementation group: 'junit', name: 'junit'
10 | testImplementation group: 'org.assertj', name: 'assertj-core'
11 | testImplementation group: 'org.mockito', name: 'mockito-core'
12 | testRuntimeOnly group: 'org.mockito', name: 'mockito-inline'
13 | }
14 |
15 | moduleJvmArgs {
16 | opens 'java.base/java.net'
17 | }
18 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/CachingDockerContainerInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import com.google.common.annotations.VisibleForTesting;
20 | import com.google.common.cache.CacheBuilder;
21 | import com.google.common.cache.CacheLoader;
22 | import com.google.common.cache.LoadingCache;
23 | import java.util.Optional;
24 | import java.util.concurrent.TimeUnit;
25 |
26 | /**
27 | * CachingDockerContainerInfo will cache and refresh container info. If the refresh
28 | * fails four times in a row, the entry will be removed and the next call will return
29 | * the exception to you if it happens again.
30 | */
31 | @SuppressWarnings("checkstyle:BanGuavaCaches")
32 | public final class CachingDockerContainerInfo implements DockerContainerInfo {
33 | private final DockerContainerInfo delegate;
34 | private final LoadingCache> ipForHostCache;
35 | private final LoadingCache> hostForIpCache;
36 |
37 | public CachingDockerContainerInfo(DockerContainerInfo delegate) {
38 | // It takes up to 1s to query docker so we set this to be under a multiple of 5, 10, and 15 by at least 2s
39 | this(delegate, 53, TimeUnit.SECONDS);
40 | }
41 |
42 | @VisibleForTesting
43 | CachingDockerContainerInfo(DockerContainerInfo delegate, long refreshDuration, TimeUnit refreshUnit) {
44 | this.delegate = delegate;
45 | this.ipForHostCache = CacheBuilder.newBuilder()
46 | .expireAfterWrite(4 * refreshDuration, refreshUnit)
47 | .refreshAfterWrite(refreshDuration, refreshUnit)
48 | .build(CacheLoader.from(delegate::getIpForHost));
49 | this.hostForIpCache = CacheBuilder.newBuilder()
50 | .expireAfterWrite(4 * refreshDuration, refreshUnit)
51 | .refreshAfterWrite(refreshDuration / 4, refreshUnit)
52 | .build(CacheLoader.from(delegate::getHostForIp));
53 | }
54 |
55 | @Override
56 | public Optional getIpForHost(String hostname) {
57 | Optional ip = ipForHostCache.getUnchecked(hostname);
58 | if (!ip.isPresent()) {
59 | ipForHostCache.invalidate(hostname);
60 | }
61 | return ip;
62 | }
63 |
64 | @Override
65 | public Optional getHostForIp(String ip) {
66 | Optional host = hostForIpCache.getUnchecked(ip);
67 | if (!host.isPresent()) {
68 | hostForIpCache.invalidate(ip);
69 | }
70 | return host;
71 | }
72 |
73 | @Override
74 | public String getNetworkName() {
75 | return delegate.getNetworkName();
76 | }
77 |
78 | @Override
79 | public Optional getImageNameOverride() {
80 | return delegate.getImageNameOverride();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerContainerInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import java.util.Optional;
20 |
21 | public interface DockerContainerInfo {
22 | /**
23 | * Converts a docker IP to a docker hostname if it exists.
24 | *
25 | * @param hostname The docker hostname to lookup
26 | * @return The docker IP for a docker hostname if it exists
27 | */
28 | Optional getIpForHost(String hostname);
29 |
30 | /**
31 | * Converts a docker hostname to a docker IP if it exists.
32 | *
33 | * @param ip The docker ip to lookup
34 | * @return The docker hostname for a docker IP address if it exists
35 | */
36 | Optional getHostForIp(String ip);
37 |
38 | /**
39 | * Returns the network name the proxy will connect to.
40 | *
41 | * @return The network for the proxy to connect to
42 | */
43 | String getNetworkName();
44 |
45 | /**
46 | * Returns an override for the image name to use for the docker container,
47 | * otherwise `vimagick/dante:latest` will get used.
48 | */
49 | Optional getImageNameOverride();
50 | }
51 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerContainerInfoUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import com.google.common.annotations.VisibleForTesting;
20 | import com.google.common.base.CharMatcher;
21 | import com.google.common.base.Preconditions;
22 | import com.google.common.base.Splitter;
23 | import com.google.common.base.Throwables;
24 | import com.google.common.collect.ImmutableList;
25 | import com.google.common.collect.Iterables;
26 | import com.google.common.io.CharStreams;
27 | import com.google.common.net.InetAddresses;
28 | import com.palantir.docker.compose.configuration.ProjectName;
29 | import com.palantir.docker.compose.execution.DockerExecutable;
30 | import java.io.IOException;
31 | import java.io.InputStream;
32 | import java.io.InputStreamReader;
33 | import java.nio.charset.StandardCharsets;
34 | import java.util.List;
35 | import java.util.Optional;
36 | import java.util.concurrent.TimeUnit;
37 | import java.util.stream.Collectors;
38 | import one.util.streamex.StreamEx;
39 |
40 | public final class DockerContainerInfoUtils {
41 | private static final ImmutableList DOCKER_NAME_TAGS = ImmutableList.of(
42 | "{{ .Name }}", "{{ .Config.Hostname }}", "{{ .Config.Hostname }}.{{ .Config.Domainname }}");
43 | private static final ImmutableList DOCKER_NAME_LABELS =
44 | ImmutableList.of("com.docker.compose.service", "hostname");
45 |
46 | @VisibleForTesting
47 | static final String IP_FORMAT_STRING = "{{ range .NetworkSettings.Networks }}{{ .IPAddress }}{{ end }}";
48 |
49 | private DockerContainerInfoUtils() {
50 | // Utility class
51 | }
52 |
53 | public static List getAllNamesForContainerId(DockerExecutable docker, String containerId) {
54 | try {
55 | String labelsFormat = StreamEx.of(DOCKER_NAME_LABELS)
56 | .map(label -> String.format("{{ index .Config.Labels \"%s\" }}", label))
57 | .append(DOCKER_NAME_TAGS)
58 | .collect(Collectors.joining(","));
59 | String labelsString = Iterables.getOnlyElement(
60 | runDockerProcess(docker, "inspect", "--format", labelsFormat, containerId));
61 | return Splitter.on(CharMatcher.anyOf(",/")).omitEmptyStrings().splitToList(labelsString);
62 | } catch (IOException | InterruptedException e) {
63 | throw Throwables.propagate(e);
64 | }
65 | }
66 |
67 | public static Optional getContainerIpFromId(DockerExecutable docker, String containerId) {
68 | try {
69 | String ip = Iterables.getOnlyElement(
70 | runDockerProcess(docker, "inspect", "--format", IP_FORMAT_STRING, containerId));
71 |
72 | // stopped containers don't return IPs
73 | if (ip.trim().isEmpty()) {
74 | return Optional.empty();
75 | }
76 |
77 | Preconditions.checkState(InetAddresses.isInetAddress(ip), "IP address is not valid: %s", ip);
78 | return Optional.of(ip);
79 | } catch (InterruptedException | IOException | RuntimeException e) {
80 | throw new IllegalStateException("Couldn't get IP for container ID " + containerId, e);
81 | }
82 | }
83 |
84 | public static List getContainerIdsOnNetwork(DockerExecutable docker, String networkName) {
85 | try {
86 | String containersOnNetworkString = Iterables.getOnlyElement(DockerContainerInfoUtils.runDockerProcess(
87 | docker,
88 | "network",
89 | "inspect",
90 | "--format",
91 | "{{ range $container, $_ := .Containers }}{{ $container }},{{ end }}",
92 | networkName));
93 |
94 | return Splitter.on(',').omitEmptyStrings().splitToList(containersOnNetworkString);
95 | } catch (InterruptedException | IOException | RuntimeException e) {
96 | throw new IllegalStateException("Unable to find the container IDs on the network " + networkName, e);
97 | }
98 | }
99 |
100 | public static List getContainerIdsInDockerComposeProject(DockerExecutable docker, ProjectName projectName) {
101 | try {
102 | return DockerContainerInfoUtils.runDockerProcess(
103 | docker,
104 | "ps",
105 | "--filter",
106 | "label=com.docker.compose.project=" + projectName.asString(),
107 | "--format",
108 | "{{ .ID }}");
109 | } catch (IOException | InterruptedException | RuntimeException e) {
110 | throw new IllegalStateException(
111 | "Unable to get container IDs in the docker compose project " + projectName.asString(), e);
112 | }
113 | }
114 |
115 | private static List runDockerProcess(DockerExecutable docker, String... args)
116 | throws IOException, InterruptedException {
117 | Process process = docker.execute(args);
118 | if (!process.waitFor(15, TimeUnit.SECONDS) || process.exitValue() != 0) {
119 | throw new IllegalStateException("Unable to execute docker command: " + ImmutableList.copyOf(args));
120 | }
121 | return getLinesFromInputStream(process.getInputStream());
122 | }
123 |
124 | private static List getLinesFromInputStream(InputStream inputStream) throws IOException {
125 | try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
126 | return CharStreams.readLines(inputStreamReader);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerNameService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import com.google.common.net.InetAddresses;
20 | import java.net.InetAddress;
21 | import java.net.UnknownHostException;
22 | import java.util.Optional;
23 |
24 | public final class DockerNameService {
25 | private final DockerContainerInfo containerInfo;
26 |
27 | public DockerNameService(DockerContainerInfo containerInfo) {
28 | this.containerInfo = containerInfo;
29 | }
30 |
31 | public InetAddress[] lookupAllHostAddr(String hostname) throws UnknownHostException {
32 | Optional containerIp = containerInfo.getIpForHost(hostname);
33 |
34 | if (containerIp.isPresent()) {
35 | return new InetAddress[] {InetAddresses.forString(containerIp.get())};
36 | }
37 | throw new UnknownHostException(hostname);
38 | }
39 |
40 | public String getHostByAddr(byte[] bytes) throws UnknownHostException {
41 | String ipAddress = InetAddress.getByAddress(bytes).getHostAddress();
42 | Optional containerHost = containerInfo.getHostForIp(ipAddress);
43 |
44 | if (containerHost.isPresent()) {
45 | return containerHost.get();
46 | }
47 | throw new UnknownHostException(ipAddress);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docker-proxy-rule-core/src/main/java/com/palantir/docker/proxy/DockerProxyManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.palantir.docker.proxy;
18 |
19 | import com.google.common.base.Throwables;
20 | import com.google.common.io.Files;
21 | import com.google.common.io.Resources;
22 | import com.palantir.docker.compose.DockerComposeManager;
23 | import com.palantir.docker.compose.connection.Container;
24 | import com.palantir.docker.compose.connection.DockerMachine;
25 | import com.palantir.docker.compose.execution.DockerExecutable;
26 | import com.palantir.docker.compose.execution.DockerExecutionException;
27 | import com.palantir.docker.compose.logging.LogDirectory;
28 | import java.io.File;
29 | import java.io.IOException;
30 | import java.lang.reflect.Field;
31 | import java.lang.reflect.InvocationHandler;
32 | import java.lang.reflect.InvocationTargetException;
33 | import java.lang.reflect.Method;
34 | import java.lang.reflect.Proxy;
35 | import java.net.InetAddress;
36 | import java.net.ProxySelector;
37 | import java.net.UnknownHostException;
38 | import java.nio.charset.StandardCharsets;
39 | import java.util.List;
40 | import java.util.function.Function;
41 | import java.util.function.UnaryOperator;
42 | import javax.annotation.Nullable;
43 |
44 | @SuppressWarnings("PreferSafeLoggableExceptions")
45 | abstract class DockerProxyManager> {
46 | private final DockerContainerInfo dockerContainerInfo;
47 | private final DockerComposeManager dockerComposeRule;
48 |
49 | private ProxySelector originalProxySelector;
50 | private Object originalNameService;
51 |
52 | @Nullable
53 | private static DockerNameService dockerNameService;
54 |
55 | /**
56 | * Creates a {@link DockerProxyManager} which will create a proxy and DNS so that
57 | * tests can interface with docker containers directly.
58 | *
59 | * @param dockerContainerInfoCreator A {@link Function} that creates the DockerContainerInfo to use
60 | * @param classToLogFor The class using {@link DockerProxyManager}
61 | */
62 | DockerProxyManager(
63 | Customizer builderSupplier,
64 | Function dockerContainerInfoCreator,
65 | Class> classToLogFor) {
66 | DockerContainerInfo builtDockerContainerInfo = dockerContainerInfoCreator.apply(DockerExecutable.builder()
67 | .dockerConfiguration(DockerMachine.localMachine().build())
68 | .build());
69 | String logDirectory = DockerProxyManager.class.getSimpleName() + "-" + classToLogFor.getSimpleName();
70 | this.dockerContainerInfo = new CachingDockerContainerInfo(builtDockerContainerInfo);
71 | this.dockerComposeRule = builderSupplier.customize(builder -> builder.file(getDockerComposeFile(
72 | this.dockerContainerInfo.getNetworkName(),
73 | this.dockerContainerInfo.getImageNameOverride().orElse("vimagick/dante:latest"))
74 | .getPath())
75 | .waitingForService("proxy", Container::areAllPortsOpen)
76 | .saveLogsTo(LogDirectory.circleAwareLogDirectory(logDirectory)));
77 | }
78 |
79 | public interface Customizer {
80 | DockerComposeManager customize(UnaryOperator customizeFunction);
81 | }
82 |
83 | public void before() throws IOException, InterruptedException {
84 | try {
85 | originalProxySelector = ProxySelector.getDefault();
86 | dockerComposeRule.before();
87 | setNameService(new DockerNameService(dockerContainerInfo));
88 | ProxySelector.setDefault(new DockerProxySelector(
89 | dockerComposeRule.containers(), dockerContainerInfo, originalProxySelector));
90 | } catch (DockerExecutionException e) {
91 | if (e.getMessage().contains("declared as external")) {
92 | throw new IllegalStateException(
93 | "DockerComposeRule must run before DockerProxyRule. Please use a RuleChain.", e);
94 | } else {
95 | throw e;
96 | }
97 | }
98 | }
99 |
100 | public void after() {
101 | ProxySelector.setDefault(originalProxySelector);
102 | unsetNameService();
103 | dockerComposeRule.after();
104 | }
105 |
106 | private static File getDockerComposeFile(String networkName, String imageName) {
107 | try {
108 | File proxyFile = File.createTempFile("proxy", ".yml");
109 | String proxyConfig =
110 | Resources.toString(Resources.getResource("docker-compose.proxy.yml"), StandardCharsets.UTF_8);
111 | Files.write(
112 | proxyConfig.replace("{{NETWORK_NAME}}", networkName).replace("{{IMAGE_NAME}}", imageName),
113 | proxyFile,
114 | StandardCharsets.UTF_8);
115 | return proxyFile;
116 | } catch (IOException e) {
117 | throw Throwables.propagate(e);
118 | }
119 | }
120 |
121 | private void setNameService(DockerNameService nameService) {
122 | int featureVersion = Runtime.version().feature();
123 | if (featureVersion < 9) {
124 | getJava8NameServices().add(0, wrapNameService("sun.net.spi.nameservice.NameService", nameService, null));
125 | } else if (featureVersion < 21) {
126 | originalNameService = getJava9NameService();
127 | setJava9NameService(wrapNameService("java.net.InetAddress$NameService", nameService, originalNameService));
128 | } else {
129 | dockerNameService = nameService;
130 | }
131 | }
132 |
133 | private void unsetNameService() {
134 | int featureVersion = Runtime.version().feature();
135 | if (featureVersion < 9) {
136 | getJava8NameServices().remove(0);
137 | } else if (featureVersion < 21) {
138 | setJava9NameService(originalNameService);
139 | } else {
140 | dockerNameService = null;
141 | }
142 | }
143 |
144 | @Nullable
145 | public static DockerNameService getDockerNameService() {
146 | return dockerNameService;
147 | }
148 |
149 | @SuppressWarnings("unchecked")
150 | private static List