Implement private NPM server (#1337)
* WIP start adding NPM data. * WIP 2 Testing against verdaccio npm * WIP private npm package servers * WIP2 add server type config * WIP3 parse tarball url * Fix package date parsing, start mocking server tests * Add dummy package, assert authorization and content * Add more tests * Add serverType to settings test * Ignore tgz files for bidi check * tighter bidi ignore * different ignore regex * Make packageManagement settings, allow ignoring default package servers * New tests + token authentication * Manage 404s when configured servers don't have package
This commit is contained in:
parent
b62f9afc5a
commit
2654e85df3
|
@ -24,5 +24,7 @@ jobs:
|
|||
- name: Check for bidi characters
|
||||
id: bidi_check
|
||||
uses: HL7/bidi-checker-action@v1.9
|
||||
env:
|
||||
IGNORE: dummy-package.tgz$
|
||||
- name: Get the output time
|
||||
run: echo "The time was ${{ steps.bidi_check.outputs.time }}"
|
||||
|
|
|
@ -157,6 +157,7 @@ public class TerminologyCacheManager {
|
|||
String url = "https://tx.fhir.org/post/tx-cache/"+ghOrg+"/"+ghRepo+"/"+ghBranch+".zip";
|
||||
System.out.println("Sending tx-cache to "+url+" ("+Utilities.describeSize(bs.toByteArray().length)+")");
|
||||
SimpleHTTPClient http = new SimpleHTTPClient();
|
||||
http.setAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC);
|
||||
http.setUsername(token.substring(0, token.indexOf(':')));
|
||||
http.setPassword(token.substring(token.indexOf(':')+1));
|
||||
HTTPResult res = http.put(url, "application/zip", bs.toByteArray(), null); // accept doesn't matter
|
||||
|
|
|
@ -157,6 +157,7 @@ public class TerminologyCacheManager {
|
|||
String url = "https://tx.fhir.org/post/tx-cache/"+ghOrg+"/"+ghRepo+"/"+ghBranch+".zip";
|
||||
System.out.println("Sending tx-cache to "+url+" ("+Utilities.describeSize(bs.toByteArray().length)+")");
|
||||
SimpleHTTPClient http = new SimpleHTTPClient();
|
||||
http.setAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC);
|
||||
http.setUsername(token.substring(0, token.indexOf(':')));
|
||||
http.setPassword(token.substring(token.indexOf(':')+1));
|
||||
HTTPResult res = http.put(url, "application/zip", bs.toByteArray(), null); // accept doesn't matter
|
||||
|
|
|
@ -153,6 +153,13 @@
|
|||
<version>3.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--
|
||||
<dependency>
|
||||
|
|
|
@ -11,12 +11,20 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
|
||||
public class SimpleHTTPClient {
|
||||
|
||||
public class Header {
|
||||
|
||||
public enum AuthenticationMode {
|
||||
NONE,
|
||||
BASIC,
|
||||
TOKEN
|
||||
}
|
||||
|
||||
public class Header {
|
||||
private String name;
|
||||
private String value;
|
||||
public Header(String name, String value) {
|
||||
|
@ -85,34 +93,23 @@ public class SimpleHTTPClient {
|
|||
}
|
||||
|
||||
private List<Header> headers = new ArrayList<>();
|
||||
|
||||
@Getter @Setter
|
||||
private AuthenticationMode authenticationMode;
|
||||
|
||||
@Getter @Setter
|
||||
private String username;
|
||||
|
||||
@Getter @Setter
|
||||
private String password;
|
||||
|
||||
@Getter @Setter
|
||||
private String token;
|
||||
|
||||
public void addHeader(String name, String value) {
|
||||
headers.add(new Header(name, value));
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public HTTPResult get(String url) throws IOException {
|
||||
return get(url, null);
|
||||
}
|
||||
|
@ -170,10 +167,20 @@ public class SimpleHTTPClient {
|
|||
}
|
||||
c.setConnectTimeout(15000);
|
||||
c.setReadTimeout(15000);
|
||||
if (username != null) {
|
||||
setAuthenticationHeader(c);
|
||||
}
|
||||
|
||||
private void setAuthenticationHeader(HttpURLConnection c) {
|
||||
String authHeaderValue = null;
|
||||
if (authenticationMode == AuthenticationMode.TOKEN) {
|
||||
authHeaderValue = "Bearer " + new String(token);
|
||||
} else if (authenticationMode == AuthenticationMode.BASIC) {
|
||||
String auth = username+":"+password;
|
||||
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
|
||||
String authHeaderValue = "Basic " + new String(encodedAuth);
|
||||
authHeaderValue = "Basic " + new String(encodedAuth);
|
||||
}
|
||||
|
||||
if (authHeaderValue != null) {
|
||||
c.setRequestProperty("Authorization", authHeaderValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ public abstract class BasePackageCacheManager implements IPackageCacheManager {
|
|||
if (version.endsWith(".x")) {
|
||||
version = packageClient.getLatestVersion(id, version);
|
||||
}
|
||||
|
||||
InputStream stream = packageClient.fetch(id, version);
|
||||
String url = packageClient.url(id, version);
|
||||
return new InputStreamWithSrc(stream, url, version);
|
||||
|
|
|
@ -14,7 +14,6 @@ import java.text.ParseException;
|
|||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
|
@ -22,7 +21,6 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
@ -37,13 +35,13 @@ import org.hl7.fhir.utilities.json.model.JsonArray;
|
|||
import org.hl7.fhir.utilities.json.model.JsonElement;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.parser.JsonParser;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.FilesystemPackageCacheMode;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.IPackageProvider;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage.NpmPackageFolder;
|
||||
import org.hl7.fhir.utilities.npm.PackageList.PackageListEntry;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/*
|
||||
Copyright (c) 2011+, HL7, Inc.
|
||||
All rights reserved.
|
||||
|
@ -137,10 +135,11 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
|
|||
this.cacheFolder = new File(customFolder);
|
||||
init(FilesystemPackageCacheMode.CUSTOM);
|
||||
}
|
||||
|
||||
|
||||
public void init(FilesystemPackageCacheMode mode) throws IOException {
|
||||
myPackageServers.addAll(PackageServer.publicServers());
|
||||
|
||||
|
||||
|
||||
protected void init(FilesystemPackageCacheMode mode) throws IOException {
|
||||
initPackageServers();
|
||||
|
||||
switch (mode) {
|
||||
case SYSTEM:
|
||||
|
@ -173,6 +172,26 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
|
|||
}
|
||||
}
|
||||
|
||||
private void initPackageServers() {
|
||||
myPackageServers.addAll(getConfiguredServers());
|
||||
if (!isIgnoreDefaultPackageServers()) {
|
||||
myPackageServers.addAll(getDefaultServers());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isIgnoreDefaultPackageServers() {
|
||||
return FhirSettings.isIgnoreDefaultPackageServers();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected List<PackageServer> getDefaultServers() {
|
||||
return PackageServer.defaultServers();
|
||||
}
|
||||
|
||||
protected List<PackageServer> getConfiguredServers() {
|
||||
return PackageServer.getConfiguredServers();
|
||||
}
|
||||
|
||||
public boolean isMinimalMemory() {
|
||||
return minimalMemory;
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ import java.io.ByteArrayInputStream;
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.*;
|
||||
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.SimpleHTTPClient;
|
||||
|
@ -22,10 +21,11 @@ import org.hl7.fhir.utilities.json.model.JsonObject;
|
|||
import org.hl7.fhir.utilities.json.model.JsonProperty;
|
||||
import org.hl7.fhir.utilities.json.parser.JsonParser;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PackageClient {
|
||||
|
||||
|
||||
|
||||
private PackageServer server;
|
||||
private String address;
|
||||
|
||||
|
@ -47,26 +47,34 @@ public class PackageClient {
|
|||
}
|
||||
|
||||
public InputStream fetch(String id, String ver) throws IOException {
|
||||
return fetchCached(Utilities.pathURL(address, id, ver));
|
||||
return fetchCached(getPackageTarballUrl(id, ver));
|
||||
}
|
||||
|
||||
private String getPackageTarballUrl(String id, String ver) throws IOException {
|
||||
if (server.getServerType() == PackageServer.PackageServerType.NPM) {
|
||||
return getNpmServerTarballUrl(id, ver);
|
||||
}
|
||||
return Utilities.pathURL(address, id, ver);
|
||||
}
|
||||
|
||||
private String getNpmServerTarballUrl(String id, String ver) throws IOException {
|
||||
String packageDescriptorUrl = Utilities.pathURL(address, id, ver);
|
||||
JsonObject json;
|
||||
|
||||
json = fetchJson(packageDescriptorUrl);
|
||||
JsonObject dist = json.getJsonObject("dist");
|
||||
return dist.getJsonString("tarball").asString();
|
||||
|
||||
}
|
||||
|
||||
public InputStream fetch(PackageInfo info) throws IOException {
|
||||
return fetchCached(Utilities.pathURL(address, info.getId(), info.getVersion()));
|
||||
}
|
||||
|
||||
public InputStream fetchNpm(String id, String ver) throws IOException {
|
||||
return fetchCached(Utilities.pathURL(address, id, "-", id+"-"+ver+".tgz"));
|
||||
return fetchCached(getPackageTarballUrl(info.getId(), info.getVersion()));
|
||||
}
|
||||
|
||||
public InputStream fetchCached(String url) throws IOException, FileNotFoundException {
|
||||
return fetchUrl(url, null);
|
||||
}
|
||||
|
||||
protected String fn(String url) {
|
||||
String[] p = url.split("\\/");
|
||||
return p[2]+"-"+p[p.length-2]+"-"+p[p.length-1]+".tgz";
|
||||
}
|
||||
|
||||
public List<PackageInfo> getVersions(String id) throws IOException {
|
||||
String url = Utilities.pathURL(address, id);
|
||||
List<PackageInfo> res = new ArrayList<>();
|
||||
|
@ -78,7 +86,7 @@ public class PackageClient {
|
|||
if (versions != null) {
|
||||
for (JsonProperty v : versions.getProperties()) {
|
||||
JsonObject obj = versions.getJsonObject(v.getName());
|
||||
Instant d = obj.hasString("date") ? obj.asDate("date") : null;
|
||||
Instant d = getInstantFromPackageDate(obj);
|
||||
if (d == null) {
|
||||
hasDates = false;
|
||||
}
|
||||
|
@ -101,7 +109,21 @@ public class PackageClient {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static Instant getInstantFromPackageDate(JsonObject obj) {
|
||||
try {
|
||||
return obj.hasString("date") ? obj.asDate("date") : null;
|
||||
} catch (DateTimeParseException e) {
|
||||
//FIXME Some IGs use an older date format:
|
||||
try {
|
||||
return new SimpleDateFormat("yyyyMMddhhmmss").parse(obj.getJsonString("date").asString()).toInstant();
|
||||
} catch (ParseException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<PackageInfo> search(String name, String canonical, String fhirVersion, boolean preRelease) throws IOException {
|
||||
CommaSeparatedStringBuilder params = new CommaSeparatedStringBuilder("&");
|
||||
if (!Utilities.noString(name)) {
|
||||
|
@ -148,12 +170,22 @@ public class PackageClient {
|
|||
}
|
||||
|
||||
private InputStream fetchUrl(String source, String accept) throws IOException {
|
||||
SimpleHTTPClient http = new SimpleHTTPClient();
|
||||
SimpleHTTPClient http = getSimpleHTTPClient();
|
||||
HTTPResult res = http.get(source, accept);
|
||||
res.checkThrowException();
|
||||
return new ByteArrayInputStream(res.getContent());
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
private SimpleHTTPClient getSimpleHTTPClient() {
|
||||
SimpleHTTPClient client = new SimpleHTTPClient();
|
||||
client.setAuthenticationMode(server.getAuthenticationMode());
|
||||
client.setUsername(server.getUsername());
|
||||
client.setPassword(server.getPassword());
|
||||
client.setToken(server.getToken());
|
||||
return client;
|
||||
}
|
||||
|
||||
private JsonObject fetchJson(String source) throws IOException {
|
||||
String src = TextFile.streamToString(fetchUrl(source, "application/json"));
|
||||
//System.out.println(src);
|
||||
|
|
|
@ -1,36 +1,48 @@
|
|||
package org.hl7.fhir.utilities.npm;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.hl7.fhir.utilities.SimpleHTTPClient;
|
||||
import org.hl7.fhir.utilities.settings.FhirSettings;
|
||||
import org.hl7.fhir.utilities.settings.PackageServerPOJO;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PackageServer {
|
||||
|
||||
public enum PackageServerAuthenticationMode {
|
||||
NONE
|
||||
|
||||
public enum PackageServerType {
|
||||
FHIR,
|
||||
NPM
|
||||
}
|
||||
|
||||
|
||||
public PackageServer(String url) {
|
||||
this.url = url;
|
||||
mode = PackageServerAuthenticationMode.NONE;
|
||||
authenticationMode = SimpleHTTPClient.AuthenticationMode.NONE;
|
||||
serverType = PackageServerType.FHIR;
|
||||
}
|
||||
|
||||
private String url;
|
||||
private PackageServerAuthenticationMode mode;
|
||||
|
||||
@Getter
|
||||
private SimpleHTTPClient.AuthenticationMode authenticationMode;
|
||||
|
||||
@Getter
|
||||
private PackageServerType serverType;
|
||||
|
||||
@Getter
|
||||
private String username;
|
||||
|
||||
@Getter
|
||||
private String password;
|
||||
|
||||
@Getter
|
||||
private String token;
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
public PackageServerAuthenticationMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static final String PRIMARY_SERVER = "http://packages.fhir.org";
|
||||
public static final String SECONDARY_SERVER = "https://packages2.fhir.org/packages";
|
||||
|
@ -43,15 +55,79 @@ public class PackageServer {
|
|||
return new PackageServer(SECONDARY_SERVER);
|
||||
}
|
||||
|
||||
public static List<PackageServer> publicServers() {
|
||||
public static List<PackageServer> defaultServers() {
|
||||
List<PackageServer> servers = new ArrayList<>();
|
||||
servers.add(primaryServer());
|
||||
servers.add(secondaryServer());
|
||||
return servers;
|
||||
}
|
||||
|
||||
public static PackageServer getPackageServerFromPOJO(PackageServerPOJO pojo) {
|
||||
return new PackageServer(pojo.getUrl())
|
||||
.withAuthenticationMode(getModeFromPOJO(pojo))
|
||||
.withServerType(
|
||||
pojo.getServerType() != null && pojo.getServerType().equalsIgnoreCase("npm") ? PackageServerType.NPM : PackageServerType.FHIR
|
||||
)
|
||||
.withUsername(pojo.getUsername())
|
||||
.withPassword(pojo.getPassword())
|
||||
.withToken(pojo.getToken());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static SimpleHTTPClient.AuthenticationMode getModeFromPOJO(PackageServerPOJO pojo) {
|
||||
if (pojo.getAuthenticationType().equalsIgnoreCase("basic")) return SimpleHTTPClient.AuthenticationMode.BASIC;
|
||||
if (pojo.getAuthenticationType().equalsIgnoreCase("token")) return SimpleHTTPClient.AuthenticationMode.TOKEN;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<PackageServer> getConfiguredServers() {
|
||||
return FhirSettings.getPackageServers().stream().map(
|
||||
PackageServer::getPackageServerFromPOJO
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
public PackageServer copy() {
|
||||
PackageServer packageServer = new PackageServer(url);
|
||||
packageServer.authenticationMode = this.authenticationMode;
|
||||
packageServer.serverType = this.serverType;
|
||||
packageServer.username = this.username;
|
||||
packageServer.password = this.password;
|
||||
packageServer.token = this.token;
|
||||
return packageServer;
|
||||
}
|
||||
|
||||
public PackageServer withAuthenticationMode( SimpleHTTPClient.AuthenticationMode mode) {
|
||||
PackageServer packageServer = this.copy();
|
||||
packageServer.authenticationMode = mode;
|
||||
return packageServer;
|
||||
}
|
||||
|
||||
public PackageServer withServerType(PackageServerType serverType) {
|
||||
PackageServer packageServer = this.copy();
|
||||
packageServer.serverType = serverType;
|
||||
return packageServer;
|
||||
}
|
||||
|
||||
public PackageServer withPassword(String password) {
|
||||
PackageServer packageServer = this.copy();
|
||||
packageServer.password = password;
|
||||
return packageServer;
|
||||
}
|
||||
|
||||
public PackageServer withUsername(String username) {
|
||||
PackageServer packageServer = this.copy();
|
||||
packageServer.username = username;
|
||||
return packageServer;
|
||||
}
|
||||
|
||||
public PackageServer withToken(String token) {
|
||||
PackageServer packageServer = this.copy();
|
||||
packageServer.token = token;
|
||||
return packageServer;
|
||||
}
|
||||
}
|
|
@ -2,13 +2,14 @@ package org.hl7.fhir.utilities.settings;
|
|||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Getter;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class FhirSettings {
|
||||
|
@ -210,4 +211,19 @@ public class FhirSettings {
|
|||
return Utilities.path(System.getProperty("user.home"), ".fhir", "fhir-settings.json");
|
||||
}
|
||||
|
||||
public static boolean isIgnoreDefaultPackageServers() {
|
||||
getInstance();
|
||||
if (instance.fhirSettings.getPackageManagement() == null || instance.fhirSettings.getPackageManagement().getIgnoreDefaultServers() == null) {
|
||||
return false;
|
||||
}
|
||||
return instance.fhirSettings.getPackageManagement().getIgnoreDefaultServers();
|
||||
}
|
||||
|
||||
public static List<PackageServerPOJO> getPackageServers() {
|
||||
getInstance();
|
||||
if (instance.fhirSettings.getPackageManagement() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return List.of(instance.fhirSettings.getPackageManagement().getServers().toArray(new PackageServerPOJO[]{}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import lombok.Builder;
|
|||
import lombok.Data;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
|
@ -39,6 +41,8 @@ public class FhirSettingsPOJO {
|
|||
private String txFhirDevelopment;
|
||||
private String txFhirLocal;
|
||||
|
||||
private PackageManagementPOJO packageManagement;
|
||||
|
||||
protected FhirSettingsPOJO() {
|
||||
apiKeys = null;
|
||||
npmPath = null;
|
||||
|
@ -50,5 +54,7 @@ public class FhirSettingsPOJO {
|
|||
txFhirProduction = TX_SERVER_PROD;
|
||||
txFhirDevelopment = TX_SERVER_DEV;
|
||||
txFhirLocal = TX_SERVER_LOCAL;
|
||||
|
||||
packageManagement = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package org.hl7.fhir.utilities.settings;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@AllArgsConstructor
|
||||
public class PackageManagementPOJO {
|
||||
|
||||
private Boolean ignoreDefaultServers;
|
||||
|
||||
private List<PackageServerPOJO> servers;
|
||||
|
||||
protected PackageManagementPOJO() {
|
||||
ignoreDefaultServers = false;
|
||||
servers = new ArrayList<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.hl7.fhir.utilities.settings;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@AllArgsConstructor
|
||||
public class PackageServerPOJO {
|
||||
|
||||
String url;
|
||||
|
||||
String authenticationType;
|
||||
|
||||
String serverType;
|
||||
|
||||
String username;
|
||||
|
||||
String password;
|
||||
|
||||
String token;
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package org.hl7.fhir.utilities.npm;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.SimpleHTTPClient;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class BasePackageCacheManagerTests {
|
||||
|
||||
@Test
|
||||
public void testPackageBasicAuth() throws IOException {
|
||||
BasePackageCacheManager basePackageCacheManager = getFakeBasePackageCacheManager();
|
||||
|
||||
MockPackageServer server = new MockPackageServer();
|
||||
String packageServerUrl = server.getPackageServerUrl();
|
||||
|
||||
server.enqueueDummyPackageDescription();
|
||||
server.enqueueDummyPackage();
|
||||
|
||||
PackageServer testServer = new PackageServer(packageServerUrl)
|
||||
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
|
||||
.withServerType(PackageServer.PackageServerType.NPM)
|
||||
.withUsername(MockPackageServer.DUMMY_USERNAME)
|
||||
.withPassword(MockPackageServer.DUMMY_PASSWORD);
|
||||
|
||||
basePackageCacheManager.addPackageServer(testServer);
|
||||
basePackageCacheManager.myPackageServers.addAll(PackageServer.defaultServers());
|
||||
|
||||
BasePackageCacheManager.InputStreamWithSrc inputWithSrc = basePackageCacheManager.loadFromPackageServer("example.fhir.uv.myig", "0.2.0");
|
||||
|
||||
assertCorrectPackageContent(inputWithSrc);
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test that package management moves to next server after 404")
|
||||
public void testPackageWithConfiguredServer404() throws IOException {
|
||||
BasePackageCacheManager basePackageCacheManager = getFakeBasePackageCacheManager();
|
||||
|
||||
MockPackageServer serverA = new MockPackageServer();
|
||||
serverA.enqueueResponseCode(404);
|
||||
|
||||
MockPackageServer serverB = new MockPackageServer();
|
||||
|
||||
serverB.enqueueDummyPackageDescription();
|
||||
serverB.enqueueDummyPackage();
|
||||
|
||||
String packageServerAUrl = serverA.getPackageServerUrl();
|
||||
String packageServerBUrl = serverB.getPackageServerUrl();
|
||||
|
||||
PackageServer testServerA = new PackageServer(packageServerAUrl)
|
||||
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
|
||||
.withServerType(PackageServer.PackageServerType.NPM);
|
||||
|
||||
PackageServer testServerB = new PackageServer(packageServerBUrl)
|
||||
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
|
||||
.withServerType(PackageServer.PackageServerType.NPM);
|
||||
|
||||
basePackageCacheManager.addPackageServer(testServerA);
|
||||
basePackageCacheManager.addPackageServer(testServerB);
|
||||
basePackageCacheManager.myPackageServers.addAll(PackageServer.defaultServers());
|
||||
|
||||
BasePackageCacheManager.InputStreamWithSrc inputWithSrc = basePackageCacheManager.loadFromPackageServer("example.fhir.uv.myig", "0.2.0");
|
||||
|
||||
assertCorrectPackageContent(inputWithSrc);
|
||||
serverA.shutdown();
|
||||
serverB.shutdown();
|
||||
}
|
||||
|
||||
private static void assertCorrectPackageContent(BasePackageCacheManager.InputStreamWithSrc inputWithSrc) throws IOException {
|
||||
NpmPackage npmPackage = NpmPackage.fromPackage(inputWithSrc.stream, inputWithSrc.url, false);
|
||||
|
||||
assertEquals("Dummy IG For Testing", npmPackage.title())
|
||||
;
|
||||
assertEquals("Dummy IG description (built Thu, Jul 6, 2023 15:16-0400-04:00)", npmPackage.description());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static BasePackageCacheManager getFakeBasePackageCacheManager() {
|
||||
return new BasePackageCacheManager() {
|
||||
@Override
|
||||
public NpmPackage loadPackageFromCacheOnly(String id, @Nullable String version) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NpmPackage addPackageToCache(String id, String version, InputStream packageTgzInputStream, String sourceDesc) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NpmPackage loadPackage(String id, String version) throws FHIRException, IOException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package org.hl7.fhir.utilities.npm;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class FilesystemPackageManagerTests {
|
||||
|
||||
private static final String DUMMY_URL_1 = "http://dummy1.org";
|
||||
private static final String DUMMY_URL_2 = "http://dummy2.org";
|
||||
|
||||
private static final String DUMMY_URL_3 = "http://dummy3.org";
|
||||
|
||||
private static final String DUMMY_URL_4 = "http://dummy4.org";
|
||||
private List<PackageServer> dummyPrivateServers = List.of(
|
||||
new PackageServer(DUMMY_URL_1),
|
||||
new PackageServer(DUMMY_URL_2)
|
||||
);
|
||||
|
||||
private List<PackageServer> dummyDefaultServers = List.of(
|
||||
new PackageServer(DUMMY_URL_3),
|
||||
new PackageServer(DUMMY_URL_4)
|
||||
);
|
||||
|
||||
@Test
|
||||
public void testDefaultServers() throws IOException {
|
||||
FilesystemPackageCacheManager filesystemPackageCacheManager = getFilesystemPackageCacheManager(false);
|
||||
|
||||
assertEquals(4, filesystemPackageCacheManager.myPackageServers.size());
|
||||
assertEquals(DUMMY_URL_1, filesystemPackageCacheManager.myPackageServers.get(0).getUrl());
|
||||
assertEquals(DUMMY_URL_2, filesystemPackageCacheManager.myPackageServers.get(1).getUrl());
|
||||
assertEquals(DUMMY_URL_3, filesystemPackageCacheManager.myPackageServers.get(2).getUrl());
|
||||
assertEquals(DUMMY_URL_4, filesystemPackageCacheManager.myPackageServers.get(3).getUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreDefaultServers() throws IOException {
|
||||
FilesystemPackageCacheManager filesystemPackageCacheManager = getFilesystemPackageCacheManager(true);
|
||||
|
||||
assertEquals(2, filesystemPackageCacheManager.myPackageServers.size());
|
||||
assertEquals(DUMMY_URL_1, filesystemPackageCacheManager.myPackageServers.get(0).getUrl());
|
||||
assertEquals(DUMMY_URL_2, filesystemPackageCacheManager.myPackageServers.get(1).getUrl());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private FilesystemPackageCacheManager getFilesystemPackageCacheManager(final boolean ignoreDefaultPackageServers) throws IOException {
|
||||
return new FilesystemPackageCacheManager(FilesystemPackageCacheManager.FilesystemPackageCacheMode.TESTING) {
|
||||
protected boolean isIgnoreDefaultPackageServers() {
|
||||
return ignoreDefaultPackageServers;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
protected List<PackageServer> getDefaultServers() {
|
||||
return dummyDefaultServers;
|
||||
}
|
||||
|
||||
protected List<PackageServer> getConfiguredServers() {
|
||||
return dummyPrivateServers;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package org.hl7.fhir.utilities.npm;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okio.Buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MockPackageServer {
|
||||
public static final String DUMMY_PACKAGE_NAME = "example.fhir.uv.myig";
|
||||
public static final String DUMMY_PACKAGE_VERSION = "0.2.0";
|
||||
public static final String DUMMY_USERNAME = "alfred";
|
||||
public static final String DUMMY_PASSWORD = "numan";
|
||||
|
||||
public static final String DUMMY_TOKEN = "dummyTokenValue";
|
||||
MockWebServer server = new MockWebServer();
|
||||
HttpUrl httpUrl;
|
||||
|
||||
public MockPackageServer() throws IOException {
|
||||
server = new MockWebServer();
|
||||
server.start();
|
||||
httpUrl = server.url("/");
|
||||
}
|
||||
|
||||
public MockWebServer getMockWebServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public String getPackageServerUrl() {
|
||||
return "http://" + httpUrl.host() + ":" + httpUrl.port();
|
||||
}
|
||||
|
||||
public void shutdown() throws IOException {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
public void enqueueDummyPackageDescription() {
|
||||
server.enqueue(new MockResponse().setBody("{" +
|
||||
"\"dist\": { \"tarball\": \"" + getTarballUrl() + "\"}"+
|
||||
"}"));
|
||||
}
|
||||
|
||||
public String getTarballUrl() {
|
||||
return getPackageServerUrl() + "/tarballUrl";
|
||||
}
|
||||
public void enqueueDummyPackage() throws IOException {
|
||||
server.enqueue(new MockResponse().setBody(getDummyPackageAsBuffer()));
|
||||
}
|
||||
|
||||
public void enqueueResponseCode(int code) throws IOException {
|
||||
server.enqueue(new MockResponse().setResponseCode(code));
|
||||
}
|
||||
|
||||
private Buffer getDummyPackageAsBuffer() throws IOException {
|
||||
|
||||
byte[] fileData = this.getClass().getResourceAsStream("/npm/dummy-package.tgz").readAllBytes();
|
||||
Buffer buf = new Buffer();
|
||||
buf.write(fileData);
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package org.hl7.fhir.utilities.npm;
|
||||
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import okio.Buffer;
|
||||
|
||||
import org.hl7.fhir.utilities.SimpleHTTPClient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.StringStartsWith.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
public class PackageServerTest {
|
||||
|
||||
MockPackageServer server;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws IOException {
|
||||
server = new MockPackageServer();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void afterEach() throws IOException {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageServerBasicAuth() throws IOException, InterruptedException {
|
||||
|
||||
String packageServerUrl = server.getPackageServerUrl();
|
||||
|
||||
server.enqueueDummyPackageDescription();
|
||||
server.enqueueDummyPackage();
|
||||
|
||||
PackageServer testServer = new PackageServer(packageServerUrl)
|
||||
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
|
||||
.withServerType(PackageServer.PackageServerType.NPM)
|
||||
.withUsername(MockPackageServer.DUMMY_USERNAME)
|
||||
.withPassword(MockPackageServer.DUMMY_PASSWORD);
|
||||
PackageClient packageClient = new PackageClient(testServer);
|
||||
|
||||
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
|
||||
|
||||
RecordedRequest packageRequest = server.getMockWebServer().takeRequest();
|
||||
|
||||
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
|
||||
assertBasicAuthorization(packageRequest);
|
||||
|
||||
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
|
||||
|
||||
assertEquals(server.getTarballUrl(), tarballRequest.getRequestUrl().toString());
|
||||
assertBasicAuthorization(tarballRequest);
|
||||
|
||||
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
|
||||
|
||||
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
|
||||
assertDummyPackageContent(npmPackage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageServerTokenAuth() throws IOException, InterruptedException {
|
||||
|
||||
String packageServerUrl = server.getPackageServerUrl();
|
||||
|
||||
server.enqueueDummyPackageDescription();
|
||||
server.enqueueDummyPackage();
|
||||
|
||||
PackageServer testServer = new PackageServer(packageServerUrl)
|
||||
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.TOKEN)
|
||||
.withServerType(PackageServer.PackageServerType.NPM)
|
||||
.withToken(MockPackageServer.DUMMY_TOKEN);
|
||||
PackageClient packageClient = new PackageClient(testServer);
|
||||
|
||||
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
|
||||
|
||||
RecordedRequest packageRequest = server.getMockWebServer().takeRequest();
|
||||
|
||||
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
|
||||
assertTokenAuthorization(packageRequest);
|
||||
|
||||
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
|
||||
|
||||
assertEquals(server.getTarballUrl(), tarballRequest.getRequestUrl().toString());
|
||||
assertTokenAuthorization(tarballRequest);
|
||||
|
||||
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
|
||||
|
||||
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
|
||||
assertDummyPackageContent(npmPackage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageNpmServer() throws IOException, InterruptedException {
|
||||
|
||||
String packageServerUrl = server.getPackageServerUrl();
|
||||
|
||||
server.enqueueDummyPackageDescription();
|
||||
server.enqueueDummyPackage();
|
||||
|
||||
PackageServer testServer = new PackageServer(packageServerUrl)
|
||||
.withServerType(PackageServer.PackageServerType.NPM);
|
||||
PackageClient packageClient = new PackageClient(testServer);
|
||||
|
||||
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
|
||||
|
||||
RecordedRequest packageRequest = server.getMockWebServer().takeRequest();
|
||||
|
||||
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
|
||||
assertNull(packageRequest.getHeader("Authorization"));
|
||||
|
||||
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
|
||||
|
||||
assertEquals(server.getTarballUrl(), tarballRequest.getRequestUrl().toString());
|
||||
assertNull(tarballRequest.getHeader("Authorization"));
|
||||
|
||||
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
|
||||
|
||||
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
|
||||
assertDummyPackageContent(npmPackage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageFhirServer() throws IOException, InterruptedException {
|
||||
|
||||
String packageServerUrl = server.getPackageServerUrl();
|
||||
server.enqueueDummyPackage();
|
||||
|
||||
PackageServer testServer = new PackageServer(packageServerUrl);
|
||||
|
||||
PackageClient packageClient = new PackageClient(testServer);
|
||||
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
|
||||
|
||||
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
|
||||
|
||||
assertEquals(server.getPackageServerUrl() + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, tarballRequest.getRequestUrl().toString());
|
||||
assertNull(tarballRequest.getHeader("Authorization"));
|
||||
|
||||
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
|
||||
assertDummyPackageContent(npmPackage);
|
||||
}
|
||||
private static void assertBasicAuthorization(RecordedRequest packageRequest) {
|
||||
String authorizationHeader = packageRequest.getHeader("Authorization");
|
||||
|
||||
assertThat(authorizationHeader, startsWith("Basic"));
|
||||
byte[] data = Base64.getDecoder().decode(authorizationHeader.substring(6));
|
||||
String authorizationValue = new String(data, StandardCharsets.UTF_8);
|
||||
String[] authorizationColumns = authorizationValue.split(":");
|
||||
|
||||
assertEquals(MockPackageServer.DUMMY_USERNAME, authorizationColumns[0]);
|
||||
assertEquals(MockPackageServer.DUMMY_PASSWORD, authorizationColumns[1]);
|
||||
}
|
||||
|
||||
private static void assertTokenAuthorization(RecordedRequest packageRequest) {
|
||||
String authorizationHeader = packageRequest.getHeader("Authorization");
|
||||
|
||||
assertThat(authorizationHeader, startsWith("Bearer"));
|
||||
String token = authorizationHeader.substring(7);
|
||||
|
||||
assertEquals(MockPackageServer.DUMMY_TOKEN, token);
|
||||
}
|
||||
|
||||
private void assertDummyPackageContent(NpmPackage npmPackage) throws IOException {
|
||||
assertEquals("Dummy IG For Testing", npmPackage.title())
|
||||
;
|
||||
assertEquals("Dummy IG description (built Thu, Jul 6, 2023 15:16-0400-04:00)", npmPackage.description());
|
||||
}
|
||||
|
||||
|
||||
public Buffer getDummyPackageAsBuffer() throws IOException {
|
||||
|
||||
byte[] fileData = this.getClass().getResourceAsStream("/npm/dummy-package.tgz").readAllBytes();
|
||||
Buffer buf = new Buffer();
|
||||
buf.write(fileData);
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import org.junit.jupiter.api.parallel.Isolated;
|
|||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -83,5 +84,20 @@ public class FhirSettingsTests implements ResourceLoaderTests {
|
|||
assertEquals("dummy-diff-tool-path", fhirSettings.getDiffToolPath());
|
||||
assertEquals("dummy-temp-path", fhirSettings.getTempPath());
|
||||
assertEquals("dummy-test-igs-path", fhirSettings.getTestIgsPath());
|
||||
|
||||
assertTrue(fhirSettings.getPackageManagement().getIgnoreDefaultServers());
|
||||
|
||||
List<PackageServerPOJO> packageServers = fhirSettings.getPackageManagement().getServers();
|
||||
|
||||
assertEquals(2, packageServers.size());
|
||||
|
||||
assertEquals("http://dummy.org", packageServers.get(0).url);
|
||||
assertEquals("npm", packageServers.get(0).serverType);
|
||||
assertEquals("joe", packageServers.get(0).username);
|
||||
assertEquals("swordfish", packageServers.get(0).password);
|
||||
assertEquals("BASIC", packageServers.get(0).authenticationType);
|
||||
|
||||
assertEquals("http://dummy2.com", packageServers.get(1).url);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -9,6 +9,22 @@
|
|||
"tempPath": "dummy-temp-path",
|
||||
"testIgsPath": "dummy-test-igs-path",
|
||||
"unusedField" : "unused",
|
||||
"packageManagement" : {
|
||||
"ignoreDefaultServers" : true,
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://dummy.org",
|
||||
"serverType": "npm",
|
||||
"authenticationType": "BASIC",
|
||||
"username": "joe",
|
||||
"password": "swordfish"
|
||||
},
|
||||
{
|
||||
"url": "http://dummy2.com",
|
||||
"goobledy-goo": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
"unusedData" : {
|
||||
"unusedDateField" : "unused-data"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue