diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 8ab1161044..5ef68f9a99 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -136,6 +136,9 @@ jobs:
- lang: scala
docker-image: mozilla/sbt:latest
entrypoint: /bin/sh
+ - lang: java
+ docker-image: openjdk:11
+ entrypoint: /bin/sh
name: test-${{ matrix.lang }}-example
runs-on: ubuntu-latest
diff --git a/README.md b/README.md
index e7747748ef..d7543d866d 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@ curl http://0.0.0.0:4443/storage/v1/b/sample-bucket/o
## Client library examples
-For examples using the Python, Node.js and Go clients, check out the
+For examples using the Python, Node.js, Java and Go clients, check out the
[``examples``](/examples/) directory.
### Building the image locally
diff --git a/ci/run-java-example.sh b/ci/run-java-example.sh
new file mode 100755
index 0000000000..c5ed0843ed
--- /dev/null
+++ b/ci/run-java-example.sh
@@ -0,0 +1,12 @@
+# Copyright 2022 Francisco Souza. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+set -e
+
+./fake-gcs-server -backend memory -scheme http -port 8080 -external-url "http://localhost:8080" &
+
+(
+ cd examples/java
+ ./mvnw clean test -B
+)
diff --git a/examples/go/main.go b/examples/go/main.go
index 0eebe90cc4..afe51f5342 100644
--- a/examples/go/main.go
+++ b/examples/go/main.go
@@ -14,6 +14,8 @@ import (
"fmt"
"io/ioutil"
"log"
+ "net/http"
+ "strings"
"cloud.google.com/go/storage"
"google.golang.org/api/iterator"
@@ -48,6 +50,11 @@ func main() {
if err != nil {
log.Fatal(err)
}
+
+ err = updateConfig()
+ if err != nil {
+ log.Fatal(err)
+ }
}
func list(client *storage.Client, bucketName string) ([]string, error) {
@@ -78,3 +85,17 @@ func downloadFile(client *storage.Client, bucketName, fileKey string) ([]byte, e
func deleteFile(client *storage.Client, bucketName, fileKey string) error {
return client.Bucket(bucketName).Object(fileKey).Delete(context.TODO())
}
+
+func updateConfig() error {
+ changeExternalUrl := "http://localhost:8080/_internal/config"
+
+ client := &http.Client{}
+ req, err := http.NewRequest(http.MethodPut, changeExternalUrl, strings.NewReader("{\"externalUrl\": \"http://localhost:8080\"}"))
+ req.Header.Add("Content-Type", "application/json")
+ if err != nil {
+ return err
+ }
+ _, err = client.Do(req)
+
+ return err
+}
diff --git a/examples/java/.gitignore b/examples/java/.gitignore
new file mode 100644
index 0000000000..b7d2fe410f
--- /dev/null
+++ b/examples/java/.gitignore
@@ -0,0 +1,2 @@
+target
+.mvn
diff --git a/examples/java/.mvn/wrapper/maven-wrapper.properties b/examples/java/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..642d572ce9
--- /dev/null
+++ b/examples/java/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/examples/java/README.md b/examples/java/README.md
new file mode 100644
index 0000000000..f9609c7729
--- /dev/null
+++ b/examples/java/README.md
@@ -0,0 +1,105 @@
+# Java example
+
+## Common case
+
+ Storage storageClient = StorageOptions.newBuilder()
+ .setHost(fakeGcsExternalUrl)
+ .setProjectId("test-project")
+ .setCredentials(NoCredentials.getInstance())
+ .build()
+ .getService();
+
+See [the example](/src/test/java/com/fsouza/fakegcsserver/java/examples/FakeGcsServerTest.java) for more details.
+
+## Resumable upload operations and containerised fake-gcs-server
+
+The main difficulty with the case when a fake-gcs-server is containerised (by [Testcontainers](https://www.testcontainers.org/) for example)
+is that the container IP and port are assigned after the container is eventually started, so we can not provide
+fake-gcs-server with `external-url` option beforehand(when a test container has not been started yet).
+
+It's necessary to update the server configuration before making resumable upload calls against the server, so the fake server can respond
+with correct `content` HTTP header.
+
+The example will look like as follows:
+
+* additional testcontainers dependencies at `pom.xml`:
+
+```xml
+
+ org.testcontainers
+ testcontainers
+ ${testcontainers.version}
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ ${testcontainers.version}
+ test
+
+```
+
+ * spin up fake-gcs-server testcontainer right in the test `FakeGcsServerTest.java`:
+```java
+@Testcontainers
+class FakeGcsServerTest {
+
+ @Container
+ static final GenericContainer> fakeGcs = new GenericContainer<>("fsouza/fake-gcs-server")
+ .withExposedPorts(8080)
+ .withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint(
+ "/bin/fake-gcs-server",
+ "-scheme", "http"
+ ));
+
+ @BeforeAll
+ static void setUpFakeGcs() throws Exception {
+ String fakeGcsExternalUrl = "http://" + fakeGcs.getContainerIpAddress() + ":" + fakeGcs.getFirstMappedPort();
+
+ updateExternalUrlWithContainerUrl(fakeGcsExternalUrl);
+
+ storageClient = StorageOptions.newBuilder()
+ .setHost(fakeGcsExternalUrl)
+ .setProjectId("test-project")
+ .setCredentials(NoCredentials.getInstance())
+ .build()
+ .getService();
+ }
+
+ private static void updateExternalUrlWithContainerUrl(String fakeGcsExternalUrl) throws Exception {
+ String modifyExternalUrlRequestUri = fakeGcsExternalUrl + "/_internal/config";
+ String updateExternalUrlJson = "{"
+ + "\"externalUrl\": \"" + fakeGcsExternalUrl + "\""
+ + "}";
+
+ HttpRequest req = HttpRequest.newBuilder()
+ .uri(URI.create(modifyExternalUrlRequestUri))
+ .header("Content-Type", "application/json")
+ .PUT(BodyPublishers.ofString(updateExternalUrlJson))
+ .build();
+ HttpResponse response = HttpClient.newBuilder().build()
+ .send(req, BodyHandlers.discarding());
+
+ if (response.statusCode() != 200) {
+ throw new RuntimeException(
+ "error updating fake-gcs-server with external url, response status code " + response.statusCode() + " != 200");
+ }
+ }
+
+ @Test
+ void shouldUploadFileByWriterChannel() throws IOException {
+
+ storageClient.create(BucketInfo.newBuilder("sample-bucket2").build());
+
+ WriteChannel channel = storageClient.writer(BlobInfo.newBuilder("sample-bucket2", "some_file2.txt").build());
+ channel.write(ByteBuffer.wrap("line1\n".getBytes()));
+ channel.write(ByteBuffer.wrap("line2\n".getBytes()));
+ channel.close();
+
+ Blob someFile2 = storageClient.get("sample-bucket2", "some_file2.txt");
+ String fileContent = new String(someFile2.getContent());
+ assertEquals("line1\nline2\n", fileContent);
+ }
+}
+```
+
diff --git a/examples/java/mvnw b/examples/java/mvnw
new file mode 100755
index 0000000000..41c0f0c23d
--- /dev/null
+++ b/examples/java/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/examples/java/pom.xml b/examples/java/pom.xml
new file mode 100644
index 0000000000..3299cb6276
--- /dev/null
+++ b/examples/java/pom.xml
@@ -0,0 +1,71 @@
+
+
+ 4.0.0
+
+ fsouza
+ fake-gcs-server-java-examples
+ 1.0-SNAPSHOT
+
+
+ 11
+ UTF-8
+ ${java.version}
+ ${java.version}
+ ${java.version}
+ ${project.encoding}
+ ${project.encoding}
+
+ 5.8.2
+ 24.2.0
+
+
+
+
+
+ com.google.cloud
+ libraries-bom
+ ${java-libraries-bom.version}
+ pom
+ import
+
+
+
+
+
+
+ com.google.cloud
+ google-cloud-storage
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit-jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+
+
\ No newline at end of file
diff --git a/examples/java/src/test/java/com/fsouza/fakegcsserver/java/examples/FakeGcsServerTest.java b/examples/java/src/test/java/com/fsouza/fakegcsserver/java/examples/FakeGcsServerTest.java
new file mode 100644
index 0000000000..dd1c8dbb84
--- /dev/null
+++ b/examples/java/src/test/java/com/fsouza/fakegcsserver/java/examples/FakeGcsServerTest.java
@@ -0,0 +1,47 @@
+package com.fsouza.fakegcsserver.java.examples;
+
+import com.google.cloud.NoCredentials;
+import com.google.cloud.WriteChannel;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class FakeGcsServerTest {
+
+ static Storage storageClient;
+
+ @BeforeAll
+ static void setUpStorageClient() throws Exception {
+ String fakeGcsExternalUrl = "http://0.0.0.0:8080";
+
+ storageClient = StorageOptions.newBuilder()
+ .setHost(fakeGcsExternalUrl)
+ .setProjectId("test-project")
+ .setCredentials(NoCredentials.getInstance())
+ .build()
+ .getService();
+ }
+
+ @Test
+ void shouldUploadFileByWriterChannel() throws IOException {
+
+ storageClient.create(BucketInfo.newBuilder("sample-bucket2").build());
+
+ WriteChannel channel = storageClient.writer(BlobInfo.newBuilder("sample-bucket2", "some_file2.txt").build());
+ channel.write(ByteBuffer.wrap("line1\n".getBytes()));
+ channel.write(ByteBuffer.wrap("line2\n".getBytes()));
+ channel.close();
+
+ Blob someFile2 = storageClient.get("sample-bucket2", "some_file2.txt");
+ String fileContent = new String(someFile2.getContent());
+ assertEquals("line1\nline2\n", fileContent);
+ }
+}
diff --git a/fakestorage/config.go b/fakestorage/config.go
new file mode 100644
index 0000000000..b28b1e00fd
--- /dev/null
+++ b/fakestorage/config.go
@@ -0,0 +1,26 @@
+package fakestorage
+
+import (
+ "encoding/json"
+ "net/http"
+)
+
+func (s *Server) updateServerConfig(r *http.Request) jsonResponse {
+
+ var configOptions struct {
+ ExternalUrl string `json:"externalUrl,omitempty"`
+ }
+ err := json.NewDecoder(r.Body).Decode(&configOptions)
+ if err != nil {
+ return jsonResponse{
+ status: http.StatusBadRequest,
+ errorMessage: "Update server config payload can not be parsed.",
+ }
+ }
+
+ if configOptions.ExternalUrl != "" {
+ s.externalURL = configOptions.ExternalUrl
+ }
+
+ return jsonResponse{status: http.StatusOK}
+}
diff --git a/fakestorage/server.go b/fakestorage/server.go
index b5a548842b..99f37ee20a 100644
--- a/fakestorage/server.go
+++ b/fakestorage/server.go
@@ -220,6 +220,11 @@ func (s *Server) buildMuxer() {
r.Path("/b/{bucketName}/o/{objectName:.+}").Methods(http.MethodPut, http.MethodPost).HandlerFunc(jsonToHTTPHandler(s.updateObject))
}
+ // Internal / update server configuration
+ s.mux.Path("/_internal/config").Methods(http.MethodPut).HandlerFunc(jsonToHTTPHandler(s.updateServerConfig))
+ s.mux.Host(s.publicHost).Path("/_internal/config").Methods(http.MethodPut).HandlerFunc(jsonToHTTPHandler(s.updateServerConfig))
+ // Internal - end
+
bucketHost := fmt.Sprintf("{bucketName}.%s", s.publicHost)
s.mux.Host(bucketHost).Path("/{objectName:.+}").Methods(http.MethodGet, http.MethodHead).HandlerFunc(s.downloadObject)
s.mux.Path("/download/storage/v1/b/{bucketName}/o/{objectName:.+}").Methods(http.MethodGet).HandlerFunc(s.downloadObject)
diff --git a/fakestorage/server_test.go b/fakestorage/server_test.go
index a099d3805b..3a4a1d7b55 100644
--- a/fakestorage/server_test.go
+++ b/fakestorage/server_test.go
@@ -270,6 +270,56 @@ func testDownloadObjectRange(t *testing.T, server *Server) {
}
}
+func TestUpdateServerConfig(t *testing.T) {
+ tests := []struct {
+ name string
+ requestBody string
+ expectedExternalUrl string
+ }{
+ {
+ "PUT: empty json",
+ "{}",
+ "https://0.0.0.0:4443",
+ },
+ {
+ "PUT: externalUrl provided",
+ "{\"externalUrl\": \"https://1.2.3.4:4321\"}",
+ "https://1.2.3.4:4321",
+ },
+ }
+
+ opts := Options{
+ PublicHost: "0.0.0.0:4443",
+ ExternalURL: "https://0.0.0.0:4443",
+ }
+ server, err := NewServerWithOptions(opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ client := server.HTTPClient()
+ configJson := strings.NewReader(test.requestBody)
+ req, err := http.NewRequest(http.MethodPut, "https://0.0.0.0:4443/_internal/config", configJson)
+ if err != nil {
+ t.Fatal(err)
+ }
+ resp, err := client.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ t.Errorf("wrong status returned\nwant %d\ngot %d", http.StatusOK, resp.StatusCode)
+ }
+
+ assert.Equal(t, test.expectedExternalUrl, server.externalURL)
+ })
+ }
+}
+
func TestDownloadObjectAlternatePublicHost(t *testing.T) {
tests := []struct {
name string