diff --git a/deployment/helm/science-portal/Chart.yaml b/deployment/helm/science-portal/Chart.yaml
index d6ea9b0c..901cb50c 100644
--- a/deployment/helm/science-portal/Chart.yaml
+++ b/deployment/helm/science-portal/Chart.yaml
@@ -21,7 +21,7 @@ version: 0.3.1
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
-appVersion: "0.3.0"
+appVersion: "0.3.1"
dependencies:
- name: "redis"
diff --git a/deployment/helm/science-portal/values.yaml b/deployment/helm/science-portal/values.yaml
index 6f69ec15..9248b648 100644
--- a/deployment/helm/science-portal/values.yaml
+++ b/deployment/helm/science-portal/values.yaml
@@ -11,7 +11,7 @@ skaha:
deployment:
hostname: example.host.com # Change this!
sciencePortal:
- image: images.opencadc.org/platform/science-portal:0.3.0
+ image: images.opencadc.org/platform/science-portal:0.3.1
imagePullPolicy: Always
tabLabels:
diff --git a/deployment/helm/skaha/Chart.yaml b/deployment/helm/skaha/Chart.yaml
index c992b63b..13188e6f 100644
--- a/deployment/helm/skaha/Chart.yaml
+++ b/deployment/helm/skaha/Chart.yaml
@@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.9.0
+version: 0.9.1
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
-appVersion: "0.23.0"
+appVersion: "0.23.1"
dependencies:
- name: "redis"
diff --git a/deployment/helm/skaha/values.yaml b/deployment/helm/skaha/values.yaml
index bcf4c712..6c83afb0 100644
--- a/deployment/helm/skaha/values.yaml
+++ b/deployment/helm/skaha/values.yaml
@@ -17,7 +17,7 @@ skahaWorkload:
deployment:
hostname: myhost.example.com # Change this!
skaha:
- image: images.opencadc.org/platform/skaha:0.23.0
+ image: images.opencadc.org/platform/skaha:0.23.1
imagePullPolicy: Always
# Cron string for the image caching cron job schedule. Defaults to every minute.
diff --git a/skaha/VERSION b/skaha/VERSION
index 64f76ae5..7592d8bb 100644
--- a/skaha/VERSION
+++ b/skaha/VERSION
@@ -1,6 +1,6 @@
## deployable containers have a semantic and build tag
# version tag: major.minor.patch
# build version tag: timestamp
-VER=0.23.0
+VER=0.23.1
TAGS="${VER} ${VER}-$(date -u +"%Y%m%dT%H%M%S")"
unset VER
diff --git a/skaha/src/main/java/org/opencadc/skaha/SkahaAction.java b/skaha/src/main/java/org/opencadc/skaha/SkahaAction.java
index 17659e62..d0da3bb8 100644
--- a/skaha/src/main/java/org/opencadc/skaha/SkahaAction.java
+++ b/skaha/src/main/java/org/opencadc/skaha/SkahaAction.java
@@ -105,7 +105,7 @@
import org.opencadc.permissions.TokenTool;
import org.opencadc.permissions.WriteGrant;
import org.opencadc.skaha.image.Image;
-import org.opencadc.skaha.registry.ImageRegistryAuth;
+import org.opencadc.skaha.repository.ImageRepositoryAuth;
import org.opencadc.skaha.session.Session;
import org.opencadc.skaha.session.SessionDAO;
import org.opencadc.skaha.utils.CommandExecutioner;
@@ -280,13 +280,13 @@ protected void initRequest() throws Exception {
}
}
- protected ImageRegistryAuth getRegistryAuth(final String registryHost) {
+ protected ImageRepositoryAuth getRegistryAuth(final String registryHost) {
final String registryAuthValue = this.syncInput.getHeader(SkahaAction.X_REGISTRY_AUTH_HEADER);
if (!StringUtil.hasText(registryAuthValue)) {
throw new IllegalArgumentException("No authentication provided for unknown or private image. Use "
+ SkahaAction.X_REGISTRY_AUTH_HEADER + " request header with base64Encode(username:secret).");
}
- return ImageRegistryAuth.fromEncoded(registryAuthValue, registryHost);
+ return ImageRepositoryAuth.fromEncoded(registryAuthValue, registryHost);
}
private boolean isSkahaCallBackFlow(Subject currentSubject) {
diff --git a/skaha/src/main/java/org/opencadc/skaha/context/GetAction.java b/skaha/src/main/java/org/opencadc/skaha/context/GetAction.java
index 123f2f6b..c82749d4 100644
--- a/skaha/src/main/java/org/opencadc/skaha/context/GetAction.java
+++ b/skaha/src/main/java/org/opencadc/skaha/context/GetAction.java
@@ -88,7 +88,7 @@ public void doAction() throws Exception {
super.initRequest();
File propertiesFile = ResourceContexts.getResourcesFile("k8s-resources.json");
- byte[] bytes = getBytes(propertiesFile);
+ byte[] bytes = GetAction.getBytes(propertiesFile);
syncOutput.setHeader("Content-Type", "application/json");
syncOutput.getOutputStream().write(bytes);
}
diff --git a/skaha/src/main/java/org/opencadc/skaha/repository/GetAction.java b/skaha/src/main/java/org/opencadc/skaha/repository/GetAction.java
new file mode 100644
index 00000000..a13c9951
--- /dev/null
+++ b/skaha/src/main/java/org/opencadc/skaha/repository/GetAction.java
@@ -0,0 +1,43 @@
+package org.opencadc.skaha.repository;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import org.json.JSONWriter;
+import org.opencadc.skaha.SkahaAction;
+
+/**
+ * Output the resource context information.
+ *
+ * @author majorb
+ */
+public class GetAction extends SkahaAction {
+
+ @Override
+ public void doAction() throws Exception {
+ initRequest();
+ final Writer writer = initWriter();
+ final JSONWriter jsonWriter = new JSONWriter(writer).array();
+ try {
+ Arrays.stream(getHarborHosts()).forEach(jsonWriter::value);
+ } finally {
+ jsonWriter.endArray();
+ writer.flush();
+ }
+ }
+
+ @Override
+ protected void initRequest() throws Exception {
+ super.initRequest();
+ }
+
+ Writer initWriter() throws IOException {
+ this.syncOutput.setHeader("content-type", "application/json");
+ return new OutputStreamWriter(syncOutput.getOutputStream());
+ }
+
+ String[] getHarborHosts() {
+ return this.harborHosts.toArray(new String[0]);
+ }
+}
diff --git a/skaha/src/main/java/org/opencadc/skaha/registry/ImageRegistryAuth.java b/skaha/src/main/java/org/opencadc/skaha/repository/ImageRepositoryAuth.java
similarity index 60%
rename from skaha/src/main/java/org/opencadc/skaha/registry/ImageRegistryAuth.java
rename to skaha/src/main/java/org/opencadc/skaha/repository/ImageRepositoryAuth.java
index fba295c2..cfd09d16 100644
--- a/skaha/src/main/java/org/opencadc/skaha/registry/ImageRegistryAuth.java
+++ b/skaha/src/main/java/org/opencadc/skaha/repository/ImageRepositoryAuth.java
@@ -1,26 +1,26 @@
-package org.opencadc.skaha.registry;
+package org.opencadc.skaha.repository;
import ca.nrc.cadc.util.Base64;
import ca.nrc.cadc.util.StringUtil;
import java.nio.charset.StandardCharsets;
/**
- * Represents credentials to an image registry. Will be used from the request input as a base64 encoded value.
+ * Represents credentials to an image repository. Will be used from the request input as a base64 encoded value.
*/
-public class ImageRegistryAuth {
+public class ImageRepositoryAuth {
private static final String VALUE_DELIMITER = ":";
private final String host;
private final String username;
private final byte[] secret;
- ImageRegistryAuth(final String username, final byte[] secret, final String host) {
+ ImageRepositoryAuth(final String username, final byte[] secret, final String host) {
if (!StringUtil.hasText(username)) {
throw new IllegalArgumentException("username is required.");
} else if (secret.length == 0) {
throw new IllegalArgumentException("secret value cannot be empty.");
} else if (!StringUtil.hasText(host)) {
- throw new IllegalArgumentException("registry host is required.");
+ throw new IllegalArgumentException("repository host is required.");
}
this.username = username;
@@ -29,31 +29,31 @@ public class ImageRegistryAuth {
}
/**
- * Constructor to use the Base64 encoded value to obtain the credentials for an Image Registry.
+ * Constructor to use the Base64 encoded value to obtain the credentials for an Image Repository.
*
* @param encodedValue The Base64 encoded String. Never null.
- * @param host The registry host for these credentials.
- * @return ImageRegistryAuth instance. Never null.
+ * @param host The repository host for these credentials.
+ * @return ImageRepositoryAuth instance. Never null.
*/
- public static ImageRegistryAuth fromEncoded(final String encodedValue, final String host) {
+ public static ImageRepositoryAuth fromEncoded(final String encodedValue, final String host) {
if (!StringUtil.hasText(encodedValue)) {
throw new IllegalArgumentException("Encoded auth username and key is required.");
} else if (!StringUtil.hasText(host)) {
- throw new IllegalArgumentException("Registry host is required.");
+ throw new IllegalArgumentException("Repository host is required.");
}
final String decodedValue = new String(Base64.decode(encodedValue), StandardCharsets.UTF_8);
- final String[] values = decodedValue.split(ImageRegistryAuth.VALUE_DELIMITER);
+ final String[] values = decodedValue.split(ImageRepositoryAuth.VALUE_DELIMITER);
if (values.length != 2) {
throw new IllegalArgumentException("Invalid input. Must be in form of username:secret");
}
- return new ImageRegistryAuth(values[0].trim(), values[1].trim().getBytes(), host);
+ return new ImageRepositoryAuth(values[0].trim(), values[1].trim().getBytes(), host);
}
public String getEncoded() {
- return Base64.encodeString(this.username + ImageRegistryAuth.VALUE_DELIMITER + new String(this.secret));
+ return Base64.encodeString(this.username + ImageRepositoryAuth.VALUE_DELIMITER + new String(this.secret));
}
public String getHost() {
diff --git a/skaha/src/main/java/org/opencadc/skaha/session/PostAction.java b/skaha/src/main/java/org/opencadc/skaha/session/PostAction.java
index 38453e73..8e36434e 100644
--- a/skaha/src/main/java/org/opencadc/skaha/session/PostAction.java
+++ b/skaha/src/main/java/org/opencadc/skaha/session/PostAction.java
@@ -101,7 +101,7 @@
import org.opencadc.skaha.SkahaAction;
import org.opencadc.skaha.context.ResourceContexts;
import org.opencadc.skaha.image.Image;
-import org.opencadc.skaha.registry.ImageRegistryAuth;
+import org.opencadc.skaha.repository.ImageRepositoryAuth;
import org.opencadc.skaha.utils.CommandExecutioner;
import org.opencadc.skaha.utils.PosixCache;
@@ -503,8 +503,8 @@ private String validateImage(String imageID, String type) throws Exception {
// TODO: and since we can't verify one way or the other, let them through.
if (image == null) {
log.warn("Image " + imageID + " missing from cache...");
- final ImageRegistryAuth imageRegistryAuth = getRegistryAuth(imageRegistryHost);
- if (imageRegistryAuth == null) {
+ final ImageRepositoryAuth imageRepositoryAuth = getRegistryAuth(imageRegistryHost);
+ if (imageRepositoryAuth == null) {
throw new ResourceNotFoundException("image not found or not labelled: " + imageID);
} else {
log.warn("Assuming image " + imageID + " is private as credentials were supplied.");
@@ -606,7 +606,7 @@ public void createSession(
// In the absence of the existence of a public image, assume Private. The validateImage() step above will have
// caught a non-existent Image already.
if (getPublicImage(image) == null) {
- final ImageRegistryAuth userRegistryAuth = getRegistryAuth(getRegistryHost(image));
+ final ImageRepositoryAuth userRegistryAuth = getRegistryAuth(getRegistryHost(image));
imageRegistrySecretName = createRegistryImageSecret(userRegistryAuth);
} else {
imageRegistrySecretName = PostAction.DEFAULT_SOFTWARE_IMAGESECRET_VALUE;
@@ -697,7 +697,7 @@ private String generateToken() throws Exception {
* @param registryAuth The credentials to use to authenticate to the Image Registry.
* @return String secret name, never null.
*/
- private String createRegistryImageSecret(final ImageRegistryAuth registryAuth) throws Exception {
+ private String createRegistryImageSecret(final ImageRepositoryAuth registryAuth) throws Exception {
final String username = this.posixPrincipal.username;
final String secretName = "registry-auth-" + username.toLowerCase();
log.debug("Creating user secret " + secretName);
diff --git a/skaha/src/main/java/org/opencadc/skaha/utils/CommandExecutioner.java b/skaha/src/main/java/org/opencadc/skaha/utils/CommandExecutioner.java
index 4a093e2a..82243002 100644
--- a/skaha/src/main/java/org/opencadc/skaha/utils/CommandExecutioner.java
+++ b/skaha/src/main/java/org/opencadc/skaha/utils/CommandExecutioner.java
@@ -15,7 +15,7 @@
import org.apache.log4j.Logger;
import org.json.JSONObject;
import org.opencadc.skaha.K8SUtil;
-import org.opencadc.skaha.registry.ImageRegistryAuth;
+import org.opencadc.skaha.repository.ImageRepositoryAuth;
public class CommandExecutioner {
private static final Logger log = Logger.getLogger(CommandExecutioner.class);
@@ -92,7 +92,7 @@ public static void execute(final String[] command, final OutputStream standardOu
* @param secretName The name of the secret to create.
* @throws Exception If there is an error creating the secret.
*/
- public static void ensureRegistrySecret(final ImageRegistryAuth registryAuth, final String secretName)
+ public static void ensureRegistrySecret(final ImageRepositoryAuth registryAuth, final String secretName)
throws Exception {
// delete any old secret by this name
final String[] deleteCmd = CommandExecutioner.getDeleteSecretCommand(secretName);
@@ -135,7 +135,7 @@ static String[] getDeleteSecretCommand(final String secretName) {
return new String[] {"kubectl", "--namespace", K8SUtil.getWorkloadNamespace(), "delete", "secret", secretName};
}
- static String[] getRegistryCreateSecretCommand(final ImageRegistryAuth registryAuth, final String secretName) {
+ static String[] getRegistryCreateSecretCommand(final ImageRepositoryAuth registryAuth, final String secretName) {
if (registryAuth == null) {
throw new IllegalArgumentException("registryAuth is required.");
} else if (!StringUtil.hasText(secretName)) {
diff --git a/skaha/src/main/webapp/WEB-INF/web.xml b/skaha/src/main/webapp/WEB-INF/web.xml
index 9c0ae7e9..dc1c533d 100644
--- a/skaha/src/main/webapp/WEB-INF/web.xml
+++ b/skaha/src/main/webapp/WEB-INF/web.xml
@@ -73,6 +73,16 @@
2
+
+ ImageRepositoryServlet
+ ca.nrc.cadc.rest.RestServlet
+
+ get
+ org.opencadc.skaha.repository.GetAction
+
+ 2
+
+
CapabilitiesServlet
@@ -122,6 +132,11 @@
ImageServlet
/v0/image/*
+
+
+ ImageRepositoryServlet
+ /v0/repository/*
+
CapabilitiesServlet
diff --git a/skaha/src/main/webapp/service.yaml b/skaha/src/main/webapp/service.yaml
index 5cbb57c4..0b2a004c 100644
--- a/skaha/src/main/webapp/service.yaml
+++ b/skaha/src/main/webapp/service.yaml
@@ -381,3 +381,22 @@ paths:
description: Service busy
default:
description: Unexpected error
+ /v0/registry:
+ get:
+ description: |
+ List the Image Registry hosts configured as a JSON Array.
+ tags:
+ - Harbor, Registry, Image, Repository
+ responses:
+ '200':
+ description: Successful response
+ '401':
+ description: Not authenticated
+ '403':
+ description: Permission denied
+ '500':
+ description: Internal error
+ '503':
+ description: Service busy
+ default:
+ description: Unexpected error
diff --git a/skaha/src/test/java/org/opencadc/skaha/repository/GetActionTest.java b/skaha/src/test/java/org/opencadc/skaha/repository/GetActionTest.java
new file mode 100644
index 00000000..abd6bd33
--- /dev/null
+++ b/skaha/src/test/java/org/opencadc/skaha/repository/GetActionTest.java
@@ -0,0 +1,39 @@
+package org.opencadc.skaha.repository;
+
+import java.io.StringWriter;
+import java.io.Writer;
+import org.json.JSONArray;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GetActionTest {
+ @Test
+ public void ensureJSON() throws Exception {
+ final String[] hostArray = new String[] {"images.example.org", "images2.example.org"};
+ final Writer writer = new StringWriter();
+ final GetAction testSubject = new GetAction() {
+ @Override
+ protected void initRequest() {
+ // Do nothing.
+ }
+
+ @Override
+ Writer initWriter() {
+ return writer;
+ }
+
+ @Override
+ String[] getHarborHosts() {
+ return hostArray;
+ }
+ };
+
+ testSubject.doAction();
+
+ final JSONArray resultArray = new JSONArray(writer.toString());
+ final String[] resultStringArray =
+ resultArray.toList().stream().map(Object::toString).toArray(String[]::new);
+
+ Assert.assertArrayEquals("Wrong host array", hostArray, resultStringArray);
+ }
+}
diff --git a/skaha/src/test/java/org/opencadc/skaha/registry/ImageRegistryAuthTest.java b/skaha/src/test/java/org/opencadc/skaha/repository/ImageRepositoryAuthTest.java
similarity index 73%
rename from skaha/src/test/java/org/opencadc/skaha/registry/ImageRegistryAuthTest.java
rename to skaha/src/test/java/org/opencadc/skaha/repository/ImageRepositoryAuthTest.java
index 4ae6e08a..1b7852c5 100644
--- a/skaha/src/test/java/org/opencadc/skaha/registry/ImageRegistryAuthTest.java
+++ b/skaha/src/test/java/org/opencadc/skaha/repository/ImageRepositoryAuthTest.java
@@ -1,32 +1,32 @@
-package org.opencadc.skaha.registry;
+package org.opencadc.skaha.repository;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.junit.Assert;
import org.junit.Test;
-public class ImageRegistryAuthTest {
+public class ImageRepositoryAuthTest {
@Test
public void testFromEncodedBadInputs() {
try {
- ImageRegistryAuth.fromEncoded(null, "host");
+ ImageRepositoryAuth.fromEncoded(null, "host");
Assert.fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
Assert.assertEquals("Encoded auth username and key is required.", e.getMessage());
}
try {
- ImageRegistryAuth.fromEncoded("", "host");
+ ImageRepositoryAuth.fromEncoded("", "host");
Assert.fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
Assert.assertEquals("Encoded auth username and key is required.", e.getMessage());
}
try {
- ImageRegistryAuth.fromEncoded("value", null);
+ ImageRepositoryAuth.fromEncoded("value", null);
Assert.fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
- Assert.assertEquals("Registry host is required.", e.getMessage());
+ Assert.assertEquals("Repository host is required.", e.getMessage());
}
}
@@ -34,7 +34,7 @@ public void testFromEncodedBadInputs() {
public void testFromEncoded() {
final String encodedValue =
new String(Base64.getEncoder().encode("username:supersecret".getBytes(StandardCharsets.UTF_8)));
- final ImageRegistryAuth auth = ImageRegistryAuth.fromEncoded(encodedValue, "host.example.com");
+ final ImageRepositoryAuth auth = ImageRepositoryAuth.fromEncoded(encodedValue, "host.example.com");
Assert.assertEquals("Wrong username", "username", auth.getUsername());
Assert.assertArrayEquals("Wrong secret", "supersecret".getBytes(), auth.getSecret());
diff --git a/skaha/src/test/java/org/opencadc/skaha/utils/CommandExecutionerTest.java b/skaha/src/test/java/org/opencadc/skaha/utils/CommandExecutionerTest.java
index 7447e0f3..dd04950c 100644
--- a/skaha/src/test/java/org/opencadc/skaha/utils/CommandExecutionerTest.java
+++ b/skaha/src/test/java/org/opencadc/skaha/utils/CommandExecutionerTest.java
@@ -4,7 +4,7 @@
import org.junit.Assert;
import org.junit.Test;
import org.opencadc.skaha.K8SUtil;
-import org.opencadc.skaha.registry.ImageRegistryAuth;
+import org.opencadc.skaha.repository.ImageRepositoryAuth;
public class CommandExecutionerTest {
@Test
@@ -31,17 +31,17 @@ public void testGetRegistryCreateSecretCommand() {
Assert.assertEquals("registryAuth is required.", e.getMessage());
}
- final ImageRegistryAuth imageRegistryAuth = ImageRegistryAuth.fromEncoded(
+ final ImageRepositoryAuth imageRepositoryAuth = ImageRepositoryAuth.fromEncoded(
new String(Base64.getEncoder().encode("username:password".getBytes())), "host");
try {
- CommandExecutioner.getRegistryCreateSecretCommand(imageRegistryAuth, "");
+ CommandExecutioner.getRegistryCreateSecretCommand(imageRepositoryAuth, "");
} catch (IllegalArgumentException e) {
Assert.assertEquals("secretName is required.", e.getMessage());
}
try {
- CommandExecutioner.getRegistryCreateSecretCommand(imageRegistryAuth, null);
+ CommandExecutioner.getRegistryCreateSecretCommand(imageRepositoryAuth, null);
} catch (IllegalArgumentException e) {
Assert.assertEquals("secretName is required.", e.getMessage());
}