Skip to content

Commit

Permalink
Add ExposedPort class with port/protocol fields, move exposed port co…
Browse files Browse the repository at this point in the history
…nfig-related parsing to frontend class (#449)
  • Loading branch information
TadCordle authored Jun 27, 2018
1 parent 2069024 commit 369e30d
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.cloud.tools.jib.cache.CacheDirectoryNotOwnedException;
import com.google.cloud.tools.jib.cache.CacheMetadataCorruptedException;
import com.google.cloud.tools.jib.cache.Caches;
import com.google.cloud.tools.jib.frontend.ExposedPortsParser;
import com.google.cloud.tools.jib.image.ImageReference;
import com.google.cloud.tools.jib.registry.LocalRegistry;
import java.io.IOException;
Expand Down Expand Up @@ -56,7 +57,9 @@ public void testSteps_forBuildToDockerRegistry()
.setTargetImage(ImageReference.of("localhost:5000", "testimage", "testtag"))
.setMainClass("HelloWorld")
.setJavaArguments(Collections.singletonList("An argument."))
.setExposedPorts(Arrays.asList("1000", "2000-2002/tcp", "3000/udp"))
.setExposedPorts(
ExposedPortsParser.parse(
Arrays.asList("1000", "2000-2002/tcp", "3000/udp"), logger))
.setAllowHttp(true)
.build();

Expand Down Expand Up @@ -100,7 +103,9 @@ public void testSteps_forBuildToDockerDaemon()
.setTargetImage(ImageReference.of(null, "testdocker", null))
.setMainClass("HelloWorld")
.setJavaArguments(Collections.singletonList("An argument."))
.setExposedPorts(Arrays.asList("1000", "2000-2002/tcp", "3000/udp"))
.setExposedPorts(
ExposedPortsParser.parse(
Arrays.asList("1000", "2000-2002/tcp", "3000/udp"), logger))
.build();

Path cacheDirectory = temporaryCacheDirectory.newFolder().toPath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,13 @@
import com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import com.google.cloud.tools.jib.registry.credentials.RegistryCredentials;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.lang.model.SourceVersion;

Expand All @@ -40,13 +36,6 @@ public class BuildConfiguration {

public static class Builder {

/**
* Pattern used for parsing information out of exposed port configurations.
*
* <p>Examples: 100, 200-210, 1000/tcp, 2000/udp, 500-600/tcp
*/
private static final Pattern portPattern = Pattern.compile("(\\d+)(?:-(\\d+))?(/tcp|/udp)?");

// All the parameters below are set to their default values.
@Nullable private ImageReference baseImageReference;
@Nullable private String baseImageCredentialHelperName;
Expand Down Expand Up @@ -212,7 +201,7 @@ public BuildConfiguration build() {
ImmutableList.copyOf(javaArguments),
ImmutableList.copyOf(jvmFlags),
ImmutableMap.copyOf(environmentMap),
expandPortRanges(exposedPorts),
ImmutableList.copyOf(exposedPorts),
targetFormat,
applicationLayersCacheConfiguration,
baseImageLayersCacheConfiguration,
Expand Down Expand Up @@ -240,63 +229,6 @@ public BuildConfiguration build() {
throw new IllegalStateException(errorMessage.toString());
}
}

/**
* TODO: Move this to a class in frontend
*
* <p>Converts/validates a list of ports with ranges to an expanded form without ranges.
*
* <p>Example: ["1000/tcp", "2000-2002/tcp"] -> ["1000/tcp", "2000/tcp", "2001/tcp", "2002/tcp"]
*
* @param ports the list of port numbers/ranges
* @return the ports as a list of integers
* @throws NumberFormatException if any of the ports are in an invalid format or out of range
*/
@VisibleForTesting
ImmutableList<String> expandPortRanges(List<String> ports) throws NumberFormatException {
ImmutableList.Builder<String> result = new ImmutableList.Builder<>();

for (String port : ports) {
Matcher matcher = portPattern.matcher(port);

if (!matcher.matches()) {
throw new NumberFormatException(
"Invalid port configuration: '"
+ port
+ "'. Make sure the port is a single number or a range of two numbers separated "
+ "with a '-', with or without protocol specified (e.g. '<portNum>/tcp' or "
+ "'<portNum>/udp').");
}

// Parse protocol
int min = Integer.parseInt(matcher.group(1));
int max = min;
if (!Strings.isNullOrEmpty(matcher.group(2))) {
max = Integer.parseInt(matcher.group(2));
}
String protocol = matcher.group(3);

// Error if configured as 'max-min' instead of 'min-max'
if (min > max) {
throw new NumberFormatException(
"Invalid port range '" + port + "'; smaller number must come first.");
}

// Warn for possibly invalid port numbers
if (min < 1 || max > 65535) {
// TODO: Add details/use HelpfulSuggestions for these warnings
buildLogger.warn("Port number '" + port + "' is out of usual range (1-65535).");
}

// Add all numbers in range to list
for (int portNum = min; portNum <= max; portNum++) {
// TODO: Use a class w/ port number and protocol instead of a string
result.add(portNum + (protocol == null ? "" : protocol));
}
}

return result.build();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2018 Google LLC. All rights reserved.
*
* 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.configuration;

/** Holds port number and protocol for an exposed port. */
public class PortWithProtocol {

/** Represents the protocol portion of the port. */
public enum Protocol {
TCP("tcp"),
UDP("udp");

private final String stringRepresentation;

Protocol(String stringRepresentation) {
this.stringRepresentation = stringRepresentation;
}

@Override
public String toString() {
return stringRepresentation;
}
}

private final int port;
private final Protocol protocol;

public PortWithProtocol(int port, Protocol protocol) {
this.port = port;
this.protocol = protocol;
}

public int getPort() {
return port;
}

public Protocol getProtocol() {
return protocol;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2018 Google LLC. All rights reserved.
*
* 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.frontend;

import com.google.cloud.tools.jib.builder.BuildLogger;
import com.google.cloud.tools.jib.configuration.PortWithProtocol;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ExposedPortsParser {

/**
* Pattern used for parsing information out of exposed port configurations.
*
* <p>Examples: 100, 200-210, 1000/tcp, 2000/udp, 500-600/tcp
*/
private static final Pattern portPattern = Pattern.compile("(\\d+)(?:-(\\d+))?(/tcp|/udp)?");

/**
* TODO: Return list of {@link PortWithProtocol}s instead of strings
*
* <p>Converts/validates a list of ports with ranges to an expanded form without ranges.
*
* <p>Example: {@code ["1000/tcp", "2000-2002/tcp"] -> ["1000/tcp", "2000/tcp", "2001/tcp",
* "2002/tcp"]}
*
* @param ports the list of port numbers/ranges
* @param buildLogger used to log warning messages
* @return the ports as a list of integers
* @throws NumberFormatException if any of the ports are in an invalid format or out of range
*/
@VisibleForTesting
public static ImmutableList<String> parse(List<String> ports, BuildLogger buildLogger)
throws NumberFormatException {
ImmutableList.Builder<String> result = new ImmutableList.Builder<>();

for (String port : ports) {
Matcher matcher = portPattern.matcher(port);

if (!matcher.matches()) {
throw new NumberFormatException(
"Invalid port configuration: '"
+ port
+ "'. Make sure the port is a single number or a range of two numbers separated "
+ "with a '-', with or without protocol specified (e.g. '<portNum>/tcp' or "
+ "'<portNum>/udp').");
}

// Parse protocol
int min = Integer.parseInt(matcher.group(1));
int max = min;
if (!Strings.isNullOrEmpty(matcher.group(2))) {
max = Integer.parseInt(matcher.group(2));
}
String protocol = matcher.group(3);

// Error if configured as 'max-min' instead of 'min-max'
if (min > max) {
throw new NumberFormatException(
"Invalid port range '" + port + "'; smaller number must come first.");
}

// Warn for possibly invalid port numbers
if (min < 1 || max > 65535) {
// TODO: Add details/use HelpfulSuggestions for these warnings
buildLogger.warn("Port number '" + port + "' is out of usual range (1-65535).");
}

// Add all numbers in range to list
String portString = (protocol == null ? "" : protocol);
for (int portNum = min; portNum <= max; portNum++) {
result.add(portNum + portString);
}
}

return result.build();
}

private ExposedPortsParser() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,10 @@
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class BuildConfigurationTest {

@Mock private BuildLogger mockLogger;

@Test
public void testBuilder() {
String expectedBaseImageServerUrl = "someserver";
Expand Down Expand Up @@ -202,61 +196,4 @@ public void testValidJavaClassRegex() {
Assert.assertFalse(BuildConfiguration.isValidJavaClass("{class}"));
Assert.assertFalse(BuildConfiguration.isValidJavaClass("not valid"));
}

@Test
public void testExpandPortList() {
List<String> goodInputs =
Arrays.asList("1000", "2000-2003", "3000-3000", "4000/tcp", "5000/udp", "6000-6002/tcp");
ImmutableList<String> expected =
new ImmutableList.Builder<String>()
.add(
"1000",
"2000",
"2001",
"2002",
"2003",
"3000",
"4000/tcp",
"5000/udp",
"6000/tcp",
"6001/tcp",
"6002/tcp")
.build();
BuildConfiguration.Builder builder = BuildConfiguration.builder(mockLogger);
ImmutableList<String> result = builder.expandPortRanges(goodInputs);
Assert.assertEquals(expected, result);

List<String> badInputs = Arrays.asList("abc", "/udp", "1000/abc", "a100/tcp", "20/udpabc");
for (String input : badInputs) {
try {
builder.expandPortRanges(Collections.singletonList(input));
Assert.fail();
} catch (NumberFormatException ex) {
Assert.assertEquals(
"Invalid port configuration: '"
+ input
+ "'. Make sure the port is a single number or a range of two numbers separated "
+ "with a '-', with or without protocol specified (e.g. '<portNum>/tcp' or "
+ "'<portNum>/udp').",
ex.getMessage());
}
}

try {
builder.expandPortRanges(Collections.singletonList("4002-4000"));
Assert.fail();
} catch (NumberFormatException ex) {
Assert.assertEquals(
"Invalid port range '4002-4000'; smaller number must come first.", ex.getMessage());
}

builder.expandPortRanges(Collections.singletonList("0"));
Mockito.verify(mockLogger).warn("Port number '0' is out of usual range (1-65535).");
builder.expandPortRanges(Collections.singletonList("70000"));
Mockito.verify(mockLogger).warn("Port number '70000' is out of usual range (1-65535).");
builder.expandPortRanges(Collections.singletonList("0-400"));
Mockito.verify(mockLogger).warn("Port number '0-400' is out of usual range (1-65535).");
builder.expandPortRanges(Collections.singletonList("1-70000"));
Mockito.verify(mockLogger).warn("Port number '1-70000' is out of usual range (1-65535).");
}
}
Loading

0 comments on commit 369e30d

Please sign in to comment.