mirror of
https://gitee.com/dcren/initializr.git
synced 2026-02-26 05:32:58 +08:00
Merge pull request #1417 from mhalbritter
* pr/1417: Polish "Add support for Docker Compose" Add support for Docker Compose Closes gh-1417
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.spring.container.docker.compose;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeFile;
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeFileWriter;
|
||||||
|
import io.spring.initializr.generator.io.IndentingWriter;
|
||||||
|
import io.spring.initializr.generator.io.IndentingWriterFactory;
|
||||||
|
import io.spring.initializr.generator.project.contributor.ProjectContributor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ProjectContributor} that creates a 'compose.yaml' file through a
|
||||||
|
* {@link ComposeFile}.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class ComposeProjectContributor implements ProjectContributor {
|
||||||
|
|
||||||
|
private final ComposeFile composeFile;
|
||||||
|
|
||||||
|
private final IndentingWriterFactory indentingWriterFactory;
|
||||||
|
|
||||||
|
private final ComposeFileWriter composeFileWriter;
|
||||||
|
|
||||||
|
public ComposeProjectContributor(ComposeFile composeFile, IndentingWriterFactory indentingWriterFactory) {
|
||||||
|
this.composeFile = composeFile;
|
||||||
|
this.indentingWriterFactory = indentingWriterFactory;
|
||||||
|
this.composeFileWriter = new ComposeFileWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contribute(Path projectRoot) throws IOException {
|
||||||
|
Path file = Files.createFile(projectRoot.resolve("compose.yaml"));
|
||||||
|
writeComposeFile(Files.newBufferedWriter(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeComposeFile(Writer out) throws IOException {
|
||||||
|
try (IndentingWriter writer = this.indentingWriterFactory.createIndentingWriter("yaml", out)) {
|
||||||
|
this.composeFileWriter.writeTo(writer, this.composeFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.spring.container.docker.compose;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeFile;
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeService;
|
||||||
|
import io.spring.initializr.generator.spring.container.docker.compose.Markdown.MarkdownTable;
|
||||||
|
import io.spring.initializr.generator.spring.documentation.HelpDocument;
|
||||||
|
import io.spring.initializr.generator.spring.documentation.HelpDocumentCustomizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link HelpDocumentCustomizer} that provide additional information about the
|
||||||
|
* {@link ComposeService compose services} that are defined for the project.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
public class DockerComposeHelpDocumentCustomizer implements HelpDocumentCustomizer {
|
||||||
|
|
||||||
|
private final ComposeFile composeFile;
|
||||||
|
|
||||||
|
public DockerComposeHelpDocumentCustomizer(ComposeFile composeFile) {
|
||||||
|
this.composeFile = composeFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(HelpDocument document) {
|
||||||
|
Map<String, Object> model = new HashMap<>();
|
||||||
|
if (this.composeFile.services().isEmpty()) {
|
||||||
|
model.put("serviceTable", null);
|
||||||
|
document.getWarnings()
|
||||||
|
.addItem(
|
||||||
|
"No Docker Compose services found. As of now, the application won't start! Please add at least one service to the `compose.yaml` file.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
MarkdownTable serviceTable = Markdown.table("Service name", "Image", "Tag", "Website");
|
||||||
|
this.composeFile.services()
|
||||||
|
.values()
|
||||||
|
.forEach((service) -> serviceTable.addRow(service.getName(), Markdown.code(service.getImage()),
|
||||||
|
Markdown.code(service.getImageTag()), Markdown.link("Website", service.getImageWebsite())));
|
||||||
|
model.put("serviceTable", serviceTable.toMarkdown());
|
||||||
|
}
|
||||||
|
document.addSection("documentation/docker-compose", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.spring.container.docker.compose;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for Markdown.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
final class Markdown {
|
||||||
|
|
||||||
|
private Markdown() {
|
||||||
|
// Static class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given string as code.
|
||||||
|
* @param code the input string
|
||||||
|
* @return string formatted as code
|
||||||
|
*/
|
||||||
|
static String code(String code) {
|
||||||
|
return "`%s`".formatted(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Markdown link.
|
||||||
|
* @param text text of the link
|
||||||
|
* @param url url of the link
|
||||||
|
* @return the formatted link in Markdown
|
||||||
|
*/
|
||||||
|
static String link(String text, String url) {
|
||||||
|
return "[%s](%s)".formatted(text, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Markdown table.
|
||||||
|
* @param headerCaptions captions of the header
|
||||||
|
* @return the Markdown table
|
||||||
|
*/
|
||||||
|
static MarkdownTable table(String... headerCaptions) {
|
||||||
|
return new MarkdownTable(headerCaptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Markdown table.
|
||||||
|
* <p>
|
||||||
|
* The formatted table is pretty-printed, all the columns are padded with spaces to
|
||||||
|
* have a consistent look.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
static class MarkdownTable {
|
||||||
|
|
||||||
|
private final List<String> headerCaptions;
|
||||||
|
|
||||||
|
private final List<List<String>> rows;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new table with the given header captions.
|
||||||
|
* @param headerCaptions the header captions
|
||||||
|
*/
|
||||||
|
MarkdownTable(String... headerCaptions) {
|
||||||
|
this.headerCaptions = List.of(headerCaptions);
|
||||||
|
this.rows = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new row with the given cells.
|
||||||
|
* @param cells the cells to add
|
||||||
|
* @throws IllegalArgumentException if the cell size doesn't match the number of
|
||||||
|
* header captions
|
||||||
|
*/
|
||||||
|
void addRow(String... cells) {
|
||||||
|
if (cells.length != this.headerCaptions.size()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected %d cells, got %d".formatted(this.headerCaptions.size(), cells.length));
|
||||||
|
}
|
||||||
|
this.rows.add(List.of(cells));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the whole table as Markdown.
|
||||||
|
* @return the table formatted as Markdown.
|
||||||
|
*/
|
||||||
|
String toMarkdown() {
|
||||||
|
int[] columnMaxLengths = calculateMaxColumnLengths();
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
writeHeader(result, columnMaxLengths);
|
||||||
|
writeHeaderSeparator(result, columnMaxLengths);
|
||||||
|
writeRows(result, columnMaxLengths);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeHeader(StringBuilder result, int[] columnMaxLengths) {
|
||||||
|
for (int i = 0; i < this.headerCaptions.size(); i++) {
|
||||||
|
result.append((i > 0) ? " " : "| ")
|
||||||
|
.append(pad(this.headerCaptions.get(i), columnMaxLengths[i]))
|
||||||
|
.append(" |");
|
||||||
|
}
|
||||||
|
result.append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeHeaderSeparator(StringBuilder result, int[] columnMaxLengths) {
|
||||||
|
for (int i = 0; i < this.headerCaptions.size(); i++) {
|
||||||
|
result.append((i > 0) ? " " : "| ").append("-".repeat(columnMaxLengths[i])).append(" |");
|
||||||
|
}
|
||||||
|
result.append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeRows(StringBuilder result, int[] columnMaxLengths) {
|
||||||
|
for (List<String> row : this.rows) {
|
||||||
|
for (int i = 0; i < row.size(); i++) {
|
||||||
|
result.append((i > 0) ? " " : "| ").append(pad(row.get(i), columnMaxLengths[i])).append(" |");
|
||||||
|
}
|
||||||
|
result.append(System.lineSeparator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] calculateMaxColumnLengths() {
|
||||||
|
int[] columnMaxLengths = new int[this.headerCaptions.size()];
|
||||||
|
for (int i = 0; i < this.headerCaptions.size(); i++) {
|
||||||
|
columnMaxLengths[i] = this.headerCaptions.get(i).length();
|
||||||
|
}
|
||||||
|
for (List<String> row : this.rows) {
|
||||||
|
for (int i = 0; i < row.size(); i++) {
|
||||||
|
String cell = row.get(i);
|
||||||
|
if (cell.length() > columnMaxLengths[i]) {
|
||||||
|
columnMaxLengths[i] = cell.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnMaxLengths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String pad(String input, int length) {
|
||||||
|
return input + " ".repeat(length - input.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support for Docker Compose.
|
||||||
|
*/
|
||||||
|
package io.spring.initializr.generator.spring.container.docker.compose;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
### Docker Compose support
|
||||||
|
|
||||||
|
This project contains a Docker Compose file named `compose.yaml`.
|
||||||
|
|
||||||
|
{{#serviceTable}}
|
||||||
|
In this file, the following services have been defined:
|
||||||
|
|
||||||
|
{{serviceTable}}
|
||||||
|
|
||||||
|
Please review the tags of the used images and set them to the same as you're running in production.
|
||||||
|
{{/serviceTable}}
|
||||||
|
{{^serviceTable}}
|
||||||
|
However, no services were found. As of now, the application won't start!
|
||||||
|
|
||||||
|
Please make sure to add at least one service in the `compose.yaml` file.
|
||||||
|
{{/serviceTable}}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.spring.container.docker.compose;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeFile;
|
||||||
|
import io.spring.initializr.generator.io.IndentingWriterFactory;
|
||||||
|
import io.spring.initializr.generator.io.SimpleIndentStrategy;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ComposeProjectContributor}.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class ComposeProjectContributorTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void composeFileIsContributedInProjectStructure(@TempDir Path projectDir) throws IOException {
|
||||||
|
ComposeFile compose = new ComposeFile();
|
||||||
|
compose.services().add("test", (service) -> service.image("my-image:1.2.3"));
|
||||||
|
new ComposeProjectContributor(compose, IndentingWriterFactory.withDefaultSettings()).contribute(projectDir);
|
||||||
|
Path composeFile = projectDir.resolve("compose.yaml");
|
||||||
|
assertThat(composeFile).isRegularFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void composeFileIsContributedUsingYamlContentId() throws IOException {
|
||||||
|
IndentingWriterFactory indentingWriterFactory = IndentingWriterFactory.create(new SimpleIndentStrategy(" "),
|
||||||
|
(factory) -> factory.indentingStrategy("yaml", new SimpleIndentStrategy("\t")));
|
||||||
|
ComposeFile composeFile = new ComposeFile();
|
||||||
|
composeFile.services()
|
||||||
|
.add("test", (service) -> service.imageAndTag("image:1.3.3").environment("a", "aa").environment("b", "bb"));
|
||||||
|
assertThat(generateComposeFile(composeFile, indentingWriterFactory)).isEqualToIgnoringNewLines("""
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
image: 'image:1.3.3'
|
||||||
|
environment:
|
||||||
|
- 'a=aa'
|
||||||
|
- 'b=bb'
|
||||||
|
""");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateComposeFile(ComposeFile composeFile, IndentingWriterFactory indentingWriterFactory)
|
||||||
|
throws IOException {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
new ComposeProjectContributor(composeFile, indentingWriterFactory).writeComposeFile(writer);
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.spring.container.docker.compose;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeFile;
|
||||||
|
import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
|
||||||
|
import io.spring.initializr.generator.io.text.MustacheSection;
|
||||||
|
import io.spring.initializr.generator.io.text.Section;
|
||||||
|
import io.spring.initializr.generator.spring.documentation.HelpDocument;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DockerComposeHelpDocumentCustomizer}.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
class DockerComposeHelpDocumentCustomizerTests {
|
||||||
|
|
||||||
|
private DockerComposeHelpDocumentCustomizer customizer;
|
||||||
|
|
||||||
|
private ComposeFile dockerComposeFile;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
this.dockerComposeFile = new ComposeFile();
|
||||||
|
this.customizer = new DockerComposeHelpDocumentCustomizer(this.dockerComposeFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addsDockerComposeSection() throws IOException {
|
||||||
|
this.dockerComposeFile.services()
|
||||||
|
.add("test", (service) -> service.imageAndTag("image-1:1.2.3").imageWebsite("https:/example.com/image-1"));
|
||||||
|
HelpDocument helpDocument = helpDocument();
|
||||||
|
this.customizer.customize(helpDocument);
|
||||||
|
assertThat(helpDocument.getSections()).hasSize(1);
|
||||||
|
Section section = helpDocument.getSections().get(0);
|
||||||
|
assertThat(section).isInstanceOf(MustacheSection.class);
|
||||||
|
StringWriter stringWriter = new StringWriter();
|
||||||
|
helpDocument.write(new PrintWriter(stringWriter));
|
||||||
|
assertThat(stringWriter.toString()).isEqualToIgnoringNewLines("""
|
||||||
|
### Docker Compose support
|
||||||
|
|
||||||
|
This project contains a Docker Compose file named `compose.yaml`.
|
||||||
|
|
||||||
|
In this file, the following services have been defined:
|
||||||
|
|
||||||
|
| Service name | Image | Tag | Website |
|
||||||
|
| ------------ | --------- | ------- | ------------------------------------- |
|
||||||
|
| test | `image-1` | `1.2.3` | [Website](https:/example.com/image-1) |
|
||||||
|
|
||||||
|
|
||||||
|
Please review the tags of the used images and set them to the same as you're running in production.""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addsWarningIfNoServicesAreDefined() throws IOException {
|
||||||
|
HelpDocument helpDocument = helpDocument();
|
||||||
|
this.customizer.customize(helpDocument);
|
||||||
|
assertThat(helpDocument.getWarnings().getItems()).containsExactly(
|
||||||
|
"No Docker Compose services found. As of now, the application won't start! Please add at least one service to the `compose.yaml` file.");
|
||||||
|
StringWriter stringWriter = new StringWriter();
|
||||||
|
helpDocument.write(new PrintWriter(stringWriter));
|
||||||
|
assertThat(stringWriter.toString()).isEqualToIgnoringNewLines(
|
||||||
|
"""
|
||||||
|
# Read Me First
|
||||||
|
The following was discovered as part of building this project:
|
||||||
|
|
||||||
|
* No Docker Compose services found. As of now, the application won't start! Please add at least one service to the `compose.yaml` file.
|
||||||
|
|
||||||
|
### Docker Compose support
|
||||||
|
|
||||||
|
This project contains a Docker Compose file named `compose.yaml`.
|
||||||
|
|
||||||
|
However, no services were found. As of now, the application won't start!
|
||||||
|
|
||||||
|
Please make sure to add at least one service in the `compose.yaml` file.""");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HelpDocument helpDocument() {
|
||||||
|
return new HelpDocument(new MustacheTemplateRenderer("/templates"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.spring.container.docker.compose;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.spring.container.docker.compose.Markdown.MarkdownTable;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link Markdown}.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
class MarkdownTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFormatCode() {
|
||||||
|
String code = Markdown.code("c = a + b");
|
||||||
|
assertThat(code).isEqualTo("`c = a + b`");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFormatLink() {
|
||||||
|
String link = Markdown.link("Spring Website", "https://spring.io/");
|
||||||
|
assertThat(link).isEqualTo("[Spring Website](https://spring.io/)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFormatCorrectly() {
|
||||||
|
MarkdownTable table = new MarkdownTable("a", "b1", "c22", "d333");
|
||||||
|
table.addRow("0", "1", "2", "3");
|
||||||
|
table.addRow("4", "5", "6", "7");
|
||||||
|
String markdown = table.toMarkdown();
|
||||||
|
assertThat(markdown).isEqualToIgnoringNewLines("""
|
||||||
|
| a | b1 | c22 | d333 |
|
||||||
|
| - | -- | --- | ---- |
|
||||||
|
| 0 | 1 | 2 | 3 |
|
||||||
|
| 4 | 5 | 6 | 7 |
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rowIsBiggerThanHeading() {
|
||||||
|
MarkdownTable table = new MarkdownTable("a", "b", "c", "d");
|
||||||
|
table.addRow("0.0", "1.1", "2.2", "3.3");
|
||||||
|
table.addRow("4.4", "5.5", "6.6", "7.7");
|
||||||
|
String markdown = table.toMarkdown();
|
||||||
|
assertThat(markdown).isEqualToIgnoringNewLines("""
|
||||||
|
| a | b | c | d |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 0.0 | 1.1 | 2.2 | 3.3 |
|
||||||
|
| 4.4 | 5.5 | 6.6 | 7.7 |
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void throwsIfCellsDifferFromHeader() {
|
||||||
|
MarkdownTable table = new MarkdownTable("a", "b", "c", "d");
|
||||||
|
assertThatThrownBy(() -> table.addRow("1")).isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("Expected 4 cells, got 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.container.docker.compose;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for a Docker Compose file.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class ComposeFile {
|
||||||
|
|
||||||
|
private final ComposeServiceContainer services = new ComposeServiceContainer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@linkplain ComposeServiceContainer service container} to use to
|
||||||
|
* configure services.
|
||||||
|
* @return the {@link ComposeServiceContainer}
|
||||||
|
*/
|
||||||
|
public ComposeServiceContainer services() {
|
||||||
|
return this.services;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.container.docker.compose;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.io.IndentingWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ComposeFile} writer for {@code compose.yaml}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
*/
|
||||||
|
public class ComposeFileWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a {@linkplain ComposeFile compose.yaml} using the specified
|
||||||
|
* {@linkplain IndentingWriter writer}.
|
||||||
|
* @param writer the writer to use
|
||||||
|
* @param compose the compose file to write
|
||||||
|
*/
|
||||||
|
public void writeTo(IndentingWriter writer, ComposeFile compose) {
|
||||||
|
writer.println("services:");
|
||||||
|
compose.services()
|
||||||
|
.values()
|
||||||
|
.sorted(Comparator.comparing(ComposeService::getName))
|
||||||
|
.forEach((service) -> writeService(writer, service));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeService(IndentingWriter writer, ComposeService service) {
|
||||||
|
writer.indented(() -> {
|
||||||
|
writer.println(service.getName() + ":");
|
||||||
|
writer.indented(() -> {
|
||||||
|
writer.println("image: '%s:%s'".formatted(service.getImage(), service.getImageTag()));
|
||||||
|
writerServiceEnvironment(writer, service.getEnvironment());
|
||||||
|
writerServicePorts(writer, service.getPorts());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writerServiceEnvironment(IndentingWriter writer, Map<String, String> environment) {
|
||||||
|
if (environment.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writer.println("environment:");
|
||||||
|
writer.indented(() -> {
|
||||||
|
for (Map.Entry<String, String> env : environment.entrySet()) {
|
||||||
|
writer.println("- '%s=%s'".formatted(env.getKey(), env.getValue()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writerServicePorts(IndentingWriter writer, Set<Integer> ports) {
|
||||||
|
if (ports.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writer.println("ports:");
|
||||||
|
writer.indented(() -> {
|
||||||
|
for (Integer port : ports) {
|
||||||
|
writer.println("- '%d'".formatted(port));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.container.docker.compose;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service to be declared in a Docker Compose file.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public final class ComposeService {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final String image;
|
||||||
|
|
||||||
|
private final String imageTag;
|
||||||
|
|
||||||
|
private final String imageWebsite;
|
||||||
|
|
||||||
|
private final Map<String, String> environment;
|
||||||
|
|
||||||
|
private final Set<Integer> ports;
|
||||||
|
|
||||||
|
private ComposeService(Builder builder) {
|
||||||
|
this.name = builder.name;
|
||||||
|
this.image = builder.image;
|
||||||
|
this.imageTag = builder.imageTag;
|
||||||
|
this.imageWebsite = builder.imageWebsite;
|
||||||
|
this.environment = Collections.unmodifiableMap(new TreeMap<>(builder.environment));
|
||||||
|
this.ports = Collections.unmodifiableSet(new TreeSet<>(builder.ports));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImage() {
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageTag() {
|
||||||
|
return this.imageTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageWebsite() {
|
||||||
|
return this.imageWebsite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getEnvironment() {
|
||||||
|
return this.environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Integer> getPorts() {
|
||||||
|
return this.ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for {@link ComposeService}.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
private String imageTag = "latest";
|
||||||
|
|
||||||
|
private String imageWebsite;
|
||||||
|
|
||||||
|
private final Map<String, String> environment = new TreeMap<>();
|
||||||
|
|
||||||
|
private final Set<Integer> ports = new TreeSet<>();
|
||||||
|
|
||||||
|
protected Builder(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder imageAndTag(String imageAndTag) {
|
||||||
|
String[] split = imageAndTag.split(":", 2);
|
||||||
|
String tag = (split.length == 1) ? "latest" : split[1];
|
||||||
|
return image(split[0]).imageTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder image(String image) {
|
||||||
|
this.image = image;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder imageTag(String imageTag) {
|
||||||
|
this.imageTag = imageTag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder imageWebsite(String imageWebsite) {
|
||||||
|
this.imageWebsite = imageWebsite;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder environment(String key, String value) {
|
||||||
|
this.environment.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder environment(Map<String, String> environment) {
|
||||||
|
this.environment.putAll(environment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder ports(Collection<Integer> ports) {
|
||||||
|
this.ports.addAll(ports);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder ports(int... ports) {
|
||||||
|
return ports(Arrays.stream(ports).boxed().toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the {@link ComposeService} instance.
|
||||||
|
* @return the built instance
|
||||||
|
*/
|
||||||
|
public ComposeService build() {
|
||||||
|
return new ComposeService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.container.docker.compose;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeService.Builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container for {@linkplain ComposeService Docker Compose services}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class ComposeServiceContainer {
|
||||||
|
|
||||||
|
private final Map<String, Builder> services = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify if this container is empty.
|
||||||
|
* @return {@code true} if no service is registered
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.services.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify if this container has a service customization with the specified
|
||||||
|
* {@code name}.
|
||||||
|
* @param name the name of a service
|
||||||
|
* @return {@code true} if a customization for a service with the specified
|
||||||
|
* {@code name} exists
|
||||||
|
*/
|
||||||
|
public boolean has(String name) {
|
||||||
|
return this.services.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link ComposeService services} to customize.
|
||||||
|
* @return the compose services
|
||||||
|
*/
|
||||||
|
public Stream<ComposeService> values() {
|
||||||
|
return this.services.values().stream().map(Builder::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a {@link ComposeService} with the specified name and {@link Consumer} to
|
||||||
|
* customize the object. If the service has already been added, the consumer can be
|
||||||
|
* used to further tune the existing service configuration.
|
||||||
|
* @param name the name of the service
|
||||||
|
* @param service a {@link Consumer} to customize the {@link ComposeService}
|
||||||
|
*/
|
||||||
|
public void add(String name, Consumer<Builder> service) {
|
||||||
|
service.accept(this.services.computeIfAbsent(name, Builder::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the service with the specified {@code name}.
|
||||||
|
* @param name the name of the service
|
||||||
|
* @return {@code true} if such a service was registered, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
public boolean remove(String name) {
|
||||||
|
return this.services.remove(name) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Docker Compose support.
|
||||||
|
*/
|
||||||
|
package io.spring.initializr.generator.container.docker.compose;
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.container.docker.compose;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import io.spring.initializr.generator.container.docker.compose.ComposeService.Builder;
|
||||||
|
import io.spring.initializr.generator.io.IndentingWriter;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ComposeFile}.
|
||||||
|
*
|
||||||
|
* @author Moritz Halbritter
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class ComposeFileWriterTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeBasicServices() {
|
||||||
|
ComposeFile file = new ComposeFile();
|
||||||
|
file.services().add("first", withSuffix(1));
|
||||||
|
file.services().add("second", withSuffix(2));
|
||||||
|
assertThat(write(file)).isEqualToIgnoringNewLines("""
|
||||||
|
services:
|
||||||
|
first:
|
||||||
|
image: 'image-1:image-tag-1'
|
||||||
|
second:
|
||||||
|
image: 'image-2:image-tag-2'
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeDetailedService() {
|
||||||
|
ComposeFile file = new ComposeFile();
|
||||||
|
file.services()
|
||||||
|
.add("elasticsearch",
|
||||||
|
(builder) -> builder.image("elasticsearch")
|
||||||
|
.imageTag("8.6.1")
|
||||||
|
.imageWebsite("https://www.docker.elastic.co/r/elasticsearch")
|
||||||
|
.environment("ELASTIC_PASSWORD", "secret")
|
||||||
|
.environment("discovery.type", "single-node")
|
||||||
|
.ports(9200, 9300));
|
||||||
|
assertThat(write(file)).isEqualToIgnoringNewLines("""
|
||||||
|
services:
|
||||||
|
elasticsearch:
|
||||||
|
image: 'elasticsearch:8.6.1'
|
||||||
|
environment:
|
||||||
|
- 'ELASTIC_PASSWORD=secret'
|
||||||
|
- 'discovery.type=single-node'
|
||||||
|
ports:
|
||||||
|
- '9200'
|
||||||
|
- '9300'
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void servicesAreOrderedByName() {
|
||||||
|
ComposeFile file = new ComposeFile();
|
||||||
|
file.services().add("b", withSuffix(2));
|
||||||
|
file.services().add("a", withSuffix(1));
|
||||||
|
assertThat(write(file)).isEqualToIgnoringNewLines("""
|
||||||
|
services:
|
||||||
|
a:
|
||||||
|
image: 'image-1:image-tag-1'
|
||||||
|
b:
|
||||||
|
image: 'image-2:image-tag-2'
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<Builder> withSuffix(int suffix) {
|
||||||
|
return (builder) -> builder.image("image-" + suffix).imageTag("image-tag-" + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String write(ComposeFile file) {
|
||||||
|
StringWriter out = new StringWriter();
|
||||||
|
IndentingWriter writer = new IndentingWriter(out, "\t"::repeat);
|
||||||
|
new ComposeFileWriter().writeTo(writer, file);
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.spring.initializr.generator.container.docker.compose;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ComposeServiceContainer}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class ComposeServiceContainerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isEmptyWithEmptyContainer() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
assertThat(container.isEmpty()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isEmptyWithService() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.image("my-image"));
|
||||||
|
assertThat(container.isEmpty()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasWithMatchingService() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.image("my-image"));
|
||||||
|
assertThat(container.has("test")).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasWithNonMatchingName() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.image("my-image"));
|
||||||
|
assertThat(container.has("another")).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tagIsSetToLatestIfNotGiven() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.image("my-image"));
|
||||||
|
assertThat(container.values()).singleElement().satisfies((service) -> {
|
||||||
|
assertThat(service.getImage()).isEqualTo("my-image");
|
||||||
|
assertThat(service.getImageTag()).isEqualTo("latest");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tagIsSetToLatestIfNotGivenInImageTag() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.imageAndTag("my-image"));
|
||||||
|
assertThat(container.values()).singleElement().satisfies((service) -> {
|
||||||
|
assertThat(service.getImage()).isEqualTo("my-image");
|
||||||
|
assertThat(service.getImageTag()).isEqualTo("latest");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tagIsSetToGivenInImageTag() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.imageAndTag("my-image:1.2.3"));
|
||||||
|
assertThat(container.values()).singleElement().satisfies((service) -> {
|
||||||
|
assertThat(service.getImage()).isEqualTo("my-image");
|
||||||
|
assertThat(service.getImageTag()).isEqualTo("1.2.3");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void portsAreSorted() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.imageAndTag("my-image").ports(8080));
|
||||||
|
container.add("test", (service) -> service.ports(7070));
|
||||||
|
assertThat(container.values()).singleElement()
|
||||||
|
.satisfies((service) -> assertThat(service.getPorts()).containsExactly(7070, 8080));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void environmentKeysAreSorted() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.imageAndTag("my-image").environment("z", "zz"));
|
||||||
|
container.add("test", (service) -> service.environment("a", "aa"));
|
||||||
|
assertThat(container.values()).singleElement()
|
||||||
|
.satisfies((service) -> assertThat(service.getEnvironment()).containsExactly(entry("a", "aa"),
|
||||||
|
entry("z", "zz")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void environmentIsMerged() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.imageAndTag("my-image").environment(Map.of("a", "aa", "z", "zz")));
|
||||||
|
container.add("test", (service) -> service.environment(Map.of("a", "aaa", "b", "bb")));
|
||||||
|
assertThat(container.values()).singleElement()
|
||||||
|
.satisfies((service) -> assertThat(service.getEnvironment()).containsExactly(entry("a", "aaa"),
|
||||||
|
entry("b", "bb"), entry("z", "zz")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customizeService() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> {
|
||||||
|
service.image("my-image");
|
||||||
|
service.imageTag("my-image-tag");
|
||||||
|
service.imageWebsite("https://example.com/my-image");
|
||||||
|
service.environment("param", "value");
|
||||||
|
service.ports(8080);
|
||||||
|
});
|
||||||
|
assertThat(container.values()).singleElement().satisfies((service) -> {
|
||||||
|
assertThat(service.getName()).isEqualTo("test");
|
||||||
|
assertThat(service.getImage()).isEqualTo("my-image");
|
||||||
|
assertThat(service.getImageTag()).isEqualTo("my-image-tag");
|
||||||
|
assertThat(service.getImageWebsite()).isEqualTo("https://example.com/my-image");
|
||||||
|
assertThat(service.getEnvironment()).containsOnly(entry("param", "value"));
|
||||||
|
assertThat(service.getPorts()).containsOnly(8080);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customizeTaskSeveralTimeReuseConfiguration() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> {
|
||||||
|
service.image("my-image");
|
||||||
|
service.imageTag("my-image-tag");
|
||||||
|
service.imageWebsite("https://example.com/my-image");
|
||||||
|
service.environment("param", "value");
|
||||||
|
service.ports(7070);
|
||||||
|
});
|
||||||
|
container.add("test", (service) -> {
|
||||||
|
service.image("my-image");
|
||||||
|
service.imageTag("my-image-tag");
|
||||||
|
service.imageWebsite("https://example.com/my-image");
|
||||||
|
service.environment("param", "value2");
|
||||||
|
service.ports(8080);
|
||||||
|
});
|
||||||
|
assertThat(container.values()).singleElement().satisfies((service) -> {
|
||||||
|
assertThat(service.getName()).isEqualTo("test");
|
||||||
|
assertThat(service.getImage()).isEqualTo("my-image");
|
||||||
|
assertThat(service.getImageTag()).isEqualTo("my-image-tag");
|
||||||
|
assertThat(service.getImageWebsite()).isEqualTo("https://example.com/my-image");
|
||||||
|
assertThat(service.getEnvironment()).containsOnly(entry("param", "value2"));
|
||||||
|
assertThat(service.getPorts()).containsOnly(7070, 8080);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeWithMatchingService() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.image("my-image"));
|
||||||
|
assertThat(container.remove("test")).isTrue();
|
||||||
|
assertThat(container.isEmpty()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeWithNonMatchingName() {
|
||||||
|
ComposeServiceContainer container = new ComposeServiceContainer();
|
||||||
|
container.add("test", (service) -> service.image("my-image"));
|
||||||
|
assertThat(container.remove("another")).isFalse();
|
||||||
|
assertThat(container.isEmpty()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -87,7 +87,8 @@ public class InitializrAutoConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public IndentingWriterFactory indentingWriterFactory() {
|
public IndentingWriterFactory indentingWriterFactory() {
|
||||||
return IndentingWriterFactory.create(new SimpleIndentStrategy("\t"));
|
return IndentingWriterFactory.create(new SimpleIndentStrategy("\t"),
|
||||||
|
(builder) -> builder.indentingStrategy("yaml", new SimpleIndentStrategy(" ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
Reference in New Issue
Block a user