Close those file handles 😅 (#1398)
This commit is contained in:
parent
28622da826
commit
739cc3025d
|
|
@ -246,8 +246,6 @@ public class ConfigurationAsCode extends ManagementLink {
|
||||||
return FormValidation.okWithMarkup("The configuration can be applied");
|
return FormValidation.okWithMarkup("The configuration can be applied");
|
||||||
} catch (ConfiguratorException | IllegalArgumentException e) {
|
} catch (ConfiguratorException | IllegalArgumentException e) {
|
||||||
return FormValidation.error(e, e.getCause() == null ? e.getMessage() : e.getCause().getMessage());
|
return FormValidation.error(e, e.getCause() == null ? e.getMessage() : e.getCause().getMessage());
|
||||||
} finally {
|
|
||||||
closeSources(yamlSources);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -616,7 +614,6 @@ public class ConfigurationAsCode extends ManagementLink {
|
||||||
lastTimeLoaded = System.currentTimeMillis();
|
lastTimeLoaded = System.currentTimeMillis();
|
||||||
ConfigurationContext context = new ConfigurationContext(registry);
|
ConfigurationContext context = new ConfigurationContext(registry);
|
||||||
configureWith(YamlUtils.loadFrom(sources, context), context);
|
configureWith(YamlUtils.loadFrom(sources, context), context);
|
||||||
closeSources(sources);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Restricted(NoExternalUse.class)
|
@Restricted(NoExternalUse.class)
|
||||||
|
|
@ -632,16 +629,6 @@ public class ConfigurationAsCode extends ManagementLink {
|
||||||
return checkWith(YamlUtils.loadFrom(sources, context), context);
|
return checkWith(YamlUtils.loadFrom(sources, context), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeSources(List<YamlSource> sources) {
|
|
||||||
for (YamlSource source : sources) {
|
|
||||||
try {
|
|
||||||
source.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.log(Level.WARNING, "Failed to close YAML Source", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursive search for all {@link #YAML_FILES_PATTERN} in provided base path
|
* Recursive search for all {@link #YAML_FILES_PATTERN} in provided base path
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package io.jenkins.plugins.casc.yaml;
|
package io.jenkins.plugins.casc.yaml;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import org.yaml.snakeyaml.reader.StreamReader;
|
import org.yaml.snakeyaml.reader.StreamReader;
|
||||||
|
|
||||||
|
|
@ -10,8 +11,8 @@ import org.yaml.snakeyaml.reader.StreamReader;
|
||||||
*/
|
*/
|
||||||
class StreamReaderWithSource extends StreamReader {
|
class StreamReaderWithSource extends StreamReader {
|
||||||
|
|
||||||
public StreamReaderWithSource(YamlSource source) throws IOException {
|
public StreamReaderWithSource(YamlSource source, Reader reader) throws IOException {
|
||||||
super(source.read());
|
super(reader);
|
||||||
try {
|
try {
|
||||||
final Field f = StreamReader.class.getDeclaredField("name");
|
final Field f = StreamReader.class.getDeclaredField("name");
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package io.jenkins.plugins.casc.yaml;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
|
|
||||||
public interface YamlReader<T> {
|
|
||||||
|
|
||||||
Reader open(T source) throws IOException;
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +1,46 @@
|
||||||
package io.jenkins.plugins.casc.yaml;
|
package io.jenkins.plugins.casc.yaml;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class YamlSource<T> implements AutoCloseable {
|
public class YamlSource<T> {
|
||||||
|
|
||||||
public static final YamlReader<String> READ_FROM_URL = config -> {
|
|
||||||
final URL url = URI.create(config).toURL();
|
|
||||||
return new InputStreamReader(url.openStream(), UTF_8);
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final YamlReader<Path> READ_FROM_PATH = Files::newBufferedReader;
|
|
||||||
|
|
||||||
public static final YamlReader<InputStream> READ_FROM_INPUTSTREAM = in -> new InputStreamReader(in, UTF_8);
|
|
||||||
|
|
||||||
public static final YamlReader<HttpServletRequest> READ_FROM_REQUEST = req -> {
|
|
||||||
// TODO get encoding from req.getContentType()
|
|
||||||
return new InputStreamReader(req.getInputStream(), UTF_8);
|
|
||||||
};
|
|
||||||
|
|
||||||
public final T source;
|
public final T source;
|
||||||
|
|
||||||
public final YamlReader<T> reader;
|
public YamlSource(T source) {
|
||||||
|
|
||||||
public YamlSource(T source, YamlReader<T> reader) {
|
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.reader = reader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YamlSource<InputStream> of(InputStream in) {
|
public static YamlSource<InputStream> of(InputStream in) {
|
||||||
return new YamlSource<>(in, READ_FROM_INPUTSTREAM);
|
return new YamlSource<>(in);
|
||||||
}
|
|
||||||
|
|
||||||
public static YamlSource<String> of(URL url) {
|
|
||||||
return new YamlSource<>(url.toExternalForm(), READ_FROM_URL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YamlSource<String> of(String url) {
|
public static YamlSource<String> of(String url) {
|
||||||
return new YamlSource<>(url, READ_FROM_URL);
|
return new YamlSource<>(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YamlSource<HttpServletRequest> of(HttpServletRequest req) {
|
public static YamlSource<HttpServletRequest> of(HttpServletRequest req) {
|
||||||
return new YamlSource<>(req, YamlSource.READ_FROM_REQUEST);
|
return new YamlSource<>(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YamlSource<Path> of(Path path) {
|
public static YamlSource<Path> of(Path path) {
|
||||||
return new YamlSource<>(path, YamlSource.READ_FROM_PATH);
|
return new YamlSource<>(path);
|
||||||
}
|
|
||||||
|
|
||||||
public Reader read() throws IOException {
|
|
||||||
return reader.open(source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String source() {
|
public String source() {
|
||||||
|
if (source instanceof HttpServletRequest) {
|
||||||
|
return ((HttpServletRequest) source).getPathInfo();
|
||||||
|
}
|
||||||
return source.toString();
|
return source.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "YamlSource: " + source;
|
return "YamlSource: " + source();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (reader instanceof BufferedReader) {
|
|
||||||
((BufferedReader) reader).close();
|
|
||||||
} else if (reader instanceof InputStreamReader) {
|
|
||||||
((InputStreamReader) reader).close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,17 @@ import io.jenkins.plugins.casc.ConfigurationContext;
|
||||||
import io.jenkins.plugins.casc.ConfiguratorException;
|
import io.jenkins.plugins.casc.ConfiguratorException;
|
||||||
import io.jenkins.plugins.casc.model.Mapping;
|
import io.jenkins.plugins.casc.model.Mapping;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.yaml.snakeyaml.LoaderOptions;
|
import org.yaml.snakeyaml.LoaderOptions;
|
||||||
import org.yaml.snakeyaml.composer.Composer;
|
import org.yaml.snakeyaml.composer.Composer;
|
||||||
import org.yaml.snakeyaml.error.YAMLException;
|
import org.yaml.snakeyaml.error.YAMLException;
|
||||||
|
|
@ -21,6 +28,8 @@ import org.yaml.snakeyaml.nodes.SequenceNode;
|
||||||
import org.yaml.snakeyaml.parser.ParserImpl;
|
import org.yaml.snakeyaml.parser.ParserImpl;
|
||||||
import org.yaml.snakeyaml.resolver.Resolver;
|
import org.yaml.snakeyaml.resolver.Resolver;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||||
*/
|
*/
|
||||||
|
|
@ -29,13 +38,12 @@ public final class YamlUtils {
|
||||||
|
|
||||||
public static final Logger LOGGER = Logger.getLogger(ConfigurationAsCode.class.getName());
|
public static final Logger LOGGER = Logger.getLogger(ConfigurationAsCode.class.getName());
|
||||||
|
|
||||||
public static Node merge(List<YamlSource> configs,
|
public static Node merge(List<YamlSource> sources,
|
||||||
ConfigurationContext context) throws ConfiguratorException {
|
ConfigurationContext context) throws ConfiguratorException {
|
||||||
Node root = null;
|
Node root = null;
|
||||||
for (YamlSource source : configs) {
|
for (YamlSource<?> source : sources) {
|
||||||
try (Reader r = source.read()) {
|
try (Reader reader = reader(source)) {
|
||||||
|
final Node node = read(source, reader, context);
|
||||||
final Node node = read(source, context);
|
|
||||||
|
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
root = node;
|
root = node;
|
||||||
|
|
@ -52,11 +60,11 @@ public final class YamlUtils {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Node read(YamlSource source, ConfigurationContext context) throws IOException {
|
public static Node read(YamlSource source, Reader reader, ConfigurationContext context) throws IOException {
|
||||||
LoaderOptions loaderOptions = new LoaderOptions();
|
LoaderOptions loaderOptions = new LoaderOptions();
|
||||||
loaderOptions.setMaxAliasesForCollections(context.getYamlMaxAliasesForCollections());
|
loaderOptions.setMaxAliasesForCollections(context.getYamlMaxAliasesForCollections());
|
||||||
Composer composer = new Composer(
|
Composer composer = new Composer(
|
||||||
new ParserImpl(new StreamReaderWithSource(source)),
|
new ParserImpl(new StreamReaderWithSource(source, reader)),
|
||||||
new Resolver(),
|
new Resolver(),
|
||||||
loaderOptions);
|
loaderOptions);
|
||||||
try {
|
try {
|
||||||
|
|
@ -72,6 +80,21 @@ public final class YamlUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Reader reader(YamlSource<?> source) throws IOException {
|
||||||
|
Object src = source.source;
|
||||||
|
if (src instanceof String) {
|
||||||
|
final URL url = URI.create((String) src).toURL();
|
||||||
|
return new InputStreamReader(url.openStream(), UTF_8);
|
||||||
|
} else if (src instanceof InputStream) {
|
||||||
|
return new InputStreamReader((InputStream) src, UTF_8);
|
||||||
|
} else if (src instanceof HttpServletRequest) {
|
||||||
|
return new InputStreamReader(((HttpServletRequest) src).getInputStream(), UTF_8);
|
||||||
|
} else if (src instanceof Path) {
|
||||||
|
return Files.newBufferedReader((Path) src);
|
||||||
|
}
|
||||||
|
throw new IOException(String.format("Unknown %s", source));
|
||||||
|
}
|
||||||
|
|
||||||
private static void merge(Node root, Node node, String source) throws ConfiguratorException {
|
private static void merge(Node root, Node node, String source) throws ConfiguratorException {
|
||||||
if (root.getNodeId() != node.getNodeId()) {
|
if (root.getNodeId() != node.getNodeId()) {
|
||||||
// means one of those yaml file doesn't conform to JCasC schema
|
// means one of those yaml file doesn't conform to JCasC schema
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package io.jenkins.plugins.casc.yaml;
|
package io.jenkins.plugins.casc.yaml;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
@ -12,11 +15,11 @@ import static org.junit.Assert.assertEquals;
|
||||||
public class YamlSourceTest {
|
public class YamlSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHaveInformativeToStringForUrlSource() throws MalformedURLException {
|
public void shouldHaveInformativeToStringForUrlSource() throws IOException {
|
||||||
//given
|
//given
|
||||||
String testUrl = "http://example.com/foo/bar";
|
String testUrl = "http://example.com/foo/bar";
|
||||||
//and
|
//and
|
||||||
YamlSource<String> yamlSource = YamlSource.of(new URL(testUrl));
|
YamlSource<String> yamlSource = YamlSource.of(testUrl);
|
||||||
//expect
|
//expect
|
||||||
assertEquals("YamlSource: " + testUrl, yamlSource.toString());
|
assertEquals("YamlSource: " + testUrl, yamlSource.toString());
|
||||||
}
|
}
|
||||||
|
|
@ -31,4 +34,24 @@ public class YamlSourceTest {
|
||||||
//expect
|
//expect
|
||||||
assertEquals("YamlSource: " + testInputStreamToString, yamlSource.toString());
|
assertEquals("YamlSource: " + testInputStreamToString, yamlSource.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHaveInformativeToStringForPathSource() {
|
||||||
|
Path path = new File("./test").toPath();
|
||||||
|
String testPathToString = path.toString();
|
||||||
|
YamlSource<Path> yamlSource = YamlSource.of(path);
|
||||||
|
assertEquals("YamlSource: " + testPathToString, yamlSource.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHaveInformativeToStringForRequestSource() {
|
||||||
|
Request request = new Request(null, null) {
|
||||||
|
@Override
|
||||||
|
public String getPathInfo() {
|
||||||
|
return "/configuration-as-code/check";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
YamlSource<HttpServletRequest> yamlSource = YamlSource.of(request);
|
||||||
|
assertEquals("YamlSource: /configuration-as-code/check", yamlSource.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package io.jenkins.plugins.casc;
|
||||||
|
|
||||||
|
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
|
||||||
|
import com.gargoylesoftware.htmlunit.WebRequest;
|
||||||
|
import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;
|
||||||
|
import io.jenkins.plugins.casc.yaml.YamlSource;
|
||||||
|
import io.jenkins.plugins.casc.yaml.YamlUtils;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import jenkins.model.Jenkins;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static com.gargoylesoftware.htmlunit.HttpMethod.POST;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class YamlReaderTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule();
|
||||||
|
|
||||||
|
@Test(expected = IOException.class)
|
||||||
|
public void unknownReader() throws IOException {
|
||||||
|
YamlUtils.reader(new YamlSource<>(new StringBuilder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void folder() throws Exception {
|
||||||
|
String p = Paths.get(getClass().getResource("./folder").toURI()).toFile().getAbsolutePath();
|
||||||
|
ConfigurationAsCode.get().configure(p);
|
||||||
|
Jenkins jenkins = Jenkins.get();
|
||||||
|
assertEquals("configuration as code - JenkinsConfigTestFolder", jenkins.getSystemMessage());
|
||||||
|
assertEquals(10, jenkins.getQuietPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void httpDoApply() throws Exception {
|
||||||
|
j.jenkins.setCrumbIssuer(null);
|
||||||
|
|
||||||
|
URL apiURL = new URL(MessageFormat.format(
|
||||||
|
"{0}configuration-as-code/apply",
|
||||||
|
j.getURL().toString()));
|
||||||
|
WebRequest request =
|
||||||
|
new WebRequest(apiURL, POST);
|
||||||
|
request.setCharset(StandardCharsets.UTF_8);
|
||||||
|
request.setRequestBody("jenkins:\n"
|
||||||
|
+ " systemMessage: \"configuration as code - JenkinsConfigTestHttpRequest\"\n"
|
||||||
|
+ " quietPeriod: 10");
|
||||||
|
int response = j.createWebClient().getPage(request).getWebResponse().getStatusCode();
|
||||||
|
assertThat(response, is(200));
|
||||||
|
Jenkins jenkins = Jenkins.get();
|
||||||
|
assertEquals("configuration as code - JenkinsConfigTestHttpRequest", jenkins.getSystemMessage());
|
||||||
|
assertEquals(10, jenkins.getQuietPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void httpDoCheck() throws Exception {
|
||||||
|
j.jenkins.setCrumbIssuer(null);
|
||||||
|
|
||||||
|
URL apiURL = new URL(MessageFormat.format(
|
||||||
|
"{0}configuration-as-code/check",
|
||||||
|
j.getURL().toString()));
|
||||||
|
WebRequest request =
|
||||||
|
new WebRequest(apiURL, POST);
|
||||||
|
request.setCharset(StandardCharsets.UTF_8);
|
||||||
|
request.setRequestBody("jenkins:\n"
|
||||||
|
+ " systemMessage: \"configuration as code - JenkinsConfigTestHttpRequest\"\n"
|
||||||
|
+ " quietPeriod: 10");
|
||||||
|
int response = j.createWebClient().getPage(request).getWebResponse().getStatusCode();
|
||||||
|
assertThat(response, is(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FailingHttpStatusCodeException.class)
|
||||||
|
public void httpDoCheckFailure() throws Exception {
|
||||||
|
j.jenkins.setCrumbIssuer(null);
|
||||||
|
|
||||||
|
URL apiURL = new URL(MessageFormat.format(
|
||||||
|
"{0}configuration-as-code/check",
|
||||||
|
j.getURL().toString()));
|
||||||
|
WebRequest request =
|
||||||
|
new WebRequest(apiURL, POST);
|
||||||
|
request.setCharset(StandardCharsets.UTF_8);
|
||||||
|
request.setRequestBody("jenkins:\n"
|
||||||
|
+ " systemMessage: {}");
|
||||||
|
j.createWebClient().getPage(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
jenkins:
|
||||||
|
systemMessage: "configuration as code - JenkinsConfigTestFolder"
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
jenkins:
|
||||||
|
quietPeriod: 10
|
||||||
Loading…
Reference in New Issue