diff --git a/first-party/ownership-extension/maven/build.gradle b/first-party/ownership-extension/maven/build.gradle index 7517d41..9ce5652 100644 --- a/first-party/ownership-extension/maven/build.gradle +++ b/first-party/ownership-extension/maven/build.gradle @@ -6,6 +6,7 @@ plugins { dependencies { compileOnly "com.google.cloud.tools:jib-maven-plugin-extension-api:${dependencyVersions.GRADLE_EXTENSION}" + compileOnly "com.google.guava:guava:${dependencyVersions.GUAVA}" testImplementation "com.google.cloud.tools:jib-maven-plugin-extension-api:${dependencyVersions.GRADLE_EXTENSION}" testImplementation "junit:junit:${dependencyVersions.JUNIT}" @@ -44,9 +45,9 @@ publishing { // Release plugin (git release commits and version updates) release { - tagTemplate = 'v$version-maven-ownership' + tagTemplate = 'v$version-ownership-maven' git { - requireBranch = /^maven_ownership_release_v\d+.*$/ //regex + requireBranch = /^ownership_maven_release_v\d+.*$/ //regex } } /* RELEASE */ diff --git a/first-party/ownership-extension/maven/src/main/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/Configuration.java b/first-party/ownership-extension/maven/src/main/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/Configuration.java new file mode 100644 index 0000000..2e0653e --- /dev/null +++ b/first-party/ownership-extension/maven/src/main/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/Configuration.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Google LLC. + * + * 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 + * + * http://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 com.google.cloud.tools.jib.plugins.extension.maven.ownership; + +import com.google.common.annotations.VisibleForTesting; +import java.util.ArrayList; +import java.util.List; + +/** + * Extension-specific Maven configuration. + * + *

Example usage in {@code pom.xml}: + * + *

{@code
+ * 
+ *   
+ *     
+ *     
+ *       /app/classes/**
+ *       300
+ *     
+ *     
+ *     
+ *       /static/**
+ *       300:500
+ *     
+ *   
+ * 
+ * }
+ */ +public class Configuration { + + public static class Rule { + @VisibleForTesting String glob = ""; + @VisibleForTesting String ownership = ""; + + String getGlob() { + return glob; + } + + String getOwnership() { + return ownership; + } + } + + @VisibleForTesting List rules = new ArrayList<>(); + + List getRules() { + return rules; + } +} diff --git a/first-party/ownership-extension/maven/src/main/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/JibOwnershipExtension.java b/first-party/ownership-extension/maven/src/main/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/JibOwnershipExtension.java new file mode 100644 index 0000000..71beb53 --- /dev/null +++ b/first-party/ownership-extension/maven/src/main/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/JibOwnershipExtension.java @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Google LLC. + * + * 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 + * + * http://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 com.google.cloud.tools.jib.plugins.extension.maven.ownership; + +import com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan; +import com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer; +import com.google.cloud.tools.jib.api.buildplan.FileEntry; +import com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension; +import com.google.cloud.tools.jib.maven.extension.MavenData; +import com.google.cloud.tools.jib.plugins.extension.ExtensionLogger; +import com.google.cloud.tools.jib.plugins.extension.ExtensionLogger.LogLevel; +import com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Collectors; + +public class JibOwnershipExtension implements JibMavenPluginExtension { + + private Map pathMatchers = new LinkedHashMap<>(); + + @Override + public Optional> getExtraConfigType() { + return Optional.of(Configuration.class); + } + + @Override + public ContainerBuildPlan extendContainerBuildPlan( + ContainerBuildPlan buildPlan, + Map properties, + Optional config, + MavenData mavenData, + ExtensionLogger logger) + throws JibPluginExtensionException { + logger.log(LogLevel.LIFECYCLE, "Running Jib Ownership Extension"); + if (!config.isPresent()) { + logger.log(LogLevel.WARN, "Nothing configured for Jib Ownership Extension"); + return buildPlan; + } + + for (Configuration.Rule rule : config.get().getRules()) { + if (rule.getGlob().isEmpty()) { + throw new JibPluginExtensionException( + getClass(), "glob pattern not given in ownership configuration"); + } + pathMatchers.put( + FileSystems.getDefault().getPathMatcher("glob:" + rule.getGlob()), rule.getOwnership()); + } + + @SuppressWarnings("unchecked") + List layers = (List) buildPlan.getLayers(); + List newLayers = + layers.stream().map(this::applyRulesToLayer).collect(Collectors.toList()); + return buildPlan.toBuilder().setLayers(newLayers).build(); + } + + private FileEntriesLayer applyRulesToLayer(FileEntriesLayer layer) { + List entries = + layer.getEntries().stream().map(this::applyRulesToFileEntry).collect(Collectors.toList()); + return layer.toBuilder().setEntries(entries).build(); + } + + private FileEntry applyRulesToFileEntry(FileEntry entry) { + String newOwnership = null; + + for (Entry mapEntry : pathMatchers.entrySet()) { + PathMatcher matcher = mapEntry.getKey(); + Path pathInContainer = Paths.get(entry.getExtractionPath().toString()); + if (matcher.matches(pathInContainer)) { + newOwnership = mapEntry.getValue(); + } + } + return newOwnership == null + ? entry + : new FileEntry( + entry.getSourceFile(), + entry.getExtractionPath(), + entry.getPermissions(), + entry.getModificationTime(), + newOwnership); + } +} diff --git a/first-party/ownership-extension/maven/src/main/resources/META-INF/services/com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension b/first-party/ownership-extension/maven/src/main/resources/META-INF/services/com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension new file mode 100644 index 0000000..b988283 --- /dev/null +++ b/first-party/ownership-extension/maven/src/main/resources/META-INF/services/com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension @@ -0,0 +1 @@ +com.google.cloud.tools.jib.plugins.extension.maven.ownership.JibOwnershipExtension \ No newline at end of file diff --git a/first-party/ownership-extension/maven/src/test/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/JibOwnershipExtensionTest.java b/first-party/ownership-extension/maven/src/test/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/JibOwnershipExtensionTest.java new file mode 100644 index 0000000..23d90dc --- /dev/null +++ b/first-party/ownership-extension/maven/src/test/java/com/google/cloud/tools/jib/plugins/extension/maven/ownership/JibOwnershipExtensionTest.java @@ -0,0 +1,174 @@ +/* + * Copyright 2020 Google LLC. + * + * 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 + * + * http://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 com.google.cloud.tools.jib.plugins.extension.maven.ownership; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.verify; + +import com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath; +import com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan; +import com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer; +import com.google.cloud.tools.jib.api.buildplan.FileEntry; +import com.google.cloud.tools.jib.maven.extension.MavenData; +import com.google.cloud.tools.jib.plugins.extension.ExtensionLogger; +import com.google.cloud.tools.jib.plugins.extension.ExtensionLogger.LogLevel; +import com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class JibOwnershipExtensionTest { + + @Mock private MavenData mavenData; + @Mock private ExtensionLogger logger; + + private Map properties; + + private static List mapLayerEntries( + FileEntriesLayer layer, Function mapper) { + return layer.getEntries().stream().map(mapper).collect(Collectors.toList()); + } + + @Test + public void testExtendContainerBuildPlan_noConfiguration() throws JibPluginExtensionException { + ContainerBuildPlan buildPlan = ContainerBuildPlan.builder().build(); + ContainerBuildPlan newPlan = + new JibOwnershipExtension() + .extendContainerBuildPlan(buildPlan, properties, Optional.empty(), mavenData, logger); + assertSame(buildPlan, newPlan); + verify(logger).log(LogLevel.WARN, "Nothing configured for Jib Ownership Extension"); + } + + @Test + public void testExtendContainerBuildPlan_noGlobGiven() { + ContainerBuildPlan buildPlan = ContainerBuildPlan.builder().build(); + Configuration config = new Configuration(); + config.rules = Arrays.asList(new Configuration.Rule()); + try { + new JibOwnershipExtension() + .extendContainerBuildPlan(buildPlan, properties, Optional.of(config), mavenData, logger); + fail(); + } catch (JibPluginExtensionException ex) { + assertEquals(JibOwnershipExtension.class, ex.getExtensionClass()); + assertEquals("glob pattern not given in ownership configuration", ex.getMessage()); + } + } + + @Test + public void testExtendContainerBuildPlan() throws JibPluginExtensionException { + FileEntriesLayer layer1 = + FileEntriesLayer.builder() + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/file")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/another")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/sub/dir/file")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/sub/dir/another")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/untouched/file")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/untouched/another")) + .build(); + FileEntriesLayer layer2 = + FileEntriesLayer.builder() + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/foo")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/bar")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/sub/dir/foo")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/sub/dir/bar")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/untouched/foo")) + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/untouched/bar")) + .build(); + ContainerBuildPlan buildPlan = + ContainerBuildPlan.builder().addLayer(layer1).addLayer(layer2).build(); + + Configuration.Rule rule1 = new Configuration.Rule(); + rule1.glob = "/target/**"; + rule1.ownership = "10:20"; + Configuration.Rule rule2 = new Configuration.Rule(); + rule2.glob = "**/bar"; + rule2.ownership = "999:777"; + Configuration config = new Configuration(); + config.rules = Arrays.asList(rule1, rule2); + + ContainerBuildPlan newPlan = + new JibOwnershipExtension() + .extendContainerBuildPlan( + buildPlan, properties, Optional.of(config), mavenData, logger); + + FileEntriesLayer newLayer1 = (FileEntriesLayer) newPlan.getLayers().get(0); + FileEntriesLayer newLayer2 = (FileEntriesLayer) newPlan.getLayers().get(1); + + assertEquals( + Arrays.asList( + AbsoluteUnixPath.get("/target/file"), + AbsoluteUnixPath.get("/target/another"), + AbsoluteUnixPath.get("/target/sub/dir/file"), + AbsoluteUnixPath.get("/target/sub/dir/another"), + AbsoluteUnixPath.get("/untouched/file"), + AbsoluteUnixPath.get("/untouched/another")), + mapLayerEntries(newLayer1, FileEntry::getExtractionPath)); + assertEquals( + Arrays.asList("10:20", "10:20", "10:20", "10:20", "", ""), + mapLayerEntries(newLayer1, FileEntry::getOwnership)); + + assertEquals( + Arrays.asList( + AbsoluteUnixPath.get("/target/foo"), + AbsoluteUnixPath.get("/target/bar"), + AbsoluteUnixPath.get("/target/sub/dir/foo"), + AbsoluteUnixPath.get("/target/sub/dir/bar"), + AbsoluteUnixPath.get("/untouched/foo"), + AbsoluteUnixPath.get("/untouched/bar")), + mapLayerEntries(newLayer2, FileEntry::getExtractionPath)); + assertEquals( + Arrays.asList("10:20", "999:777", "10:20", "999:777", "", "999:777"), + mapLayerEntries(newLayer2, FileEntry::getOwnership)); + } + + @Test + public void testExtendContainerBuildPlan_lastConfigWins() throws JibPluginExtensionException { + FileEntriesLayer layer = + FileEntriesLayer.builder() + .addEntry(Paths.get("whatever"), AbsoluteUnixPath.get("/target/file")) + .build(); + ContainerBuildPlan buildPlan = ContainerBuildPlan.builder().addLayer(layer).build(); + + Configuration.Rule rule1 = new Configuration.Rule(); + rule1.glob = "**"; + rule1.ownership = "10:20"; + Configuration.Rule rule2 = new Configuration.Rule(); + rule2.glob = "**"; + rule2.ownership = "999:777"; + Configuration config = new Configuration(); + config.rules = Arrays.asList(rule1, rule2); + + ContainerBuildPlan newPlan = + new JibOwnershipExtension() + .extendContainerBuildPlan( + buildPlan, properties, Optional.of(config), mavenData, logger); + + FileEntriesLayer newLayer = (FileEntriesLayer) newPlan.getLayers().get(0); + assertEquals(Arrays.asList("999:777"), mapLayerEntries(newLayer, FileEntry::getOwnership)); + } +}