View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2020 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.devops.conga.plugins.aem.maven;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Set;
29  
30  import javax.inject.Inject;
31  
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.plugins.annotations.Mojo;
36  import org.apache.maven.plugins.annotations.Parameter;
37  import org.codehaus.plexus.archiver.ArchiverException;
38  import org.codehaus.plexus.archiver.zip.ZipArchiver;
39  
40  import com.google.inject.name.Named;
41  
42  import io.wcm.devops.conga.generator.util.FileUtil;
43  import io.wcm.devops.conga.plugins.aem.maven.model.ModelParser;
44  
45  /**
46   * Builds an Dispatcher configuration ZIP file dedicated for deployment via Adobe Cloud Manager
47   * for the given environment and node(s).
48   * Only nodes with role <code>aem-dispatcher-cloud</code> are respected.
49   */
50  @Mojo(name = "cloudmanager-dispatcher-config", threadSafe = true)
51  public final class CloudManagerDispatcherConfigMojo extends AbstractCloudManagerMojo {
52  
53    private static final String ROLE_AEM_DISPATCHER_CLOUD = "aem-dispatcher-cloud";
54  
55    /**
56     * Set this to "true" to skip installing packages to CRX although configured in the POM.
57     */
58    @Parameter(property = "conga.cloudManager.dispatcherConfig.skip", defaultValue = "false")
59    private boolean skip;
60  
61    @Inject
62    @Named("zip")
63    private ZipArchiver zipArchiver;
64  
65    @Parameter(defaultValue = "${project.build.outputTimestamp}")
66    private String outputTimestamp;
67  
68    @Override
69    public void execute() throws MojoExecutionException, MojoFailureException {
70      if (skip) {
71        return;
72      }
73  
74      int dispatcherNodeCount = 0;
75      List<File> environmentDirs = getEnvironmentDir();
76      for (File environmentDir : environmentDirs) {
77        List<File> nodeDirs = getNodeDirs(environmentDir);
78        for (File nodeDir : nodeDirs) {
79          ModelParser modelParser = new ModelParser(nodeDir);
80          if (modelParser.hasRole(ROLE_AEM_DISPATCHER_CLOUD)) {
81            buildDispatcherConfig(environmentDir, nodeDir);
82            dispatcherNodeCount++;
83          }
84        }
85      }
86  
87      if (dispatcherNodeCount > 1) {
88        throw new MojoFailureException("More than one node with role '" + ROLE_AEM_DISPATCHER_CLOUD + "' found - "
89            + "AEM Cloud service supports only a single dispatcher configuration.");
90      }
91    }
92  
93    private void buildDispatcherConfig(File environmentDir, File nodeDir) throws MojoExecutionException {
94      File targetFile = new File(getTargetDir(), environmentDir.getName() + "." + nodeDir.getName() + ".dispatcher-config.zip");
95  
96      try {
97        String basePath = toZipDirectoryPath(nodeDir);
98        addZipDirectory(basePath, nodeDir, Collections.singleton(ModelParser.MODEL_FILE));
99        zipArchiver.setDestFile(targetFile);
100 
101       BuildOutputTimestamp buildOutputTimestamp = new BuildOutputTimestamp(outputTimestamp);
102       if (buildOutputTimestamp.isValid()) {
103         zipArchiver.configureReproducibleBuild(buildOutputTimestamp.toFileTime());
104       }
105 
106       zipArchiver.createArchive();
107     }
108     catch (ArchiverException | IOException ex) {
109       throw new MojoExecutionException("Unable to build file " + targetFile.getPath() + ": " + ex.getMessage(), ex);
110     }
111   }
112 
113   /**
114    * Recursive through all directory and add file to zipArchiver.
115    * This method has special support for symlinks which are required for dispatcher configuration.
116    * @param basePath Base path
117    * @param directory Directory to include
118    * @param excludeFiles Exclude filenames
119    * @throws IOException I/O exception
120    */
121   @SuppressWarnings("java:S3776") // ignore complexity
122   private void addZipDirectory(String basePath, File directory, Set<String> excludeFiles) throws IOException {
123     String directoryPath = toZipDirectoryPath(directory);
124     if (StringUtils.startsWith(directoryPath, basePath)) {
125       String relativeDirectoryPath = StringUtils.substring(directoryPath, basePath.length());
126       File[] files = directory.listFiles();
127       if (files != null) {
128         for (File file : files) {
129           if (excludeFiles.contains(file.getName())) {
130             continue;
131           }
132           if (file.isDirectory()) {
133             addZipDirectory(basePath, file, Collections.emptySet());
134           }
135           else if (Files.isSymbolicLink(file.toPath())) {
136             Path linkPath = file.toPath();
137             Path targetPath = linkPath.toRealPath();
138             Path symlinkPath = file.getParentFile().toPath().relativize(targetPath);
139             zipArchiver.addSymlink(relativeDirectoryPath + file.getName(), sanitizePathSeparators(symlinkPath.toString()));
140           }
141           else {
142             zipArchiver.addFile(file, relativeDirectoryPath + file.getName());
143           }
144         }
145       }
146     }
147   }
148 
149   private String toZipDirectoryPath(File directory) {
150     String canoncialPath = FileUtil.getCanonicalPath(directory);
151     return sanitizePathSeparators(canoncialPath) + "/";
152   }
153 
154   private String sanitizePathSeparators(String path) {
155     return StringUtils.replace(path, "\\", "/");
156   }
157 
158 }