├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── pom.xml
└── src
└── main
└── java
└── com
└── microsoft
└── azure
└── management
├── samples
├── SSHShell.java
└── DockerUtils.java
└── containerservice
└── samples
└── DeployImageFromContainerRegistryToContainerServiceOrchestrator.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Auth filed
4 | *.auth
5 | *.azureauth
6 |
7 | # Mobile Tools for Java (J2ME)
8 | .mtj.tmp/
9 |
10 | # Package Files #
11 | *.jar
12 | *.war
13 | *.ear
14 |
15 | # Azure Tooling #
16 | node_modules
17 | packages
18 |
19 | # Eclipse #
20 | *.pydevproject
21 | .project
22 | .metadata
23 | bin/**
24 | tmp/**
25 | tmp/**/*
26 | *.tmp
27 | *.bak
28 | *.swp
29 | *~.nib
30 | local.properties
31 | .classpath
32 | .settings/
33 | .loadpath
34 |
35 | # Other Tooling #
36 | .classpath
37 | .project
38 | target/
39 | .idea
40 | *.iml
41 |
42 | # Mac OS #
43 | .DS_Store
44 | .DS_Store?
45 |
46 | # Windows #
47 | Thumbs.db
48 |
49 | # reduced pom files should not be included
50 | dependency-reduced-pom.xml
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Azure samples
2 |
3 | Thank you for your interest in contributing to Azure samples!
4 |
5 | ## Ways to contribute
6 |
7 | You can contribute to [Azure samples](https://azure.microsoft.com/documentation/samples/) in a few different ways:
8 |
9 | - Submit feedback on [this sample page](https://azure.microsoft.com/documentation/samples/acs-java-deploy-image-from-acr-to-acs-orchestrator/) whether it was helpful or not.
10 | - Submit issues through [issue tracker](https://github.com/Azure-Samples/acs-java-deploy-image-from-acr-to-acs-orchestrator/issues) on GitHub. We are actively monitoring the issues and improving our samples.
11 | - If you wish to make code changes to samples, or contribute something new, please follow the [GitHub Forks / Pull requests model](https://help.github.com/articles/fork-a-repo/): Fork the sample repo, make the change and propose it back by submitting a pull request.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | languages:
4 | - java
5 | products:
6 | - azure
7 | extensions:
8 | services: Containerservice
9 | platforms: java
10 | ---
11 |
12 | **Please use [azure-resourcemanager](https://aka.ms/azsdk/java/mgmt), see [sample](https://github.com/azure-samples/aks-java-deploy-image-from-acr-to-kubernetes/).**
13 |
14 | # Getting Started with Containerservice - Deploy Image From Container Registry To Container Service Orchestrator - in Java #
15 |
16 |
17 | Azure Container Registry sample for deploying a container image to Azure Container Service with Kubernetes orchestration.
18 | - Create an Azure Container Registry to be used for holding the Docker images
19 | - If a local Docker engine cannot be found, create a Linux virtual machine that will host a Docker engine to be used for this sample
20 | - Use Docker Java to create a Docker client that will push/pull an image to/from Azure Container Registry
21 | - Pull a test image from the public Docker repo (tomcat:8) to be used as a sample for pushing/pulling to/from an Azure Container Registry
22 | - Create a new Docker container from an image that was pulled from Azure Container Registry
23 | - Create a SSH private/public key to be used when creating a container service
24 | - Create an Azure Container Service with Kubernetes orchestration
25 | - Log in via the SSH client and download the Kubernetes config
26 | - Create a Kubernetes client using the Kubernetes config file downloaded from one of the virtual machine managers
27 | - Create a Kubernetes namespace
28 | - Create a Kubernetes secret of type "docker-registry" using the Azure Container Registry credentials from above
29 | - Create a Kubernetes replication controller using a container image from the Azure private registry from above and a load balancer service that will expose the app to the world
30 |
31 |
32 | ## Running this Sample ##
33 |
34 | To run this sample:
35 |
36 | Set the environment variable `AZURE_AUTH_LOCATION` with the full path for an auth file. See [how to create an auth file](https://github.com/Azure/azure-libraries-for-java/blob/master/AUTH.md).
37 |
38 | git clone https://github.com/Azure-Samples/acs-java-deploy-image-from-acr-to-acs-orchestrator.git
39 |
40 | cd acs-java-deploy-image-from-acr-to-acs-orchestrator
41 |
42 | mvn clean compile exec:java
43 |
44 | ## More information ##
45 |
46 | [http://azure.com/java](http://azure.com/java)
47 |
48 | If you don't have a Microsoft Azure subscription you can get a FREE trial account [here](http://go.microsoft.com/fwlink/?LinkId=330212)
49 |
50 | ---
51 |
52 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
53 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.microsoft.azure
6 | acs-java-deploy-image-from-acr-to-acs-orchestrator
7 | 0.0.1-SNAPSHOT
8 | DeployImageFromContainerRegistryToContainerServiceOrchestrator.java
9 |
10 | https://github.com/Azure/acs-java-deploy-image-from-acr-to-acs-orchestrator
11 |
12 |
13 |
14 | org.codehaus.mojo
15 | exec-maven-plugin
16 | 1.4.0
17 |
18 | com.microsoft.azure.management.containerservice.samples.DeployImageFromContainerRegistryToContainerServiceOrchestrator
19 |
20 |
21 |
22 | maven-compiler-plugin
23 | 3.0
24 |
25 | 1.7
26 | 1.7
27 |
28 |
29 |
30 | maven-assembly-plugin
31 |
32 |
33 | package
34 |
35 | attached
36 |
37 |
38 |
39 | jar-with-dependencies
40 |
41 |
42 |
43 | com.microsoft.azure.management.containerservice.samples.DeployImageFromContainerRegistryToContainerServiceOrchestrator.java
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | com.microsoft.azure
55 | azure
56 | 1.36.3
57 |
58 |
59 | commons-net
60 | commons-net
61 | 3.3
62 |
63 |
64 | commons-lang
65 | commons-lang
66 | 2.6
67 |
68 |
69 | org.apache.commons
70 | commons-lang3
71 | 3.7
72 |
73 |
74 | com.jcraft
75 | jsch
76 | 0.1.55
77 |
78 |
79 | com.github.cverges.expect4j
80 | expect4j
81 | 1.6
82 |
83 |
84 | com.github.docker-java
85 | docker-java
86 | 3.0.6
87 |
88 |
89 | io.fabric8
90 | kubernetes-client
91 | 4.3.0
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/azure/management/samples/SSHShell.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for
4 | * license information.
5 | */
6 |
7 | package com.microsoft.azure.management.samples;
8 |
9 | import com.jcraft.jsch.Channel;
10 | import com.jcraft.jsch.ChannelExec;
11 | import com.jcraft.jsch.ChannelSftp;
12 | import com.jcraft.jsch.ChannelShell;
13 | import com.jcraft.jsch.JSch;
14 | import com.jcraft.jsch.JSchException;
15 | import com.jcraft.jsch.KeyPair;
16 | import com.jcraft.jsch.Session;
17 | import expect4j.Closure;
18 | import expect4j.Expect4j;
19 | import expect4j.ExpectState;
20 | import expect4j.matches.Match;
21 | import expect4j.matches.RegExpMatch;
22 | import org.apache.oro.text.regex.MalformedPatternException;
23 |
24 | import java.io.BufferedOutputStream;
25 | import java.io.ByteArrayOutputStream;
26 | import java.io.IOException;
27 | import java.io.InputStream;
28 | import java.util.ArrayList;
29 | import java.util.Hashtable;
30 | import java.util.List;
31 |
32 | /**
33 | * Utility class to run commands on Linux VM via SSH.
34 | */
35 | public final class SSHShell {
36 | private final Session session;
37 | private final ChannelShell channel;
38 | private final Expect4j expect;
39 | private final StringBuilder shellBuffer = new StringBuilder();
40 | private List linuxPromptMatches = new ArrayList<>();
41 |
42 | /**
43 | * Creates SSHShell.
44 | *
45 | * @param host the host name
46 | * @param port the ssh port
47 | * @param userName the ssh user name
48 | * @param password the ssh password
49 | * @return the shell
50 | * @throws JSchException
51 | * @throws IOException
52 | */
53 | private SSHShell(String host, int port, String userName, String password)
54 | throws JSchException, IOException {
55 | Closure expectClosure = getExpectClosure();
56 | for (String linuxPromptPattern : new String[]{"\\>", "#", "~#", "~\\$"}) {
57 | try {
58 | Match match = new RegExpMatch(linuxPromptPattern, expectClosure);
59 | linuxPromptMatches.add(match);
60 | } catch (MalformedPatternException malformedEx) {
61 | throw new RuntimeException(malformedEx);
62 | }
63 | }
64 | JSch jsch = new JSch();
65 | this.session = jsch.getSession(userName, host, port);
66 | session.setPassword(password);
67 | Hashtable config = new Hashtable<>();
68 | config.put("StrictHostKeyChecking", "no");
69 | session.setConfig(config);
70 | session.connect(60000);
71 | this.channel = (ChannelShell) session.openChannel("shell");
72 | this.expect = new Expect4j(channel.getInputStream(), channel.getOutputStream());
73 | channel.connect();
74 | }
75 |
76 | /**
77 | * Creates SSHShell.
78 | *
79 | * @param host the host name
80 | * @param port the ssh port
81 | * @param userName the ssh user name
82 | * @param sshPrivateKey the ssh password
83 | * @return the shell
84 | * @throws JSchException
85 | * @throws IOException
86 | */
87 | private SSHShell(String host, int port, String userName, byte[] sshPrivateKey)
88 | throws JSchException, IOException {
89 | Closure expectClosure = getExpectClosure();
90 | for (String linuxPromptPattern : new String[]{"\\>", "#", "~#", "~\\$"}) {
91 | try {
92 | Match match = new RegExpMatch(linuxPromptPattern, expectClosure);
93 | linuxPromptMatches.add(match);
94 | } catch (MalformedPatternException malformedEx) {
95 | throw new RuntimeException(malformedEx);
96 | }
97 | }
98 | JSch jsch = new JSch();
99 | jsch.setKnownHosts(System.getProperty("user.home") + "/.ssh/known_hosts");
100 | jsch.addIdentity(host, sshPrivateKey, (byte[]) null, (byte[]) null);
101 | this.session = jsch.getSession(userName, host, port);
102 | this.session.setConfig("StrictHostKeyChecking", "no");
103 | this.session.setConfig("PreferredAuthentications", "publickey,keyboard-interactive,password");
104 | session.connect(60000);
105 | this.channel = (ChannelShell) session.openChannel("shell");
106 | this.expect = new Expect4j(channel.getInputStream(), channel.getOutputStream());
107 | channel.connect();
108 | }
109 |
110 | /**
111 | * Opens a SSH shell.
112 | *
113 | * @param host the host name
114 | * @param port the ssh port
115 | * @param userName the ssh user name
116 | * @param password the ssh password
117 | * @return the shell
118 | * @throws JSchException exception thrown
119 | * @throws IOException IO exception thrown
120 | */
121 | public static SSHShell open(String host, int port, String userName, String password)
122 | throws JSchException, IOException {
123 | return new SSHShell(host, port, userName, password);
124 | }
125 |
126 | /**
127 | * Opens a SSH shell.
128 | *
129 | * @param host the host name
130 | * @param port the ssh port
131 | * @param userName the ssh user name
132 | * @param sshPrivateKey the ssh private key
133 | * @return the shell
134 | * @throws JSchException exception thrown
135 | * @throws IOException IO exception thrown
136 | */
137 | public static SSHShell open(String host, int port, String userName, byte[] sshPrivateKey)
138 | throws JSchException, IOException {
139 | return new SSHShell(host, port, userName, sshPrivateKey);
140 | }
141 |
142 | /**
143 | * Runs a given list of commands in the shell.
144 | *
145 | * @param commands the commands
146 | * @return the result
147 | * @throws Exception exception thrown
148 | */
149 | public String runCommands(List commands) throws Exception {
150 | String output = null;
151 | try {
152 | for (String command : commands) {
153 | expect.expect(this.linuxPromptMatches);
154 | expect.send(command);
155 | expect.send("\r");
156 | expect.expect(this.linuxPromptMatches);
157 | }
158 | output = shellBuffer.toString();
159 | } finally {
160 | shellBuffer.setLength(0);
161 | }
162 | return output;
163 | }
164 |
165 | /**
166 | * Executes a command on the remote host.
167 | *
168 | * @param command the command to be executed
169 | * @param getExitStatus return the exit status captured in the stdout
170 | * @param withErr capture the stderr as part of the output
171 | * @return the content of the remote output from executing the command
172 | * @throws Exception exception thrown
173 | */
174 | public String executeCommand(String command, Boolean getExitStatus, Boolean withErr) throws Exception {
175 | String result = "";
176 | String resultErr = "";
177 |
178 | Channel channel = this.session.openChannel("exec");
179 | ((ChannelExec) channel).setCommand(command);
180 | InputStream commandOutput = channel.getInputStream();
181 | InputStream commandErr = ((ChannelExec) channel).getErrStream();
182 | channel.connect();
183 | byte[] tmp = new byte[4096];
184 | while (true) {
185 | while (commandOutput.available() > 0) {
186 | int i = commandOutput.read(tmp, 0, 4096);
187 | if (i < 0) {
188 | break;
189 | }
190 | result += new String(tmp, 0, i);
191 | }
192 | while (commandErr.available() > 0) {
193 | int i = commandErr.read(tmp, 0, 4096);
194 | if (i < 0) {
195 | break;
196 | }
197 | resultErr += new String(tmp, 0, i);
198 | }
199 | if (channel.isClosed()) {
200 | if (commandOutput.available() > 0) {
201 | continue;
202 | }
203 | if (getExitStatus) {
204 | result += "exit-status: " + channel.getExitStatus();
205 | if (withErr) {
206 | result += "\n With error:\n" + resultErr;
207 | }
208 | }
209 | break;
210 | }
211 | try {
212 | Thread.sleep(100);
213 | } catch (Exception ee) { }
214 | }
215 | channel.disconnect();
216 |
217 | return result;
218 | }
219 |
220 | /**
221 | * Downloads the content of a file from the remote host as a String.
222 | *
223 | * @param fileName the name of the file for which the content will be downloaded
224 | * @param fromPath the path of the file for which the content will be downloaded
225 | * @param isUserHomeBased true if the path of the file is relative to the user's home directory
226 | * @return the content of the file
227 | * @throws Exception exception thrown
228 | */
229 | public String download(String fileName, String fromPath, boolean isUserHomeBased) throws Exception {
230 | ChannelSftp channel = (ChannelSftp) this.session.openChannel("sftp");
231 | channel.connect();
232 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
233 | BufferedOutputStream buff = new BufferedOutputStream(outputStream);
234 | String absolutePath = isUserHomeBased ? channel.getHome() + "/" + fromPath : fromPath;
235 | channel.cd(absolutePath);
236 | channel.get(fileName, buff);
237 |
238 | channel.disconnect();
239 |
240 | return outputStream.toString();
241 | }
242 |
243 | /**
244 | * Creates a new file on the remote host using the input content.
245 | *
246 | * @param from the byte array content to be uploaded
247 | * @param fileName the name of the file for which the content will be saved into
248 | * @param toPath the path of the file for which the content will be saved into
249 | * @param isUserHomeBased true if the path of the file is relative to the user's home directory
250 | * @param filePerm file permissions to be set
251 | * @throws Exception exception thrown
252 | */
253 | public void upload(InputStream from, String fileName, String toPath, boolean isUserHomeBased, String filePerm) throws Exception {
254 | ChannelSftp channel = (ChannelSftp) this.session.openChannel("sftp");
255 | channel.connect();
256 | String absolutePath = isUserHomeBased ? channel.getHome() + "/" + toPath : toPath;
257 |
258 | String path = "";
259 | for (String dir : absolutePath.split("/")) {
260 | path = path + "/" + dir;
261 | try {
262 | channel.mkdir(path);
263 | } catch (Exception ee) {
264 | }
265 | }
266 | channel.cd(absolutePath);
267 | channel.put(from, fileName);
268 | if (filePerm != null) {
269 | channel.chmod(Integer.parseInt(filePerm), absolutePath + "/" + fileName);
270 | }
271 |
272 | channel.disconnect();
273 | }
274 |
275 | /**
276 | * Closes shell.
277 | */
278 | public void close() {
279 | if (expect != null) {
280 | expect.close();
281 | }
282 | if (channel != null) {
283 | channel.disconnect();
284 | }
285 | if (session != null) {
286 | session.disconnect();
287 | }
288 | }
289 |
290 | private Closure getExpectClosure() {
291 | return new Closure() {
292 | public void run(ExpectState expectState) throws Exception {
293 | String outputBuffer = expectState.getBuffer();
294 | System.out.println(outputBuffer);
295 | shellBuffer.append(outputBuffer);
296 | expectState.exp_continue();
297 | }
298 | };
299 | }
300 |
301 | /**
302 | * Automatically generate SSH keys.
303 | * @param passPhrase the byte array content to be uploaded
304 | * @param comment the name of the file for which the content will be saved into
305 | * @return SSH public and private key
306 | * @throws Exception exception thrown
307 | */
308 | public static SshPublicPrivateKey generateSSHKeys(String passPhrase, String comment) throws Exception {
309 | JSch jsch = new JSch();
310 | KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA);
311 | ByteArrayOutputStream privateKeyBuff = new ByteArrayOutputStream(2048);
312 | ByteArrayOutputStream publicKeyBuff = new ByteArrayOutputStream(2048);
313 |
314 | keyPair.writePublicKey(publicKeyBuff, (comment != null) ? comment : "SSHCerts");
315 |
316 | if (passPhrase == null || passPhrase.isEmpty()) {
317 | keyPair.writePrivateKey(privateKeyBuff);
318 | } else {
319 | keyPair.writePrivateKey(privateKeyBuff, passPhrase.getBytes());
320 | }
321 |
322 | return new SshPublicPrivateKey(privateKeyBuff.toString(), publicKeyBuff.toString());
323 | }
324 |
325 | /**
326 | * Internal class to retain the generate SSH keys.
327 | */
328 | public static class SshPublicPrivateKey {
329 | private String sshPublicKey;
330 | private String sshPrivateKey;
331 |
332 | /**
333 | * Constructor.
334 | * @param sshPrivateKey SSH private key
335 | * @param sshPublicKey SSH public key
336 | */
337 | public SshPublicPrivateKey(String sshPrivateKey, String sshPublicKey) {
338 | this.sshPrivateKey = sshPrivateKey;
339 | this.sshPublicKey = sshPublicKey;
340 | }
341 |
342 | /**
343 | * Get SSH public key.
344 | * @return public key
345 | */
346 | public String getSshPublicKey() {
347 | return sshPublicKey;
348 | }
349 |
350 | /**
351 | * Get SSH private key.
352 | * @return private key
353 | */
354 | public String getSshPrivateKey() {
355 | return sshPrivateKey;
356 | }
357 |
358 | /**
359 | * Set SSH public key.
360 | * @param sshPublicKey public key
361 | */
362 | public void setSshPublicKey(String sshPublicKey) {
363 | this.sshPublicKey = sshPublicKey;
364 | }
365 |
366 | /**
367 | * Set SSH private key.
368 | * @param sshPrivateKey private key
369 | */
370 | public void setSshPrivateKey(String sshPrivateKey) {
371 | this.sshPrivateKey = sshPrivateKey;
372 | }
373 | }
374 | }
375 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/azure/management/containerservice/samples/DeployImageFromContainerRegistryToContainerServiceOrchestrator.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for
4 | * license information.
5 | */
6 |
7 | package com.microsoft.azure.management.containerservice.samples;
8 |
9 | import com.github.dockerjava.api.DockerClient;
10 | import com.github.dockerjava.api.command.CreateContainerResponse;
11 | import com.github.dockerjava.api.exception.NotFoundException;
12 | import com.github.dockerjava.api.model.Container;
13 | import com.github.dockerjava.api.model.Image;
14 | import com.github.dockerjava.core.command.PullImageResultCallback;
15 | import com.github.dockerjava.core.command.PushImageResultCallback;
16 | import com.microsoft.azure.management.Azure;
17 | import com.microsoft.azure.management.containerregistry.AccessKeyType;
18 | import com.microsoft.azure.management.containerregistry.Registry;
19 | import com.microsoft.azure.management.containerregistry.RegistryCredentials;
20 | import com.microsoft.azure.management.containerservice.ContainerService;
21 | import com.microsoft.azure.management.containerservice.ContainerServiceMasterProfileCount;
22 | import com.microsoft.azure.management.containerservice.ContainerServiceVMSizeTypes;
23 | import com.microsoft.azure.management.resources.fluentcore.arm.Region;
24 | import com.microsoft.azure.management.resources.fluentcore.utils.SdkContext;
25 | import com.microsoft.azure.management.samples.DockerUtils;
26 | import com.microsoft.azure.management.samples.SSHShell;
27 | import com.microsoft.azure.management.samples.Utils;
28 | import com.microsoft.rest.LogLevel;
29 |
30 | import io.fabric8.kubernetes.api.model.LoadBalancerIngress;
31 | import io.fabric8.kubernetes.api.model.Namespace;
32 | import io.fabric8.kubernetes.api.model.NamespaceBuilder;
33 | import io.fabric8.kubernetes.api.model.Pod;
34 | import io.fabric8.kubernetes.api.model.ReplicationController;
35 | import io.fabric8.kubernetes.api.model.ReplicationControllerBuilder;
36 | import io.fabric8.kubernetes.api.model.Secret;
37 | import io.fabric8.kubernetes.api.model.SecretBuilder;
38 | import io.fabric8.kubernetes.api.model.Service;
39 | import io.fabric8.kubernetes.api.model.ServiceBuilder;
40 | import io.fabric8.kubernetes.client.Config;
41 | import io.fabric8.kubernetes.client.DefaultKubernetesClient;
42 | import io.fabric8.kubernetes.client.KubernetesClient;
43 |
44 | import java.io.BufferedWriter;
45 | import java.io.File;
46 | import java.io.FileWriter;
47 | import java.nio.file.Files;
48 | import java.nio.file.Paths;
49 | import java.util.HashMap;
50 | import java.util.List;
51 | import java.util.Date;
52 |
53 | import org.apache.commons.codec.binary.Base64;
54 |
55 | /**
56 | * Azure Container Registry sample for deploying a container image to Azure Container Service with Kubernetes orchestration.
57 | * - Create an Azure Container Registry to be used for holding the Docker images
58 | * - If a local Docker engine cannot be found, create a Linux virtual machine that will host a Docker engine to be used for this sample
59 | * - Use Docker Java to create a Docker client that will push/pull an image to/from Azure Container Registry
60 | * - Pull a test image from the public Docker repo (tomcat:8) to be used as a sample for pushing/pulling to/from an Azure Container Registry
61 | * - Create a new Docker container from an image that was pulled from Azure Container Registry
62 | * - Create a SSH private/public key to be used when creating a container service
63 | * - Create an Azure Container Service with Kubernetes orchestration
64 | * - Log in via the SSH client and download the Kubernetes config
65 | * - Create a Kubernetes client using the Kubernetes config file downloaded from one of the virtual machine managers
66 | * - Create a Kubernetes namespace
67 | * - Create a Kubernetes secret of type "docker-registry" using the Azure Container Registry credentials from above
68 | * - Create a Kubernetes replication controller using a container image from the Azure private registry from above and a load balancer service that will expose the app to the world
69 | */
70 | public class DeployImageFromContainerRegistryToContainerServiceOrchestrator {
71 |
72 | /**
73 | * Main function which runs the actual sample.
74 | *
75 | * @param azure instance of the azure client
76 | * @param clientId secondary service principal client ID
77 | * @param secret secondary service principal secret
78 | * @return true if sample runs successfully
79 | */
80 | public static boolean runSample(Azure azure, String clientId, String secret) {
81 | final String rgName = SdkContext.randomResourceName("rgACR", 15);
82 | final String acrName = SdkContext.randomResourceName("acrsample", 20);
83 | final String acsName = SdkContext.randomResourceName("acssample", 30);
84 | final String rootUserName = "acsuser";
85 | final Region region = Region.US_EAST;
86 | final String dockerImageName = "nginx";
87 | final String dockerImageTag = "latest";
88 | final String dockerContainerName = "acrsample-nginx";
89 | String acsSecretName = "mysecret112233";
90 | String acsNamespace = "acrsample";
91 | String acsLbIngressName = "lb-acrsample";
92 | String servicePrincipalClientId = clientId; // replace with a real service principal client id
93 | String servicePrincipalSecret = secret; // and corresponding secret
94 | SSHShell shell = null;
95 |
96 | try {
97 |
98 | //=============================================================
99 | // If service principal client id and secret are not set via the local variables, attempt to read the service
100 | // principal client id and secret from a secondary ".azureauth" file set through an environment variable.
101 | //
102 | // If the environment variable was not set then reuse the main service principal set for running this sample.
103 |
104 | if (servicePrincipalClientId.isEmpty() || servicePrincipalSecret.isEmpty()) {
105 | servicePrincipalClientId = System.getenv("AZURE_CLIENT_ID");
106 | servicePrincipalSecret = System.getenv("AZURE_CLIENT_SECRET");
107 | if (servicePrincipalClientId.isEmpty() || servicePrincipalSecret.isEmpty()) {
108 | String envSecondaryServicePrincipal = System.getenv("AZURE_AUTH_LOCATION_2");
109 |
110 | if (envSecondaryServicePrincipal == null || !envSecondaryServicePrincipal.isEmpty() || !Files.exists(Paths.get(envSecondaryServicePrincipal))) {
111 | envSecondaryServicePrincipal = System.getenv("AZURE_AUTH_LOCATION");
112 | }
113 |
114 | servicePrincipalClientId = Utils.getSecondaryServicePrincipalClientID(envSecondaryServicePrincipal);
115 | servicePrincipalSecret = Utils.getSecondaryServicePrincipalSecret(envSecondaryServicePrincipal);
116 | }
117 | }
118 |
119 |
120 | //=============================================================
121 | // Create an SSH private/public key pair to be used when creating the container service
122 |
123 | System.out.println("Creating an SSH private and public key pair");
124 |
125 | SSHShell.SshPublicPrivateKey sshKeys = SSHShell.generateSSHKeys("", "ACS");
126 | System.out.println("SSH private key value: \n" + sshKeys.getSshPrivateKey());
127 | System.out.println("SSH public key value: \n" + sshKeys.getSshPublicKey());
128 |
129 |
130 | //=============================================================
131 | // Create an Azure Container Service with Kubernetes orchestration
132 |
133 | System.out.println("Creating an Azure Container Service with Kubernetes ochestration and one agent (virtual machine)");
134 |
135 | Date t1 = new Date();
136 |
137 | ContainerService azureContainerService = azure.containerServices().define(acsName)
138 | .withRegion(region)
139 | .withNewResourceGroup(rgName)
140 | .withKubernetesOrchestration()
141 | .withServicePrincipal(servicePrincipalClientId, servicePrincipalSecret)
142 | .withLinux()
143 | .withRootUsername(rootUserName)
144 | .withSshKey(sshKeys.getSshPublicKey())
145 | .withMasterNodeCount(ContainerServiceMasterProfileCount.MIN)
146 | .defineAgentPool("agentpool")
147 | .withVirtualMachineCount(1)
148 | .withVirtualMachineSize(ContainerServiceVMSizeTypes.STANDARD_D1_V2)
149 | .withDnsPrefix("dns-ap-" + acsName)
150 | .attach()
151 | .withMasterDnsPrefix("dns-" + acsName)
152 | .create();
153 |
154 | Date t2 = new Date();
155 | System.out.println("Created Azure Container Service: (took " + ((t2.getTime() - t1.getTime()) / 1000) + " seconds) " + azureContainerService.id());
156 | Utils.print(azureContainerService);
157 |
158 |
159 | //=============================================================
160 | // Create an Azure Container Registry to store and manage private Docker container images
161 |
162 | System.out.println("Creating an Azure Container Registry");
163 |
164 | t1 = new Date();
165 |
166 | Registry azureRegistry = azure.containerRegistries().define(acrName)
167 | .withRegion(region)
168 | .withNewResourceGroup(rgName)
169 | .withBasicSku()
170 | .withRegistryNameAsAdminUser()
171 | .create();
172 |
173 | t2 = new Date();
174 | System.out.println("Created Azure Container Registry: (took " + ((t2.getTime() - t1.getTime()) / 1000) + " seconds) " + azureRegistry.id());
175 | Utils.print(azureRegistry);
176 |
177 |
178 | //=============================================================
179 | // Create a Docker client that will be used to push/pull images to/from the Azure Container Registry
180 |
181 | RegistryCredentials acrCredentials = azureRegistry.getCredentials();
182 | DockerClient dockerClient = DockerUtils.createDockerClient(azure, rgName, region,
183 | azureRegistry.loginServerUrl(), acrCredentials.username(), acrCredentials.accessKeys().get(AccessKeyType.PRIMARY));
184 |
185 | //=============================================================
186 | // Pull a temp image from public Docker repo and create a temporary container from that image
187 | // These steps can be replaced and instead build a custom image using a Dockerfile and the app's JAR
188 |
189 | dockerClient.pullImageCmd(dockerImageName)
190 | .withTag(dockerImageTag)
191 | .exec(new PullImageResultCallback())
192 | .awaitSuccess();
193 | System.out.println("List local Docker images:");
194 | List images = dockerClient.listImagesCmd().withShowAll(true).exec();
195 | for (Image image : images) {
196 | System.out.format("\tFound Docker image %s (%s)\n", image.getRepoTags()[0], image.getId());
197 | }
198 |
199 | CreateContainerResponse dockerContainerInstance = dockerClient.createContainerCmd(dockerImageName + ":" + dockerImageTag)
200 | .withName(dockerContainerName)
201 | .withCmd("/hello")
202 | .exec();
203 | System.out.println("List Docker containers:");
204 | List dockerContainers = dockerClient.listContainersCmd()
205 | .withShowAll(true)
206 | .exec();
207 | for (Container container : dockerContainers) {
208 | System.out.format("\tFound Docker container %s (%s)\n", container.getImage(), container.getId());
209 | }
210 |
211 | //=============================================================
212 | // Commit the new container
213 |
214 | String privateRepoUrl = azureRegistry.loginServerUrl() + "/samples/" + dockerContainerName;
215 | String dockerImageId = dockerClient.commitCmd(dockerContainerInstance.getId())
216 | .withRepository(privateRepoUrl)
217 | .withTag("latest").exec();
218 |
219 | // We can now remove the temporary container instance
220 | dockerClient.removeContainerCmd(dockerContainerInstance.getId())
221 | .withForce(true)
222 | .exec();
223 |
224 | //=============================================================
225 | // Push the new Docker image to the Azure Container Registry
226 |
227 | dockerClient.pushImageCmd(privateRepoUrl)
228 | .withAuthConfig(dockerClient.authConfig())
229 | .exec(new PushImageResultCallback()).awaitSuccess();
230 |
231 | // Remove the temp image from the local Docker host
232 | try {
233 | dockerClient.removeImageCmd(dockerImageName + ":" + dockerImageTag).withForce(true).exec();
234 | } catch (NotFoundException e) {
235 | // just ignore if not exist
236 | }
237 |
238 | //=============================================================
239 | // Verify that the image we saved in the Azure Container registry can be pulled and instantiated locally
240 |
241 | dockerClient.pullImageCmd(privateRepoUrl)
242 | .withAuthConfig(dockerClient.authConfig())
243 | .exec(new PullImageResultCallback()).awaitSuccess();
244 | System.out.println("List local Docker images after pulling sample image from the Azure Container Registry:");
245 | images = dockerClient.listImagesCmd()
246 | .withShowAll(true)
247 | .exec();
248 | for (Image image : images) {
249 | System.out.format("\tFound Docker image %s (%s)\n", image.getRepoTags()[0], image.getId());
250 | }
251 | dockerContainerInstance = dockerClient.createContainerCmd(privateRepoUrl)
252 | .withName(dockerContainerName + "-private")
253 | .withCmd("/hello").exec();
254 | System.out.println("List Docker containers after instantiating container from the Azure Container Registry sample image:");
255 | dockerContainers = dockerClient.listContainersCmd()
256 | .withShowAll(true)
257 | .exec();
258 | for (Container container : dockerContainers) {
259 | System.out.format("\tFound Docker container %s (%s)\n", container.getImage(), container.getId());
260 | }
261 |
262 |
263 | //=============================================================
264 | // Download the Kubernetes config file from one of the master virtual machines
265 |
266 | azureContainerService = azure.containerServices().getByResourceGroup(rgName, acsName);
267 | System.out.println("Found Kubernetes master at: " + azureContainerService.masterFqdn());
268 |
269 | shell = SSHShell.open(azureContainerService.masterFqdn(), 22, rootUserName, sshKeys.getSshPrivateKey().getBytes());
270 |
271 | String kubeConfigContent = shell.download("config", ".kube", true);
272 | System.out.println("Found Kubernetes config:\n" + kubeConfigContent);
273 |
274 |
275 | //=============================================================
276 | // Instantiate the Kubernetes client using the downloaded ".kube/config" file content
277 | // The Kubernetes client API requires setting an environment variable pointing at a real file;
278 | // we will create a temporary file that will be deleted automatically when the sample exits
279 |
280 | File tempKubeConfigFile = File.createTempFile("kube", ".config", new File(System.getProperty("java.io.tmpdir")));
281 | tempKubeConfigFile.deleteOnExit();
282 | BufferedWriter buffOut = new BufferedWriter(new FileWriter(tempKubeConfigFile));
283 | buffOut.write(kubeConfigContent);
284 | buffOut.close();
285 |
286 | System.setProperty(Config.KUBERNETES_KUBECONFIG_FILE, tempKubeConfigFile.getPath());
287 | Config config = new Config();
288 | KubernetesClient kubernetesClient = new DefaultKubernetesClient(config);
289 |
290 |
291 | //=============================================================
292 | // List all the nodes available in the Kubernetes cluster
293 |
294 | System.out.println(kubernetesClient.nodes().list());
295 |
296 |
297 | //=============================================================
298 | // Create a namespace where all the sample Kubernetes resources will be created
299 |
300 | Namespace ns = new NamespaceBuilder()
301 | .withNewMetadata()
302 | .withName(acsNamespace)
303 | .addToLabels("acr", "sample")
304 | .endMetadata()
305 | .build();
306 | try {
307 | System.out.println("Created namespace" + kubernetesClient.namespaces().create(ns));
308 | } catch (Exception ignored) {
309 | }
310 |
311 | SdkContext.sleep(5000);
312 | for (Namespace namespace : kubernetesClient.namespaces().list().getItems()) {
313 | System.out.println("\tFound Kubernetes namespace: " + namespace.toString());
314 | }
315 |
316 |
317 | //=============================================================
318 | // Create a secret of type "docker-repository" that will be used for downloading the container image from
319 | // our Azure private container repo
320 |
321 | String basicAuth = new String(Base64.encodeBase64((acrCredentials.username() + ":" + acrCredentials.accessKeys().get(AccessKeyType.PRIMARY)).getBytes()));
322 | HashMap secretData = new HashMap<>(1);
323 | String dockerCfg = String.format("{ \"%s\": { \"auth\": \"%s\", \"email\": \"%s\" } }",
324 | azureRegistry.loginServerUrl(),
325 | basicAuth,
326 | "acrsample@azure.com");
327 |
328 | dockerCfg = new String(Base64.encodeBase64(dockerCfg.getBytes("UTF-8")), "UTF-8");
329 | secretData.put(".dockercfg", dockerCfg);
330 | SecretBuilder secretBuilder = new SecretBuilder()
331 | .withNewMetadata()
332 | .withName(acsSecretName)
333 | .withNamespace(acsNamespace)
334 | .endMetadata()
335 | .withData(secretData)
336 | .withType("kubernetes.io/dockercfg");
337 |
338 | System.out.println("Creating new secret: " + kubernetesClient.secrets().inNamespace(acsNamespace).create(secretBuilder.build()));
339 |
340 | SdkContext.sleep(5000);
341 |
342 | for (Secret kubeS : kubernetesClient.secrets().inNamespace(acsNamespace).list().getItems()) {
343 | System.out.println("\tFound secret: " + kubeS);
344 | }
345 |
346 |
347 | //=============================================================
348 | // Create a replication controller for our image stored in the Azure Container Registry
349 |
350 | ReplicationController rc = new ReplicationControllerBuilder()
351 | .withNewMetadata()
352 | .withName("acrsample-rc")
353 | .withNamespace(acsNamespace)
354 | .addToLabels("acrsample-nginx", "nginx")
355 | .endMetadata()
356 | .withNewSpec()
357 | .withReplicas(2)
358 | .withNewTemplate()
359 | .withNewMetadata()
360 | .addToLabels("acrsample-nginx", "nginx")
361 | .endMetadata()
362 | .withNewSpec()
363 | .addNewImagePullSecret(acsSecretName)
364 | .addNewContainer()
365 | .withName("acrsample-pod-nginx")
366 | .withImage(privateRepoUrl)
367 | .addNewPort()
368 | .withContainerPort(80)
369 | .endPort()
370 | .endContainer()
371 | .endSpec()
372 | .endTemplate()
373 | .endSpec()
374 | .build();
375 |
376 | System.out.println("Creating a replication controller: " + kubernetesClient.replicationControllers().inNamespace(acsNamespace).create(rc));
377 | SdkContext.sleep(5000);
378 |
379 | rc = kubernetesClient.replicationControllers().inNamespace(acsNamespace).withName("acrsample-rc").get();
380 | System.out.println("Found replication controller: " + rc.toString());
381 |
382 | for (Pod pod : kubernetesClient.pods().inNamespace(acsNamespace).list().getItems()) {
383 | System.out.println("\tFound Kubernetes pods: " + pod.toString());
384 | }
385 |
386 |
387 | //=============================================================
388 | // Create a Load Balancer service that will expose the service to the world
389 |
390 | Service lbService = new ServiceBuilder()
391 | .withNewMetadata()
392 | .withName(acsLbIngressName)
393 | .withNamespace(acsNamespace)
394 | .endMetadata()
395 | .withNewSpec()
396 | .withType("LoadBalancer")
397 | .addNewPort()
398 | .withPort(80)
399 | .withProtocol("TCP")
400 | .endPort()
401 | .addToSelector("acrsample-nginx", "nginx")
402 | .endSpec()
403 | .build();
404 |
405 | System.out.println("Creating a service: " + kubernetesClient.services().inNamespace(acsNamespace).create(lbService));
406 |
407 | SdkContext.sleep(5000);
408 |
409 | System.out.println("\tFound service: " + kubernetesClient.services().inNamespace(acsNamespace).withName(acsLbIngressName).get());
410 |
411 |
412 | //=============================================================
413 | // Wait until the external IP becomes available
414 |
415 | int timeout = 30 * 60 * 1000; // 30 minutes
416 | String matchIPV4 = "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$";
417 |
418 | while (timeout > 0) {
419 | try {
420 | List lbIngressList = kubernetesClient.services().inNamespace(acsNamespace).withName(acsLbIngressName).get().getStatus().getLoadBalancer().getIngress();
421 | if (lbIngressList != null && !lbIngressList.isEmpty() && lbIngressList.get(0) != null && lbIngressList.get(0).getIp().matches(matchIPV4)) {
422 | System.out.println("\tFound ingress IP: " + lbIngressList.get(0).getIp());
423 | timeout = 0;
424 | }
425 | } catch (Exception ignored) {
426 | }
427 |
428 | if (timeout > 0) {
429 | timeout -= 30000; // 30 seconds
430 | SdkContext.sleep(30000);
431 | }
432 | }
433 |
434 | // Clean-up
435 | kubernetesClient.namespaces().delete(ns);
436 |
437 | shell.close();
438 | shell = null;
439 |
440 | return true;
441 | } catch (Exception f) {
442 | System.out.println(f.getMessage());
443 | f.printStackTrace();
444 | } finally {
445 | try {
446 | System.out.println("Deleting Resource Group: " + rgName);
447 | azure.resourceGroups().beginDeleteByName(rgName);
448 | System.out.println("Deleted Resource Group: " + rgName);
449 | if (shell != null) {
450 | shell.close();
451 | }
452 | } catch (NullPointerException npe) {
453 | System.out.println("Did not create any resources in Azure. No clean up is necessary");
454 | } catch (Exception g) {
455 | g.printStackTrace();
456 | }
457 | }
458 | return false;
459 | }
460 |
461 | /**
462 | * Main entry point.
463 | *
464 | * @param args the parameters
465 | */
466 | public static void main(String[] args) {
467 | try {
468 | //=============================================================
469 | // Authenticate
470 |
471 | final File credFile = new File(System.getenv("AZURE_AUTH_LOCATION"));
472 |
473 | Azure azure = Azure.configure()
474 | .withLogLevel(LogLevel.BODY)
475 | .authenticate(credFile)
476 | .withDefaultSubscription();
477 |
478 | // Print selected subscription
479 | System.out.println("Selected subscription: " + azure.subscriptionId());
480 |
481 | runSample(azure, "", "");
482 | } catch (Exception e) {
483 | System.out.println(e.getMessage());
484 | e.printStackTrace();
485 | }
486 | }
487 | }
488 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/azure/management/samples/DockerUtils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for
4 | * license information.
5 | */
6 |
7 | package com.microsoft.azure.management.samples;
8 |
9 | import com.github.dockerjava.api.DockerClient;
10 | import com.github.dockerjava.api.exception.DockerClientException;
11 | import com.github.dockerjava.core.DefaultDockerClientConfig;
12 | import com.github.dockerjava.core.DockerClientBuilder;
13 | import com.github.dockerjava.core.DockerClientConfig;
14 | import com.github.dockerjava.core.SSLConfig;
15 | import com.github.dockerjava.core.util.CertificateUtils;
16 | import com.jcraft.jsch.JSchException;
17 | import com.microsoft.azure.management.Azure;
18 | import com.microsoft.azure.management.compute.KnownLinuxVirtualMachineImage;
19 | import com.microsoft.azure.management.compute.VirtualMachine;
20 | import com.microsoft.azure.management.compute.VirtualMachineSizeTypes;
21 | import com.microsoft.azure.management.network.NicIPConfiguration;
22 | import com.microsoft.azure.management.network.PublicIPAddress;
23 | import com.microsoft.azure.management.resources.fluentcore.arm.Region;
24 | import com.microsoft.azure.management.resources.fluentcore.utils.SdkContext;
25 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
26 | import org.glassfish.jersey.SslConfigurator;
27 |
28 | import javax.net.ssl.SSLContext;
29 | import java.io.ByteArrayInputStream;
30 | import java.io.File;
31 | import java.io.IOException;
32 | import java.io.Serializable;
33 | import java.nio.file.Files;
34 | import java.nio.file.Paths;
35 | import java.security.Security;
36 | import java.util.Date;
37 |
38 | /**
39 | * Utility class to be used by Azure Container Registry sample.
40 | * - Creates "in memory" SSL configuration to be used by the Java Docker client
41 | * - Builds a Docker client config object
42 | * - Creates a new Azure virtual machine and installs Docker
43 | * - Creates a Java DockerClient to be used for communicating with a Docker host/engine
44 | */
45 | public class DockerUtils {
46 |
47 | /**
48 | * Creates "in memory" SSL configuration to be used by the Java Docker Client.
49 | */
50 | public static class DockerSSLConfig implements SSLConfig, Serializable {
51 | private SslConfigurator sslConfig;
52 |
53 | /**
54 | * Constructor for the class.
55 | * @param caPem - content of the ca.pem certificate file
56 | * @param keyPem - content of the key.pem certificate file
57 | * @param certPem - content of the cert.pem certificate file
58 | */
59 | public DockerSSLConfig(String caPem, String keyPem, String certPem) {
60 | try {
61 | Security.addProvider(new BouncyCastleProvider());
62 | String e = System.getProperty("https.protocols");
63 | System.setProperty("https.protocols", "TLSv1");
64 | sslConfig = SslConfigurator.newInstance(true);
65 | if (e != null) {
66 | System.setProperty("https.protocols", e);
67 | }
68 |
69 | sslConfig.keyStore(CertificateUtils.createKeyStore(keyPem, certPem));
70 | sslConfig.keyStorePassword("docker");
71 | sslConfig.trustStore(CertificateUtils.createTrustStore(caPem));
72 | } catch (Exception e) {
73 | throw new DockerClientException(e.getMessage(), e);
74 | }
75 | }
76 |
77 | @Override
78 | public SSLContext getSSLContext() {
79 | return sslConfig.createSSLContext();
80 | }
81 | }
82 |
83 | /**
84 | * Instantiate a Docker client that will be used for Docker client related operations.
85 | * @param azure - instance of Azure
86 | * @param rgName - name of the Azure resource group to be used when creating a virtual machine
87 | * @param region - region to be used when creating a virtual machine
88 | * @param registryServerUrl - address of the private container registry
89 | * @param username - user name to connect with to the private container registry
90 | * @param password - password to connect with to the private container registry
91 | * @return an instance of DockerClient
92 | * @throws Exception exception thrown
93 | */
94 | public static DockerClient createDockerClient(Azure azure, String rgName, Region region,
95 | String registryServerUrl, String username, String password) throws Exception {
96 | final String envDockerHost = System.getenv("DOCKER_HOST");
97 | final String envDockerCertPath = System.getenv("DOCKER_CERT_PATH");
98 | String dockerHostUrl;
99 | DockerClient dockerClient;
100 |
101 | if (envDockerHost == null || envDockerHost.isEmpty()) {
102 | // Could not find a Docker environment; presume that there is no local Docker engine running and
103 | // attempt to configure a Docker engine running inside a new Azure virtual machine
104 | dockerClient = fromNewDockerVM(azure, rgName, region, registryServerUrl, username, password);
105 | } else {
106 | dockerHostUrl = envDockerHost;
107 | System.out.println("Using local settings to connect to a Docker service: " + dockerHostUrl);
108 |
109 | DockerClientConfig dockerClientConfig;
110 | if (envDockerCertPath == null || envDockerCertPath.isEmpty()) {
111 | dockerClientConfig = createDockerClientConfig(dockerHostUrl, registryServerUrl, username, password);
112 | } else {
113 | String caPemPath = envDockerCertPath + File.separator + "ca.pem";
114 | String keyPemPath = envDockerCertPath + File.separator + "key.pem";
115 | String certPemPath = envDockerCertPath + File.separator + "cert.pem";
116 |
117 | String keyPemContent = new String(Files.readAllBytes(Paths.get(keyPemPath)));
118 | String certPemContent = new String(Files.readAllBytes(Paths.get(certPemPath)));
119 | String caPemContent = new String(Files.readAllBytes(Paths.get(caPemPath)));
120 |
121 | dockerClientConfig = createDockerClientConfig(dockerHostUrl, registryServerUrl, username, password,
122 | caPemContent, keyPemContent, certPemContent);
123 | }
124 |
125 | dockerClient = DockerClientBuilder.getInstance(dockerClientConfig)
126 | .build();
127 | System.out.println("List Docker host info");
128 | System.out.println("\tFound Docker version: " + dockerClient.versionCmd().exec().toString());
129 | System.out.println("\tFound Docker info: " + dockerClient.infoCmd().exec().toString());
130 | }
131 |
132 | return dockerClient;
133 | }
134 |
135 | /**
136 | * Creates a DockerClientConfig object to be used when creating the Java Docker client using a secured connection.
137 | * @param host - Docker host address (IP) to connect to
138 | * @param registryServerUrl - address of the private container registry
139 | * @param username - user name to connect with to the private container registry
140 | * @param password - password to connect with to the private container registry
141 | * @param caPemContent - content of the ca.pem certificate file
142 | * @param keyPemContent - content of the key.pem certificate file
143 | * @param certPemContent - content of the cert.pem certificate file
144 | * @return an instance of DockerClient configuration
145 | */
146 | public static DockerClientConfig createDockerClientConfig(String host, String registryServerUrl, String username, String password,
147 | String caPemContent, String keyPemContent, String certPemContent) {
148 | return DefaultDockerClientConfig.createDefaultConfigBuilder()
149 | .withDockerHost(host)
150 | .withDockerTlsVerify(true)
151 | .withCustomSslConfig(new DockerSSLConfig(caPemContent, keyPemContent, certPemContent))
152 | .withRegistryUrl(registryServerUrl)
153 | .withRegistryUsername(username)
154 | .withRegistryPassword(password)
155 | .build();
156 | }
157 |
158 | /**
159 | * Creates a DockerClientConfig object to be used when creating the Java Docker client using an unsecured connection.
160 | * @param host - Docker host address (IP) to connect to
161 | * @param registryServerUrl - address of the private container registry
162 | * @param username - user name to connect with to the private container registry
163 | * @param password - password to connect with to the private container registry
164 | * @return an instance of DockerClient configuration
165 | */
166 | public static DockerClientConfig createDockerClientConfig(String host, String registryServerUrl, String username, String password) {
167 | return DefaultDockerClientConfig.createDefaultConfigBuilder()
168 | .withDockerHost(host)
169 | .withDockerTlsVerify(false)
170 | .withRegistryUrl(registryServerUrl)
171 | .withRegistryUsername(username)
172 | .withRegistryPassword(password)
173 | .build();
174 | }
175 |
176 | /**
177 | * It creates a new Azure virtual machine and it instantiate a Java Docker client.
178 | * @param azure - instance of Azure
179 | * @param rgName - name of the Azure resource group to be used when creating a virtual machine
180 | * @param region - region to be used when creating a virtual machine
181 | * @param registryServerUrl - address of the private container registry
182 | * @param username - user name to connect with to the private container registry
183 | * @param password - password to connect with to the private container registry
184 | * @return an instance of DockerClient
185 | * @throws Exception exception thrown
186 | */
187 | public static DockerClient fromNewDockerVM(Azure azure, String rgName, Region region,
188 | String registryServerUrl, String username, String password) throws Exception {
189 | final String dockerVMName = SdkContext.randomResourceName("dockervm", 15);
190 | final String publicIPDnsLabel = SdkContext.randomResourceName("pip", 10);
191 | final String vmUserName = "dockerUser";
192 | // [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Serves as an example, not for deployment. Please change when using this in your code.")]
193 | final String vmPassword = "12NewPA!!w0rd!";
194 |
195 | // Could not find a Docker environment; presume that there is no local Docker engine running and
196 | // attempt to configure a Docker engine running inside a new Azure virtual machine
197 | System.out.println("Creating an Azure virtual machine running Docker");
198 |
199 | Date t1 = new Date();
200 |
201 | VirtualMachine dockerVM = azure.virtualMachines().define(dockerVMName)
202 | .withRegion(region)
203 | .withExistingResourceGroup(rgName)
204 | .withNewPrimaryNetwork("10.0.0.0/28")
205 | .withPrimaryPrivateIPAddressDynamic()
206 | .withNewPrimaryPublicIPAddress(publicIPDnsLabel)
207 | .withPopularLinuxImage(KnownLinuxVirtualMachineImage.UBUNTU_SERVER_16_04_LTS)
208 | .withRootUsername(vmUserName)
209 | .withRootPassword(vmPassword)
210 | .withSize(VirtualMachineSizeTypes.STANDARD_D2_V2)
211 | .create();
212 |
213 | Date t2 = new Date();
214 | System.out.println("Created Azure Virtual Machine: (took " + ((t2.getTime() - t1.getTime()) / 1000) + " seconds) " + dockerVM.id());
215 |
216 | // Wait for a minute for PIP to be available
217 | SdkContext.sleep(60 * 1000);
218 | // Get the IP of the Docker host
219 | NicIPConfiguration nicIPConfiguration = dockerVM.getPrimaryNetworkInterface().primaryIPConfiguration();
220 | PublicIPAddress publicIp = nicIPConfiguration.getPublicIPAddress();
221 | String dockerHostIP = publicIp.ipAddress();
222 |
223 | DockerClient dockerClient = installDocker(dockerHostIP, vmUserName, vmPassword, registryServerUrl, username, password);
224 | System.out.println("List Docker host info");
225 | System.out.println("\tFound Docker version: " + dockerClient.versionCmd().exec().toString());
226 | System.out.println("\tFound Docker info: " + dockerClient.infoCmd().exec().toString());
227 |
228 | return dockerClient;
229 | }
230 |
231 | /**
232 | * Install Docker on a given virtual machine and return a DockerClient.
233 | * @param dockerHostIP - address (IP) of the Docker host machine
234 | * @param vmUserName - user name to connect with to the Docker host machine
235 | * @param vmPassword - password to connect with to the Docker host machine
236 | * @param registryServerUrl - address of the private container registry
237 | * @param username - user name to connect with to the private container registry
238 | * @param password - password to connect with to the private container registry
239 | * @return an instance of DockerClient
240 | */
241 | public static DockerClient installDocker(String dockerHostIP, String vmUserName, String vmPassword,
242 | String registryServerUrl, String username, String password) {
243 | String keyPemContent = ""; // it stores the content of the key.pem certificate file
244 | String certPemContent = ""; // it stores the content of the cert.pem certificate file
245 | String caPemContent = ""; // it stores the content of the ca.pem certificate file
246 | boolean dockerHostTlsEnabled = false;
247 | String dockerHostUrl = "tcp://" + dockerHostIP + ":2375";
248 | SSHShell sshShell = null;
249 |
250 | try {
251 | System.out.println("Copy Docker setup scripts to remote host: " + dockerHostIP);
252 | sshShell = SSHShell.open(dockerHostIP, 22, vmUserName, vmPassword);
253 |
254 | sshShell.upload(new ByteArrayInputStream(INSTALL_DOCKER_FOR_UBUNTU_SERVER_16_04_LTS.getBytes()),
255 | "INSTALL_DOCKER_FOR_UBUNTU_SERVER_16_04_LTS.sh",
256 | ".azuredocker",
257 | true,
258 | "4095");
259 |
260 | sshShell.upload(new ByteArrayInputStream(CREATE_OPENSSL_TLS_CERTS_FOR_UBUNTU.replaceAll("HOST_IP", dockerHostIP).getBytes()),
261 | "CREATE_OPENSSL_TLS_CERTS_FOR_UBUNTU.sh",
262 | ".azuredocker",
263 | true,
264 | "4095");
265 | sshShell.upload(new ByteArrayInputStream(INSTALL_DOCKER_TLS_CERTS_FOR_UBUNTU.getBytes()),
266 | "INSTALL_DOCKER_TLS_CERTS_FOR_UBUNTU.sh",
267 | ".azuredocker",
268 | true,
269 | "4095");
270 | sshShell.upload(new ByteArrayInputStream(DEFAULT_DOCKERD_CONFIG_TLS_ENABLED.getBytes()),
271 | "dockerd_tls.config",
272 | ".azuredocker",
273 | true,
274 | "4095");
275 | sshShell.upload(new ByteArrayInputStream(CREATE_DEFAULT_DOCKERD_OPTS_TLS_ENABLED.getBytes()),
276 | "CREATE_DEFAULT_DOCKERD_OPTS_TLS_ENABLED.sh",
277 | ".azuredocker",
278 | true,
279 | "4095");
280 | sshShell.upload(new ByteArrayInputStream(DEFAULT_DOCKERD_CONFIG_TLS_DISABLED.getBytes()),
281 | "dockerd_notls.config",
282 | ".azuredocker",
283 | true,
284 | "4095");
285 | sshShell.upload(new ByteArrayInputStream(CREATE_DEFAULT_DOCKERD_OPTS_TLS_DISABLED.getBytes()),
286 | "CREATE_DEFAULT_DOCKERD_OPTS_TLS_DISABLED.sh",
287 | ".azuredocker",
288 | true,
289 | "4095");
290 | } catch (JSchException jSchException) {
291 | System.out.println(jSchException.getMessage());
292 | } catch (IOException ioException) {
293 | System.out.println(ioException.getMessage());
294 | } catch (Exception exception) {
295 | System.out.println(exception.getMessage());
296 | } finally {
297 | if (sshShell != null) {
298 | sshShell.close();
299 | sshShell = null;
300 | }
301 | }
302 | try {
303 | System.out.println("Trying to install Docker host at: " + dockerHostIP);
304 | sshShell = SSHShell.open(dockerHostIP, 22, vmUserName, vmPassword);
305 |
306 | String output = sshShell.executeCommand("bash -c ~/.azuredocker/INSTALL_DOCKER_FOR_UBUNTU_SERVER_16_04_LTS.sh", true, true);
307 | System.out.println(output);
308 | } catch (JSchException jSchException) {
309 | System.out.println(jSchException.getMessage());
310 | } catch (IOException ioException) {
311 | System.out.println(ioException.getMessage());
312 | } catch (Exception exception) {
313 | System.out.println(exception.getMessage());
314 | } finally {
315 | if (sshShell != null) {
316 | sshShell.close();
317 | }
318 | }
319 |
320 | try {
321 | System.out.println("Trying to create OPENSSL certificates");
322 | sshShell = SSHShell.open(dockerHostIP, 22, vmUserName, vmPassword);
323 |
324 | String output = sshShell.executeCommand("bash -c ~/.azuredocker/CREATE_OPENSSL_TLS_CERTS_FOR_UBUNTU.sh", true, true);
325 | System.out.println(output);
326 | } catch (JSchException jSchException) {
327 | System.out.println(jSchException.getMessage());
328 | } catch (IOException ioException) {
329 | System.out.println(ioException.getMessage());
330 | } catch (Exception exception) {
331 | System.out.println(exception.getMessage());
332 | } finally {
333 | if (sshShell != null) {
334 | sshShell.close();
335 | }
336 | }
337 |
338 | try {
339 | System.out.println("Trying to install TLS certificates");
340 | sshShell = SSHShell.open(dockerHostIP, 22, vmUserName, vmPassword);
341 |
342 | String output = sshShell.executeCommand("bash -c ~/.azuredocker/INSTALL_DOCKER_TLS_CERTS_FOR_UBUNTU.sh", true, true);
343 | System.out.println(output);
344 | System.out.println("Download Docker client TLS certificates from: " + dockerHostIP);
345 | keyPemContent = sshShell.download("key.pem", ".azuredocker/tls", true);
346 | certPemContent = sshShell.download("cert.pem", ".azuredocker/tls", true);
347 | caPemContent = sshShell.download("ca.pem", ".azuredocker/tls", true);
348 | } catch (JSchException jSchException) {
349 | System.out.println(jSchException.getMessage());
350 | } catch (IOException ioException) {
351 | System.out.println(ioException.getMessage());
352 | } catch (Exception exception) {
353 | System.out.println(exception.getMessage());
354 | } finally {
355 | if (sshShell != null) {
356 | sshShell.close();
357 | }
358 | }
359 |
360 | try {
361 | System.out.println("Trying to setup Docker config: " + dockerHostIP);
362 | sshShell = SSHShell.open(dockerHostIP, 22, vmUserName, vmPassword);
363 |
364 | // // Setup Docker daemon to allow connection from any Docker clients
365 | // String output = sshShell.executeCommand("bash -c ~/.azuredocker/CREATE_DEFAULT_DOCKERD_OPTS_TLS_DISABLED.sh", true, true);
366 | // System.out.println(output);
367 | // dockerHostPort = "2375"; // Default Docker port when secured connection is disabled
368 | // dockerHostTlsEnabled = false;
369 |
370 | // Setup Docker daemon to allow connection from authorized Docker clients only
371 | String output = sshShell.executeCommand("bash -c ~/.azuredocker/CREATE_DEFAULT_DOCKERD_OPTS_TLS_ENABLED.sh", true, true);
372 | System.out.println(output);
373 | String dockerHostPort = "2376"; // Default Docker port when secured connection is enabled
374 | dockerHostTlsEnabled = true;
375 |
376 | dockerHostUrl = "tcp://" + dockerHostIP + ":" + dockerHostPort;
377 | } catch (JSchException jSchException) {
378 | System.out.println(jSchException.getMessage());
379 | } catch (IOException ioException) {
380 | System.out.println(ioException.getMessage());
381 | } catch (Exception exception) {
382 | System.out.println(exception.getMessage());
383 | } finally {
384 | if (sshShell != null) {
385 | sshShell.close();
386 | }
387 | }
388 |
389 | DockerClientConfig dockerClientConfig;
390 | if (dockerHostTlsEnabled) {
391 | dockerClientConfig = createDockerClientConfig(dockerHostUrl, registryServerUrl, username, password,
392 | caPemContent, keyPemContent, certPemContent);
393 | } else {
394 | dockerClientConfig = createDockerClientConfig(dockerHostUrl, registryServerUrl, username, password);
395 | }
396 |
397 | return DockerClientBuilder.getInstance(dockerClientConfig).build();
398 | }
399 |
400 |
401 | /**
402 | * Installs Docker Engine and tools and adds current user to the docker group.
403 | */
404 | public static final String INSTALL_DOCKER_FOR_UBUNTU_SERVER_16_04_LTS = ""
405 | + "echo Running: \"if [ ! -d ~/.azuredocker/tls ]; then mkdir -p ~/.azuredocker/tls ; fi\" \n"
406 | + "if [ ! -d ~/.azuredocker/tls ]; then mkdir -p ~/.azuredocker/tls ; fi \n"
407 | + "echo Running: sudo apt-get update \n"
408 | + "sudo apt-get update \n"
409 | + "echo Running: sudo apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl software-properties-common \n"
410 | + "sudo apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl software-properties-common \n"
411 | + "echo Running: curl -fsSL https://apt.dockerproject.org/gpg | sudo apt-key add - \n"
412 | + "curl -fsSL https://apt.dockerproject.org/gpg | sudo apt-key add - \n"
413 | + "echo Running: sudo add-apt-repository \"deb https://apt.dockerproject.org/repo/ ubuntu-$(lsb_release -cs) main\" \n"
414 | + "sudo add-apt-repository \"deb https://apt.dockerproject.org/repo/ ubuntu-xenial main\" \n"
415 | + "echo Running: sudo apt-get update \n"
416 | + "sudo apt-get update \n"
417 | + "echo Running: sudo apt-get -y install docker-engine \n"
418 | + "sudo apt-get -y install docker-engine \n"
419 | + "echo Running: sudo groupadd docker \n"
420 | + "sudo groupadd docker \n"
421 | + "echo Running: sudo usermod -aG docker $USER \n"
422 | + "sudo usermod -aG docker $USER \n";
423 |
424 | /**
425 | * Linux bash script that creates the TLS certificates for a secured Docker connection.
426 | */
427 | public static final String CREATE_OPENSSL_TLS_CERTS_FOR_UBUNTU = ""
428 | + "echo Running: \"if [ ! -d ~/.azuredocker/tls ]; then rm -f -r ~/.azuredocker/tls ; fi\" \n"
429 | + "if [ ! -d ~/.azuredocker/tls ]; then rm -f -r ~/.azuredocker/tls ; fi \n"
430 | + "echo Running: mkdir -p ~/.azuredocker/tls \n"
431 | + "mkdir -p ~/.azuredocker/tls \n"
432 | + "echo Running: cd ~/.azuredocker/tls \n"
433 | + "cd ~/.azuredocker/tls \n"
434 | // Generate CA certificate
435 | + "echo Running: openssl genrsa -passout pass:$CERT_CA_PWD_PARAM$ -aes256 -out ca-key.pem 2048 \n"
436 | + "openssl genrsa -passout pass:$CERT_CA_PWD_PARAM$ -aes256 -out ca-key.pem 2048 \n"
437 | // Generate Server certificates
438 | + "echo Running: openssl req -passin pass:$CERT_CA_PWD_PARAM$ -subj '/CN=Docker Host CA/C=US' -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem \n"
439 | + "openssl req -passin pass:$CERT_CA_PWD_PARAM$ -subj '/CN=Docker Host CA/C=US' -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem \n"
440 | + "echo Running: openssl genrsa -out server-key.pem 2048 \n"
441 | + "openssl genrsa -out server-key.pem 2048 \n"
442 | + "echo Running: openssl req -subj '/CN=HOST_IP' -sha256 -new -key server-key.pem -out server.csr \n"
443 | + "openssl req -subj '/CN=HOST_IP' -sha256 -new -key server-key.pem -out server.csr \n"
444 | + "echo Running: \"echo subjectAltName = DNS:HOST_IP IP:127.0.0.1 > extfile.cnf \" \n"
445 | + "echo subjectAltName = DNS:HOST_IP IP:127.0.0.1 > extfile.cnf \n"
446 | + "echo Running: openssl x509 -req -passin pass:$CERT_CA_PWD_PARAM$ -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server.pem -extfile extfile.cnf \n"
447 | + "openssl x509 -req -passin pass:$CERT_CA_PWD_PARAM$ -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server.pem -extfile extfile.cnf \n"
448 | // Generate Client certificates
449 | + "echo Running: openssl genrsa -passout pass:$CERT_CA_PWD_PARAM$ -out key.pem \n"
450 | + "openssl genrsa -passout pass:$CERT_CA_PWD_PARAM$ -out key.pem \n"
451 | + "echo Running: openssl req -passin pass:$CERT_CA_PWD_PARAM$ -subj '/CN=client' -new -key key.pem -out client.csr \n"
452 | + "openssl req -passin pass:$CERT_CA_PWD_PARAM$ -subj '/CN=client' -new -key key.pem -out client.csr \n"
453 | + "echo Running: \"echo extendedKeyUsage = clientAuth,serverAuth > extfile.cnf \" \n"
454 | + "echo extendedKeyUsage = clientAuth,serverAuth > extfile.cnf \n"
455 | + "echo Running: openssl x509 -req -passin pass:$CERT_CA_PWD_PARAM$ -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf \n"
456 | + "openssl x509 -req -passin pass:$CERT_CA_PWD_PARAM$ -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf \n"
457 | + "echo Running: cd ~ \n"
458 | + "cd ~ \n";
459 |
460 | /**
461 | * Bash script that sets up the TLS certificates to be used in a secured Docker configuration file; must be run on the Docker dockerHostUrl after the VM is provisioned.
462 | */
463 | public static final String INSTALL_DOCKER_TLS_CERTS_FOR_UBUNTU = ""
464 | + "echo \"if [ ! -d /etc/docker/tls ]; then sudo mkdir -p /etc/docker/tls ; fi\" \n"
465 | + "if [ ! -d /etc/docker/tls ]; then sudo mkdir -p /etc/docker/tls ; fi \n"
466 | + "echo sudo cp -f ~/.azuredocker/tls/ca.pem /etc/docker/tls/ca.pem \n"
467 | + "sudo cp -f ~/.azuredocker/tls/ca.pem /etc/docker/tls/ca.pem \n"
468 | + "echo sudo cp -f ~/.azuredocker/tls/server.pem /etc/docker/tls/server.pem \n"
469 | + "sudo cp -f ~/.azuredocker/tls/server.pem /etc/docker/tls/server.pem \n"
470 | + "echo sudo cp -f ~/.azuredocker/tls/server-key.pem /etc/docker/tls/server-key.pem \n"
471 | + "sudo cp -f ~/.azuredocker/tls/server-key.pem /etc/docker/tls/server-key.pem \n"
472 | + "echo sudo chmod -R 755 /etc/docker \n"
473 | + "sudo chmod -R 755 /etc/docker \n";
474 |
475 | /**
476 | * Docker daemon config file allowing connections from any Docker client.
477 | */
478 | public static final String DEFAULT_DOCKERD_CONFIG_TLS_ENABLED = ""
479 | + "[Service]\n"
480 | + "ExecStart=\n"
481 | + "ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/etc/docker/tls/ca.pem --tlscert=/etc/docker/tls/server.pem --tlskey=/etc/docker/tls/server-key.pem -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock\n";
482 |
483 | /**
484 | * Bash script that creates a default TLS secured Docker configuration file; must be run on the Docker dockerHostUrl after the VM is provisioned.
485 | */
486 | public static final String CREATE_DEFAULT_DOCKERD_OPTS_TLS_ENABLED = ""
487 | + "echo Running: sudo service docker stop \n"
488 | + "sudo service docker stop \n"
489 | + "echo \"if [ ! -d /etc/systemd/system/docker.service.d ]; then sudo mkdir -p /etc/systemd/system/docker.service.d ; fi\" \n"
490 | + "if [ ! -d /etc/systemd/system/docker.service.d ]; then sudo mkdir -p /etc/systemd/system/docker.service.d ; fi \n"
491 | + "echo sudo cp -f ~/.azuredocker/dockerd_tls.config /etc/systemd/system/docker.service.d/custom.conf \n"
492 | + "sudo cp -f ~/.azuredocker/dockerd_tls.config /etc/systemd/system/docker.service.d/custom.conf \n"
493 | + "echo Running: sudo systemctl daemon-reload \n"
494 | + "sudo systemctl daemon-reload \n"
495 | + "echo Running: sudo service docker start \n"
496 | + "sudo service docker start \n";
497 |
498 | /**
499 | * Docker daemon config file allowing connections from any Docker client.
500 | */
501 | public static final String DEFAULT_DOCKERD_CONFIG_TLS_DISABLED = ""
502 | + "[Service]\n"
503 | + "ExecStart=\n"
504 | + "ExecStart=/usr/bin/dockerd --tls=false -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock\n";
505 |
506 | /**
507 | * Bash script that creates a default unsecured Docker configuration file; must be run on the Docker dockerHostUrl after the VM is provisioned.
508 | */
509 | public static final String CREATE_DEFAULT_DOCKERD_OPTS_TLS_DISABLED = ""
510 | + "echo Running: sudo service docker stop\n"
511 | + "sudo service docker stop\n"
512 | + "echo \"if [ ! -d /etc/systemd/system/docker.service.d ]; then sudo mkdir -p /etc/systemd/system/docker.service.d ; fi\" \n"
513 | + "if [ ! -d /etc/systemd/system/docker.service.d ]; then sudo mkdir -p /etc/systemd/system/docker.service.d ; fi \n"
514 | + "echo sudo cp -f ~/.azuredocker/dockerd_notls.config /etc/systemd/system/docker.service.d/custom.conf \n"
515 | + "sudo cp -f ~/.azuredocker/dockerd_notls.config /etc/systemd/system/docker.service.d/custom.conf \n"
516 | + "echo Running: sudo systemctl daemon-reload \n"
517 | + "sudo systemctl daemon-reload \n"
518 | + "echo Running: sudo service docker start \n"
519 | + "sudo service docker start \n";
520 | }
521 |
--------------------------------------------------------------------------------