Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Jib Ownership Maven Extension #1

Merged
merged 16 commits into from
Jun 1, 2020
Merged
5 changes: 3 additions & 2 deletions first-party/ownership-extension/maven/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

chanseokoh marked this conversation as resolved.
Show resolved Hide resolved
/**
* Extension-specific Maven configuration.
*
* <p>Example usage in {@code pom.xml}:
*
* <pre>{@code
* <configuration implementation="com.google.cloud.tools.jib.plugins.extension.maven.ownership.Configuration">
* <rules>
* <!-- sets UID 300 for all files under /app/classes/ -->
* <rule>
* <glob>/app/classes/**</glob>
* <ownership>300</ownership>
* </rule>
* <!-- sets UID 300 and GID 500 for all files under /static/ -->
* <rule>
* <glob>/static/**</glob>
* <ownership>300:500</ownership>
* </rule>
* </rules>
* </configuration>
* }</pre>
*/
public class Configuration {

public static class Rule {
@VisibleForTesting String glob = "";
@VisibleForTesting String ownership = "";

String getGlob() {
return glob;
}

String getOwnership() {
return ownership;
}
}

@VisibleForTesting List<Rule> rules = new ArrayList<>();

List<Rule> getRules() {
return rules;
}
}
Original file line number Diff line number Diff line change
@@ -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<Configuration> {

private Map<PathMatcher, String> pathMatchers = new LinkedHashMap<>();

@Override
public Optional<Class<Configuration>> getExtraConfigType() {
return Optional.of(Configuration.class);
}

@Override
public ContainerBuildPlan extendContainerBuildPlan(
ContainerBuildPlan buildPlan,
Map<String, String> properties,
Optional<Configuration> 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<FileEntriesLayer> layers = (List<FileEntriesLayer>) buildPlan.getLayers();
List<FileEntriesLayer> newLayers =
layers.stream().map(this::applyRulesToLayer).collect(Collectors.toList());
return buildPlan.toBuilder().setLayers(newLayers).build();
}

private FileEntriesLayer applyRulesToLayer(FileEntriesLayer layer) {
List<FileEntry> 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<PathMatcher, String> mapEntry : pathMatchers.entrySet()) {
PathMatcher matcher = mapEntry.getKey();
Path pathInContainer = Paths.get(entry.getExtractionPath().toString());
if (matcher.matches(pathInContainer)) {
chanseokoh marked this conversation as resolved.
Show resolved Hide resolved
newOwnership = mapEntry.getValue();
}
}
return newOwnership == null
? entry
: new FileEntry(
entry.getSourceFile(),
entry.getExtractionPath(),
entry.getPermissions(),
entry.getModificationTime(),
newOwnership);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.google.cloud.tools.jib.plugins.extension.maven.ownership.JibOwnershipExtension
Original file line number Diff line number Diff line change
@@ -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<String, String> properties;

private static <T> List<T> mapLayerEntries(
FileEntriesLayer layer, Function<FileEntry, T> 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));
}
}