• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.release;
17 
18 import static java.nio.charset.StandardCharsets.UTF_8;
19 
20 import java.io.IOException;
21 import java.nio.file.FileVisitResult;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.nio.file.SimpleFileVisitor;
26 import java.nio.file.attribute.BasicFileAttributes;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33 import java.util.stream.Stream;
34 import org.apache.commons.cli.CommandLine;
35 import org.apache.commons.io.FileUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.w3c.dom.Document;
38 import org.w3c.dom.Node;
39 import software.amazon.awssdk.utils.Validate;
40 import software.amazon.awssdk.utils.internal.CodegenNamingUtils;
41 
42 /**
43  * A command line application to create a new, empty service. This *does not* add the new service to the shared pom.xmls, that
44  * should be done via {@link FinalizeNewServiceModuleMain}.
45  *
46  * Example usage:
47  * <pre>
48  * mvn exec:java -pl :release-scripts \
49  *     -Dexec.mainClass="software.amazon.awssdk.release.CreateNewServiceModuleMain" \
50  *     -Dexec.args="--maven-project-root /path/to/root
51  *                  --maven-project-version 2.1.4-SNAPSHOT
52  *                  --service-id 'Service Id'
53  *                  --service-module-name service-module-name
54  *                  --service-protocol json"
55  * </pre>
56  *
57  * <p>By default the service new pom will include a dependency to the http-auth-aws module, this is only needed if the service
58  * has one or more operations signed by any of the aws algorithms, e.g., sigv4 or sigv4a, but not needed if the service uses,
59  * say, bearer auth (e.g., codecatalyst at the moment). Excluding this can be done by adding the
60  * {@code --exclude-internal-dependency http-auth-aws} switch. For example
61  * <pre>
62  * mvn exec:java -pl :release-scripts \
63  *     -Dexec.mainClass="software.amazon.awssdk.release.CreateNewServiceModuleMain" \
64  *     -Dexec.args="--maven-project-root /path/to/root
65  *                  --maven-project-version 2.1.4-SNAPSHOT
66  *                  --service-id 'Service Id'
67  *                  --service-module-name service-module-name
68  *                  --service-protocol json
69  *                  --exclude-internal-dependency http-auth-aws"
70  * </pre>
71  */
72 public class CreateNewServiceModuleMain extends Cli {
73 
74     private static final Set<String> DEFAULT_INTERNAL_DEPENDENCIES = toSet("http-auth-aws");
75 
CreateNewServiceModuleMain()76     private CreateNewServiceModuleMain() {
77         super(requiredOption("service-module-name", "The name of the service module to be created."),
78               requiredOption("service-id", "The service ID of the service module to be created."),
79               requiredOption("service-protocol", "The protocol of the service module to be created."),
80               requiredOption("maven-project-root", "The root directory for the maven project."),
81               requiredOption("maven-project-version", "The maven version of the service module to be created."),
82               optionalMultiValueOption("include-internal-dependency", "Includes an internal dependency from new service pom."),
83               optionalMultiValueOption("exclude-internal-dependency", "Excludes an internal dependency from new service pom."));
84     }
85 
main(String[] args)86     public static void main(String[] args) {
87         new CreateNewServiceModuleMain().run(args);
88     }
89 
toSet(String...args)90     static Set<String> toSet(String...args) {
91         Set<String> result = new LinkedHashSet<>();
92         for (String arg : args) {
93             result.add(arg);
94         }
95         return Collections.unmodifiableSet(result);
96 
97     }
98 
toList(String[] optionValues)99     static List<String> toList(String[] optionValues) {
100         if (optionValues == null) {
101             return Collections.emptyList();
102         }
103         return Arrays.asList(optionValues);
104     }
105 
computeInternalDependencies(List<String> includes, List<String> excludes)106     static Set<String> computeInternalDependencies(List<String> includes, List<String> excludes) {
107         Set<String> result = new LinkedHashSet<>(DEFAULT_INTERNAL_DEPENDENCIES);
108         result.addAll(includes);
109         excludes.forEach(result::remove);
110         return Collections.unmodifiableSet(result);
111     }
112 
113     @Override
run(CommandLine commandLine)114     protected void run(CommandLine commandLine) throws Exception {
115         new NewServiceCreator(commandLine).run();
116     }
117 
118     private static class NewServiceCreator {
119         private final Path mavenProjectRoot;
120         private final String mavenProjectVersion;
121         private final String serviceModuleName;
122         private final String serviceId;
123         private final String serviceProtocol;
124         private final Set<String> internalDependencies;
125 
NewServiceCreator(CommandLine commandLine)126         private NewServiceCreator(CommandLine commandLine) {
127             this.mavenProjectRoot = Paths.get(commandLine.getOptionValue("maven-project-root").trim());
128             this.mavenProjectVersion = commandLine.getOptionValue("maven-project-version").trim();
129             this.serviceModuleName = commandLine.getOptionValue("service-module-name").trim();
130             this.serviceId = commandLine.getOptionValue("service-id").trim();
131             this.serviceProtocol = transformSpecialProtocols(commandLine.getOptionValue("service-protocol").trim());
132             this.internalDependencies = computeInternalDependencies(toList(commandLine
133                                                                                .getOptionValues("include-internal-dependency")),
134                                                                     toList(commandLine
135                                                                                .getOptionValues("exclude-internal-dependency")));
136             Validate.isTrue(Files.exists(mavenProjectRoot), "Project root does not exist: " + mavenProjectRoot);
137         }
138 
transformSpecialProtocols(String protocol)139         private String transformSpecialProtocols(String protocol) {
140             switch (protocol) {
141                 case "ec2": return "query";
142                 case "rest-xml": return "xml";
143                 case "rest-json": return "json";
144                 default: return protocol;
145             }
146         }
147 
run()148         public void run() throws Exception {
149             Path servicesRoot = mavenProjectRoot.resolve("services");
150             Path templateModulePath = servicesRoot.resolve("new-service-template");
151             Path newServiceModulePath = servicesRoot.resolve(serviceModuleName);
152 
153             createNewModuleFromTemplate(templateModulePath, newServiceModulePath);
154             replaceTemplatePlaceholders(newServiceModulePath);
155 
156             Path newServicePom = newServiceModulePath.resolve("pom.xml");
157             new AddInternalDependenciesTransformer(internalDependencies).transform(newServicePom);
158         }
159 
createNewModuleFromTemplate(Path templateModulePath, Path newServiceModule)160         private void createNewModuleFromTemplate(Path templateModulePath, Path newServiceModule) throws IOException {
161             FileUtils.copyDirectory(templateModulePath.toFile(), newServiceModule.toFile());
162         }
163 
replaceTemplatePlaceholders(Path newServiceModule)164         private void replaceTemplatePlaceholders(Path newServiceModule) throws IOException {
165             Files.walkFileTree(newServiceModule, new SimpleFileVisitor<Path>() {
166                 @Override
167                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
168                     replacePlaceholdersInFile(file);
169                     return FileVisitResult.CONTINUE;
170                 }
171             });
172         }
173 
replacePlaceholdersInFile(Path file)174         private void replacePlaceholdersInFile(Path file) throws IOException {
175             String fileContents = new String(Files.readAllBytes(file), UTF_8);
176             String newFileContents = replacePlaceholders(fileContents);
177             Files.write(file, newFileContents.getBytes(UTF_8));
178         }
179 
replacePlaceholders(String line)180         private String replacePlaceholders(String line) {
181             String[] searchList = {
182                     "{{MVN_ARTIFACT_ID}}",
183                     "{{MVN_NAME}}",
184                     "{{MVN_VERSION}}",
185                     "{{PROTOCOL}}"
186             };
187             String[] replaceList = {
188                 serviceModuleName,
189                 mavenName(serviceId),
190                 mavenProjectVersion,
191                 serviceProtocol
192             };
193             return StringUtils.replaceEach(line, searchList, replaceList);
194         }
195 
mavenName(String serviceId)196         private String mavenName(String serviceId) {
197             return Stream.of(CodegenNamingUtils.splitOnWordBoundaries(serviceId))
198                          .map(StringUtils::capitalize)
199                          .collect(Collectors.joining(" "));
200         }
201     }
202 
203     static class AddInternalDependenciesTransformer extends PomTransformer {
204         private final Set<String> internalDependencies;
205 
AddInternalDependenciesTransformer(Set<String> internalDependencies)206         AddInternalDependenciesTransformer(Set<String> internalDependencies) {
207             this.internalDependencies = internalDependencies;
208         }
209 
210         @Override
updateDocument(Document doc)211         protected void updateDocument(Document doc) {
212             Node project = findChild(doc, "project");
213             Node dependencies = findChild(project, "dependencies");
214             for (String internalDependency : internalDependencies) {
215                 dependencies.appendChild(sdkDependencyElement(doc, internalDependency));
216             }
217         }
218     }
219 
220 }
221