You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ho...@apache.org on 2021/05/17 21:18:02 UTC
[solr] branch main updated: SOLR-15335: Add support for Official
Dockerfile generation
This is an automated email from the ASF dual-hosted git repository.
houston pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new a1bdadc SOLR-15335: Add support for Official Dockerfile generation
a1bdadc is described below
commit a1bdadc63a69f59f68276fe2eda0a2586bcffd11
Author: Houston Putman <ho...@apache.org>
AuthorDate: Mon May 17 15:58:35 2021 -0500
SOLR-15335: Add support for Official Dockerfile generation
Co-authored-by: Chris Hostetter <ho...@apache.org>
---
gradle/help.gradle | 1 +
solr/CHANGES.txt | 4 +-
solr/docker/build.gradle | 382 +++++++++++++++++----
solr/docker/gradle-help.txt | 27 ++
.../Dockerfile.body.template} | 43 +--
.../templates/Dockerfile.local.header.template | 29 ++
.../templates/Dockerfile.official.header.template | 79 +++++
solr/packaging/build.gradle | 6 +
8 files changed, 481 insertions(+), 90 deletions(-)
diff --git a/gradle/help.gradle b/gradle/help.gradle
index 5fcec75..7bbdaf5 100644
--- a/gradle/help.gradle
+++ b/gradle/help.gradle
@@ -30,6 +30,7 @@ configure(rootProject) {
["Git", "help/git.txt", "Git assistance and guides."],
["ValidateLogCalls", "help/validateLogCalls.txt", "How to use logging calls efficiently."],
["IDEs", "help/IDEs.txt", "IDE support."],
+ ["GpgSigning", "help/gpgSigning.txt", "Signing artifacts with GPG."],
["Docker", "solr/docker/gradle-help.txt", "Building Solr Docker images."],
]
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index f5b8e6b..2a540bd 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -125,7 +125,9 @@ when told to. The admin UI now tells it to. (Nazerke Seidan, David Smiley)
* SOLR-15340: Rename shardsWhitelist and extract AllowListUrlChecker to use it more broadly. (Bruno Roustant)
-* SOLR-14790: Move Solr Docker image documentation to the ref guide. (Houston Putman)
+* SOLR-14790: Docker: Move Solr Docker image documentation to the ref guide. (Houston Putman)
+
+* SOLR-15335: Docker: Solr has capability to build functionally-identical local and official Docker image. (hossman, Houston Putman)
Other Changes
----------------------
diff --git a/solr/docker/build.gradle b/solr/docker/build.gradle
index 4fd9117..c9320e0 100644
--- a/solr/docker/build.gradle
+++ b/solr/docker/build.gradle
@@ -15,8 +15,9 @@
* limitations under the License.
*/
-import com.google.common.base.Preconditions
-import com.google.common.base.Strings
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+import org.apache.commons.codec.digest.DigestUtils
description = 'Solr Docker image'
@@ -29,28 +30,39 @@ def dockerImageName = propertyOrEnvOrDefault("solr.docker.imageName", "SOLR_DOCK
def baseDockerImage = propertyOrEnvOrDefault("solr.docker.baseImage", "SOLR_DOCKER_BASE_IMAGE", 'openjdk:11-jre-slim')
def githubUrlOrMirror = propertyOrEnvOrDefault("solr.docker.githubUrl", "SOLR_DOCKER_GITHUB_URL", 'github.com')
+def releaseGpgFingerprint = propertyOrDefault('signing.gnupg.keyName','');
+
+def testCasesDir = "tests/cases"
+
// Build directory locations
def imageIdFile = "$buildDir/image-id"
+def smokeTestOfficial = "$buildDir/smoke-check-official"
+def imageIdFileOfficial = "$smokeTestOfficial/image-id"
+
configurations {
packaging {
canBeResolved = true
}
- solrArtifacts {
+ solrTgz {
+ canBeConsumed = false
canBeResolved = true
}
- dockerContext {
+ solrTgzSignature {
+ canBeConsumed = false
canBeResolved = true
}
dockerImage {
canBeResolved = true
}
+ dockerOfficialSmokeCheckImage {
+ canBeConsumed = false
+ canBeResolved = true
+ }
}
ext {
packagingDir = file("${buildDir}/packaging")
- dockerContextFile = "context.tgz"
- dockerContextPath = "${buildDir}/${dockerContextFile}"
}
dependencies {
@@ -58,40 +70,44 @@ dependencies {
builtBy 'assemblePackaging'
}
- solrArtifacts project(path: ":solr:packaging", configuration: 'archives')
-
- dockerContext files(dockerContextPath) {
- builtBy 'dockerContext'
- }
+ solrTgz project(path: ":solr:packaging", configuration: 'solrTgz')
+ solrTgzSignature project(path: ":solr:packaging", configuration: 'solrTgzSignature')
dockerImage files(imageIdFile) {
builtBy 'dockerBuild'
}
+ dockerOfficialSmokeCheckImage files(imageIdFileOfficial) {
+ builtBy 'testBuildDockerfileOfficial'
+ }
+}
+
+// We're using commons-codec for computing checksums.
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath "commons-codec:commons-codec:${scriptDepVersions['commons-codec']}"
+ }
+}
+def checksum = { file ->
+ return new DigestUtils(DigestUtils.sha512Digest).digestAsHex(file).trim()
}
+
task assemblePackaging(type: Sync) {
description = 'Assemble docker scripts and Dockerfile for Solr Packaging'
from(projectDir, {
- include "Dockerfile"
include "scripts/**"
})
-
- into packagingDir
-}
-
-task dockerContext(type: Sync) {
- description = 'Sync Solr tgz to become docker context'
-
- from(configurations.solrArtifacts, {
- include "*.tgz"
- rename { file -> dockerContextFile }
+ from(buildDir, {
+ include 'Dockerfile.local'
})
-
- into buildDir
+ into packagingDir
}
-task dockerBuild(dependsOn: configurations.dockerContext) {
+task dockerBuild(dependsOn: configurations.solrTgz) {
group = 'Docker'
description = 'Build Solr docker image'
@@ -100,13 +116,13 @@ task dockerBuild(dependsOn: configurations.dockerContext) {
baseDockerImage: baseDockerImage,
githubUrlOrMirror: githubUrlOrMirror
])
- inputs.files(dockerContextPath)
+ inputs.files(configurations.solrTgz)
doLast {
exec {
- standardInput = configurations.dockerContext.singleFile.newDataInputStream()
+ standardInput = configurations.solrTgz.singleFile.newDataInputStream()
commandLine "docker", "build",
- "-f", "solr-${version}/docker/Dockerfile",
+ "-f", "solr-${version}/docker/Dockerfile.local",
"--iidfile", imageIdFile,
"--build-arg", "BASE_IMAGE=${inputs.properties.baseDockerImage}",
"--build-arg", "GITHUB_URL=${inputs.properties.githubUrlOrMirror}",
@@ -151,48 +167,54 @@ task dockerTag(dependsOn: tasks.dockerBuild) {
}
}
+
+
+// Re-usable closure to run tests...
+def testDockerImage = { solrImageId, outputDir, testCasesInclude, testCasesExclude ->
+ // Print information on the image before it is tested
+ logger.lifecycle("Testing Solr Image:")
+ logger.lifecycle("\tID: $solrImageId\n")
+
+ // Run the tests
+ def sourceDir = file(testCasesDir)
+ sourceDir.eachFile { file ->
+ def testName = file.getName()
+ def testCaseBuildDir = "${outputDir}/${testName}"
+
+ // If specific tests are specified, only run those. Otherwise run all that are not ignored.
+ def runTest = !testCasesInclude.isEmpty() ? testCasesInclude.contains(testName) : !testCasesExclude.contains(testName)
+ if (runTest) {
+ exec {
+ environment "TEST_DIR", file
+ environment "BUILD_DIR", testCaseBuildDir
+ commandLine "bash", "$file/test.sh", solrImageId
+ }
+ }
+ }
+}
+
task testDocker(dependsOn: tasks.dockerBuild) {
group = 'Docker'
- description = 'Test Solr docker image'
+ description = 'Test Solr docker image built from Dockerfile.local'
- def inputDir = "tests/cases"
- def outputDir = "$buildDir/tmp/tests"
+ def iidFile = tasks.dockerBuild.outputs.files.singleFile
// Ensure that the docker image is re-tested if the image ID changes or the test files change
+ inputs.file(iidFile)
+ inputs.dir(testCasesDir)
inputs.properties([
- includeTests: new HashSet(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.include", "SOLR_DOCKER_TESTS_INCLUDE", ",").split(","))),
- excludeTests: new HashSet(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.exclude", "SOLR_DOCKER_TESTS_EXCLUDE", ",").split(",")))
+ // include/exclude options are designed for people who know their customizations will break some tests
+ includeTests: new HashSet(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.include", "SOLR_DOCKER_TESTS_INCLUDE", ",").split(","))),
+ excludeTests: new HashSet(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.exclude", "SOLR_DOCKER_TESTS_EXCLUDE", ",").split(",")))
])
- inputs.file(imageIdFile)
- inputs.dir(inputDir)
+
+ def outputDir = "$buildDir/test-results"
+ outputs.dir(outputDir)
doLast {
- def solrImageId = tasks.dockerBuild.outputs.files.singleFile.text
- def solrImageName = solrImageId.substring(7, 14)
-
- // Print information on the image before it is tested
- logger.lifecycle("Testing Solr Image:")
- logger.lifecycle("\tID: $solrImageId\n")
-
- // Run the tests
- def sourceDir = file(inputDir)
- sourceDir.eachFile { file ->
- def testName = file.getName()
- def testCaseBuildDir = "${outputDir}/${testName}"
-
- // If specific tests are specified, only run those. Otherwise run all that are not ignored.
- def runTest = !inputs.properties.includeTests.isEmpty() ? inputs.properties.includeTests.contains(testName) : !inputs.properties.excludeTests.contains(testName)
- if (runTest) {
- exec {
- environment "TEST_DIR", file
- environment "BUILD_DIR", testCaseBuildDir
- commandLine "bash", "$file/test.sh", solrImageName
- }
- }
- }
+ def solrImageId = iidFile.text
+ testDockerImage(solrImageId, outputDir, inputs.properties.includeTests, inputs.properties.excludeTests)
}
-
- outputs.dir(outputDir)
}
task dockerPush(dependsOn: tasks.dockerTag) {
@@ -222,3 +244,243 @@ task dockerPush(dependsOn: tasks.dockerTag) {
task docker {
dependsOn tasks.dockerBuild, tasks.dockerTag
}
+
+
+ext {
+ // Filters/Patterns for re-use in multiple "template" related tasks
+ commentFilter = { line ->
+ if (line.startsWith('#-#')) {
+ return null;
+ }
+ return line;
+ }
+ propReplacePattern = Pattern.compile('_REPLACE_((_|\\p{Upper})+)_')
+}
+task createBodySnippetDockerfile(type: Copy) {
+ from 'templates/Dockerfile.body.template'
+ into "$buildDir/snippets/"
+ rename { name -> name.replace("template","snippet") }
+ filteringCharset 'UTF-8'
+
+ // NOTE: The only "templating" the Dockerfile.body supports is removing comments.
+ //
+ // Any situation where it feel appropriate to add variable substitution should be reconsidered, and probably
+ // implemented as either a build-arg or as a variable expanded in the header snippets (or both)
+ filter( commentFilter )
+}
+
+ext {
+ // NOTE: 'props' are variables that will be replaced in the respective templates,
+ // and they must consist solely of characters matching the regex '(_|\\p{Upper})+'.
+ // They may only be used in ARG and FROM lines, via the syntax: '_REPLACE_FOO_'
+ // where 'FOO' is the key used in 'props' (NOTE the leading and trailing literal '_' characters)
+ dfLocalDetails = [
+ name: 'Local',
+ desc: 'Dockerfile used to create local Solr docker images directly from Solr release tgz file',
+
+ // NOTE: There should be no reason for Dockerfile.local to include unique values
+ //
+ // Values identical in both Dockerfiles should use consistent names in both templates and
+ // be defined in the task creation.
+ props: [:]
+ ]
+ dfOfficialDetails = [
+ name: 'Official',
+ desc: 'Dockerfile used to create official Solr docker images published to hub.docker.io',
+ props: [
+ // NOTE: Only include values here that are distinct and unique to the Official Dockerfiles
+ //
+ // Values identical in both Dockerfiles should use consistent names in both templates and
+ // be defined in the task creation
+
+ 'SOLR_VERSION': version,
+ // NOTE: SHA is lazy computed...
+ 'SOLR_TGZ_SHA': "${ -> checksum(configurations.solrTgz.singleFile) }",
+ 'RELEASE_MANAGER_GPG_FINGERPRINT': "${releaseGpgFingerprint}"
+ ]
+ ]
+}
+/*
+This section creates the following gradle tasks:
+- createDockerfileOfficial
+- createDockerfileLocal
+
+Both will create a self-standing Dockerfile that can be used to build a Solr image.
+These are templated using a unique header file for each Dockerfile and the same body template for the logic on installing Solr.
+These templates can be found in the templates/ directory.
+
+The snippets of each section (header and body) are saved to build/snippets after they are templated and before they are combined.
+The final Dockerfiles are merely the snippet headers combined with the snippet body.
+ */
+[ dfLocalDetails, dfOfficialDetails ].each{ details ->
+ def fileName = "Dockerfile.${ -> details.name.toLowerCase(Locale.ROOT) }"
+ def outFile = file("$buildDir/${fileName}")
+
+ def props = [
+ // Values defined here should be common (and consistent) across both Dockerfiles
+ 'BASE_IMAGE': baseDockerImage,
+ * : details.props
+ ]
+ tasks.create("createDockerfile${details.name}", Copy) {
+ description "Creates ${details.desc}"
+
+ dependsOn tasks.createBodySnippetDockerfile
+ inputs.properties(props)
+ outputs.file(outFile)
+
+ from "templates/${fileName}.header.template"
+ into "$buildDir/snippets/"
+ rename { name -> name.replace("template","snippet") }
+ filteringCharset 'UTF-8'
+ filter( commentFilter )
+ filter( { line ->
+ if ( line.startsWith("FROM ") || line.startsWith("ARG ") ) {
+ Matcher matcher = project.ext.propReplacePattern.matcher(line);
+ StringBuilder sb = new StringBuilder();
+ if (matcher.find()) {
+ String key = matcher.group(1);
+ if (null == key || key.isEmpty() || ( ! props.containsKey(key) ) ) {
+ throw new GradleException("Line contains invalid REPLACE variable (" + key + "): " + line);
+ }
+ matcher.appendReplacement(sb, props.get(key) );
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+ return line;
+ })
+
+ doLast {
+ outFile.withWriter('UTF-8') { writer ->
+ files("$buildDir/snippets/${fileName}.header.snippet",
+ "$buildDir/snippets/Dockerfile.body.snippet").each { snippet ->
+ snippet.withReader('UTF-8') { reader ->
+ writer << reader
+ }
+ }
+ }
+ }
+ }
+}
+assemblePackaging.dependsOn tasks.createDockerfileLocal
+tasks.createDockerfileOfficial.dependsOn configurations.solrTgz // to lazy compute SHA
+
+// sanity check...
+if (''.equals(releaseGpgFingerprint)) {
+ gradle.taskGraph.whenReady { graph ->
+ if ( graph.hasTask(createDockerfileOfficial) ) {
+ throw new GradleException("No GPG keyName found, please see help/gpgSigning.txt (GPG key is neccessary to create Dockerfile.official)")
+ }
+ }
+}
+
+task testBuildDockerfileOfficial(type: Copy) {
+ description = 'Test "docker build" works with our generated Dockerfile.official using Mocked URLs'
+
+ dependsOn createDockerfileOfficial
+ dependsOn configurations.solrTgz
+ dependsOn configurations.solrTgzSignature
+
+ def mockHttpdHome = file("$smokeTestOfficial/mock-httpd-home");
+
+ inputs.file("$buildDir/Dockerfile.official")
+ outputs.dir(mockHttpdHome)
+ outputs.file(imageIdFileOfficial)
+
+ from configurations.solrTgzSignature
+ from configurations.solrTgz
+ into mockHttpdHome
+
+ doLast {
+ // A file to record the container ID of our mock httpd
+ def mockServerIdFile = file("${buildDir}/dockerfile-mock-artifact-server-cid.txt")
+
+ // if we encounter any problems running our test, we'll fill this in and use it to suppress any
+ // other exceptions we encounter on cleanup...
+ def mainException = null;
+
+ // TODO: setup a little 'suppressOrThrow(Exception)' closure for reuse below....
+
+ try {
+ // run an httpd server to host our artifacts
+ logger.lifecycle('Running mock HTTPD server our testing...');
+ exec {
+ commandLine 'docker', 'run',
+ '--cidfile', mockServerIdFile,
+ '--rm',
+ '-d',
+ '-v', "${mockHttpdHome.absoluteFile}:/data",
+ '-w', '/data',
+ 'python:3-alpine', 'python', '-m', 'http.server', '9876'
+ }
+ try {
+ def mockServerId = mockServerIdFile.text
+ def mockServerIpStdOut = new ByteArrayOutputStream()
+ exec{
+ commandLine 'docker', 'inspect', "--format={{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}", mockServerId
+ standardOutput = mockServerIpStdOut
+ }
+ def mockServerIp = mockServerIpStdOut.toString().trim()
+
+ // *NOW* we can actually run our docker build command...
+ logger.lifecycle('Running docker build on Dockerfile.official...');
+ exec {
+ standardInput = file("${buildDir}/Dockerfile.official").newDataInputStream()
+ commandLine 'docker', 'build',
+ '--add-host', "mock-solr-dl-server:${mockServerIp}",
+ '--no-cache', // force fresh downloads from our current network
+ "--iidfile", imageIdFileOfficial,
+ '--build-arg', "SOLR_CLOSER_URL=http://mock-solr-dl-server:9876/solr-${version}.tgz",
+ '--build-arg', "SOLR_ARCHIVE_URL=http://mock-solr-dl-server:9876/solr-${version}.tgz",
+ "--build-arg", "GITHUB_URL=${githubUrlOrMirror}",
+ '-'
+ }
+
+ } finally {
+ // Try to shut down our mock httpd server....
+ if (mockServerIdFile.exists()) {
+ def mockServerId = mockServerIdFile.text
+ try {
+ exec { commandLine 'docker', 'stop', mockServerId }
+ } catch (Exception e) {
+ logger.error("Unable to stop docker container ${mockServerId}", e)
+ if (null != mainException) {
+ mainException.addSuppressed(e);
+ } else {
+ mainException = e;
+ throw e;
+ }
+ } finally {
+ project.delete(mockServerIdFile)
+ }
+ }
+ }
+ } catch (Exception e) {
+ mainException = e
+ throw e;
+ }
+ }
+}
+
+task testDockerfileOfficial(dependsOn: configurations.dockerOfficialSmokeCheckImage) {
+ description = 'Smoke Test Solr docker image built from Dockerfile.official'
+
+ def iidFile = file(imageIdFileOfficial)
+
+ // Ensure that the docker image is re-tested if the image ID changes or the test files change
+ inputs.file(iidFile)
+ inputs.dir(testCasesDir)
+
+ // This test does not respect the inputs that `testDocker` does.
+ // All docker tests will be run, no matter the inputs given.
+
+ def outputDir = "$smokeTestOfficial/test-results"
+ outputs.dir(outputDir)
+
+ doLast {
+ def solrImageId = iidFile.text
+ // for smoke testing Dockerfile.official, we always run all tests.
+ // (if there is a test we don't expect to pass, we should delete it)
+ testDockerImage(solrImageId, outputDir, [] as Set, [] as Set)
+ }
+}
diff --git a/solr/docker/gradle-help.txt b/solr/docker/gradle-help.txt
index 63ad735..8593a69 100644
--- a/solr/docker/gradle-help.txt
+++ b/solr/docker/gradle-help.txt
@@ -78,3 +78,30 @@ Run specific tests:
Exclude specific tests:
EnvVar: SOLR_DOCKER_TESTS_EXCLUDE
Gradle Property: -Psolr.docker.tests.exclude
+
+-------
+The Official Solr Image
+-------
+
+The Official Solr Docker Image is also generated within this module.
+This section should only be used by developers testing that their changes to the Solr project are compatible with the Official image.
+All users should build custom images using the instructions above.
+
+NOTE: All gradle commands for the Official Dockerfile below require the Solr artifacts to be signed with a GPG Key.
+For necessary inputs and properties, please refer to:
+
+gradlew helpGpgSigning
+
+You can use the following command to build an official Solr Dockerfile.
+The Dockerfile will be created at: solr/docker/build/Dockerfile.official
+
+gradlew createDockerfileOfficial
+
+You can also test the official docker image using the following command.
+This will build the official Dockerfile, create a local server to host the local Solr artifacts, and build the Official Solr image using this local server.
+
+gradlew testBuildDockerfileOfficial
+
+You can also run the official Docker image built by the command above through all Solr Docker tests with the following:
+
+gradlew testDockerfileOfficial
diff --git a/solr/docker/Dockerfile b/solr/docker/templates/Dockerfile.body.template
similarity index 64%
rename from solr/docker/Dockerfile
rename to solr/docker/templates/Dockerfile.body.template
index 443a0eb..5b55990 100644
--- a/solr/docker/Dockerfile
+++ b/solr/docker/templates/Dockerfile.body.template
@@ -1,41 +1,28 @@
-# This file can be used to build an (unofficial) Docker image of Apache Solr.
-#
-# The primary purpose of this file, is for use by Solr developers, with a java/gradle development env, who
-# wish to build customized -- or "patched" docker images of Solr. For this type of usecase, this file
-# will be used automatically by gradle to build docker images from your local src.
-# Example:
-# ./gradlew dockerBuild
-#
-# For most Solr users, using this Dockerfile is not recommended: pre-built docker images of Solr are
-# available at https://hub.docker.com/_/solr -- however this file can be used to build docker images from
-# a Solr release artifact -- either from a remote TGZ file, or from an TGZ artifact you have downloaded
-# locally.
-# Example:
-# docker build -f solr-X.Y.Z/docker/Dockerfile https://www.apache.org/dyn/closer.lua/solr/X.Y.Z/solr-X.Y.Z.tgz
-# Example:
-# docker build -f solr-X.Y.Z/docker/Dockerfile - < solr-X.Y.Z.tgz
-
-
-ARG BASE_IMAGE=openjdk:11-jre-slim
-
-FROM $BASE_IMAGE as input
-
-COPY / /opt/
+#-#
+#-# This template is used as the primary body of both "local" and "official" Apache Solr Dockerfiles.
+#-# It contains everything that should be "common" between both files.
+#-#
+#-# ! ! ! NO VARIABLES OR CONDITIONAL LOGIC SHOULD BE NEEDED OR USED IN THIS TEMPLATE ! ! !
+#-# (It exists as a 'template' solely so that this comment can exist)
+#-#
+#-# The pre-reqs for this file (which must be satisfied for any "header" pre-pended to it are that
+#-# '/opt/solr-X.Y.Z' exists (ie: COPY'ed from the build context and/or a downloaded and unpacked solr.tgz)
+#-#
+#-#
+#-#
# remove what we don't want; ensure permissions are right
# TODO; arguably these permissions should have been set correctly previously in the TAR
RUN set -ex; \
(cd /opt; ln -s solr-*/ solr); \
- rm -Rf /opt/solr/docs /opt/solr/docker/Dockerfile /opt/solr/dist/{solr-solrj-*.jar,solrj-lib,solr-test-framework-*.jar,test-framework}; \
+ rm -Rf /opt/solr/docs /opt/solr/docker/Dockerfile* /opt/solr/dist/{solr-solrj-*.jar,solrj-lib,solr-test-framework-*.jar,test-framework}; \
find /opt/solr/ -type d -print0 | xargs -0 chmod 0755; \
find /opt/solr/ -type f -print0 | xargs -0 chmod 0644; \
chmod -R 0755 /opt/solr/docker/scripts /opt/solr/bin /opt/solr/contrib/prometheus-exporter/bin/solr-exporter /opt/solr/server/scripts/cloud-scripts
-FROM $BASE_IMAGE
-
LABEL maintainer="The Apache Lucene/Solr Project"
LABEL repository="https://github.com/apache/lucene-solr"
-
+
# Override the default github URL to provide a mirror for github releases.
ARG GITHUB_URL=github.com
@@ -63,8 +50,6 @@ RUN set -ex; \
groupadd -r --gid "$SOLR_GID" "$SOLR_GROUP"; \
useradd -r --uid "$SOLR_UID" --gid "$SOLR_GID" "$SOLR_USER"
-COPY --from=input /opt/ /opt/
-
RUN set -ex; \
mkdir -p /opt/solr/server/solr/lib /docker-entrypoint-initdb.d; \
cp /opt/solr/bin/solr.in.sh /etc/default/solr.in.sh; \
diff --git a/solr/docker/templates/Dockerfile.local.header.template b/solr/docker/templates/Dockerfile.local.header.template
new file mode 100644
index 0000000..a9173d6
--- /dev/null
+++ b/solr/docker/templates/Dockerfile.local.header.template
@@ -0,0 +1,29 @@
+#-#
+#-# This template is used as the header of "local" Apache Solr Dockerfiles.
+#-#
+#-# #######################################################################
+#-#
+# This file can be used to build an (unofficial) Docker image of Apache Solr.
+#
+# The primary purpose of this file, is for use by Solr developers, with a java/gradle development env, who
+# wish to build customized -- or "patched" docker images of Solr. For this type of usecase, this file
+# will be used automatically by gradle to build docker images from your local src.
+# Example:
+# ./gradlew dockerBuild
+#
+# For most Solr users, using this Dockerfile is not recommended: pre-built docker images of Solr are
+# available at https://hub.docker.com/_/solr -- however this file can be used to build docker images from
+# a Solr release artifact -- either from a remote TGZ file, or from an TGZ artifact you have downloaded
+# locally.
+# Example:
+# docker build -f solr-X.Y.Z/docker/Dockerfile https://www.apache.org/dyn/closer.lua/solr/X.Y.Z/solr-X.Y.Z.tgz
+# Example:
+# docker build -f solr-X.Y.Z/docker/Dockerfile - < solr-X.Y.Z.tgz
+
+
+ARG BASE_IMAGE=_REPLACE_BASE_IMAGE_
+
+FROM $BASE_IMAGE
+
+COPY / /opt/
+
diff --git a/solr/docker/templates/Dockerfile.official.header.template b/solr/docker/templates/Dockerfile.official.header.template
new file mode 100644
index 0000000..a342547
--- /dev/null
+++ b/solr/docker/templates/Dockerfile.official.header.template
@@ -0,0 +1,79 @@
+#-#
+#-# This template is used as the header of "official" Apache Solr Dockerfiles.
+#-#
+#-# #######################################################################
+#-#
+
+FROM _REPLACE_BASE_IMAGE_
+
+# TODO: remove things that exist solely for downstream specialization since Dockerfile.local now exists for that
+# TODO: replace 3rd party keyservers with official Apache Solr KEYS url
+
+ARG SOLR_VERSION="_REPLACE_SOLR_VERSION_"
+ARG SOLR_SHA512="_REPLACE_SOLR_TGZ_SHA_"
+ARG SOLR_KEYS="_REPLACE_RELEASE_MANAGER_GPG_FINGERPRINT_"
+
+# If specified, this will override SOLR_DOWNLOAD_SERVER and all ASF mirrors. Typically used downstream for custom builds
+ARG SOLR_DOWNLOAD_URL
+# TODO: That comment isn't strictly true, if SOLR_DOWNLOAD_URL fails, other mirrors will be attempted
+# TODO: see patch in SOLR-15250 for some example ideas on fixing this to be more strict
+
+# Override the default solr download location with a prefered mirror, e.g.:
+# docker build -t mine --build-arg SOLR_DOWNLOAD_SERVER=http://www-eu.apache.org/dist/lucene/solr .
+ARG SOLR_DOWNLOAD_SERVER
+
+# These should never be overridden except for the purposes of testing the Dockerfile before release
+ARG SOLR_CLOSER_URL="http://www.apache.org/dyn/closer.lua?filename=solr/$SOLR_VERSION/solr-$SOLR_VERSION.tgz&action=download"
+ARG SOLR_DIST_URL="https://www.apache.org/dist/lucene/solr/$SOLR_VERSION/solr-$SOLR_VERSION.tgz"
+ARG SOLR_ARCHIVE_URL="https://archive.apache.org/dist/lucene/solr/$SOLR_VERSION/solr-$SOLR_VERSION.tgz"
+
+RUN set -ex; \
+ apt-get update; \
+ apt-get -y install wget gpg; \
+ rm -rf /var/lib/apt/lists/*; \
+ export GNUPGHOME="/tmp/gnupg_home"; \
+ mkdir -p "$GNUPGHOME"; \
+ chmod 700 "$GNUPGHOME"; \
+ echo "disable-ipv6" >> "$GNUPGHOME/dirmngr.conf"; \
+ for key in $SOLR_KEYS; do \
+ found=''; \
+ for server in \
+ ha.pool.sks-keyservers.net \
+ hkp://keyserver.ubuntu.com:80 \
+ hkp://p80.pool.sks-keyservers.net:80 \
+ pgp.mit.edu \
+ ; do \
+ echo " trying $server for $key"; \
+ gpg --batch --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$key" && found=yes && break; \
+ gpg --batch --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$key" && found=yes && break; \
+ done; \
+ test -z "$found" && echo >&2 "error: failed to fetch $key from several disparate servers -- network issues?" && exit 1; \
+ done; \
+ MAX_REDIRECTS=1; \
+ if [ -n "$SOLR_DOWNLOAD_URL" ]; then \
+ # If a custom URL is defined, we download from non-ASF mirror URL and allow more redirects and skip GPG step
+ # This takes effect only if the SOLR_DOWNLOAD_URL build-arg is specified, typically in downstream Dockerfiles
+ MAX_REDIRECTS=4; \
+ SKIP_GPG_CHECK=true; \
+ elif [ -n "$SOLR_DOWNLOAD_SERVER" ]; then \
+ SOLR_DOWNLOAD_URL="$SOLR_DOWNLOAD_SERVER/$SOLR_VERSION/solr-$SOLR_VERSION.tgz"; \
+ fi; \
+ for url in $SOLR_DOWNLOAD_URL $SOLR_CLOSER_URL $SOLR_DIST_URL $SOLR_ARCHIVE_URL; do \
+ if [ -f "/opt/solr-$SOLR_VERSION.tgz" ]; then break; fi; \
+ echo "downloading $url"; \
+ if wget -t 10 --max-redirect $MAX_REDIRECTS --retry-connrefused -nv "$url" -O "/opt/solr-$SOLR_VERSION.tgz"; then break; else rm -f "/opt/solr-$SOLR_VERSION.tgz"; fi; \
+ done; \
+ if [ ! -f "/opt/solr-$SOLR_VERSION.tgz" ]; then echo "failed all download attempts for solr-$SOLR_VERSION.tgz"; exit 1; fi; \
+ if [ -z "$SKIP_GPG_CHECK" ]; then \
+ echo "downloading $SOLR_ARCHIVE_URL.asc"; \
+ wget -nv "$SOLR_ARCHIVE_URL.asc" -O "/opt/solr-$SOLR_VERSION.tgz.asc"; \
+ echo "$SOLR_SHA512 */opt/solr-$SOLR_VERSION.tgz" | sha512sum -c -; \
+ (>&2 ls -l "/opt/solr-$SOLR_VERSION.tgz" "/opt/solr-$SOLR_VERSION.tgz.asc"); \
+ gpg --batch --verify "/opt/solr-$SOLR_VERSION.tgz.asc" "/opt/solr-$SOLR_VERSION.tgz"; \
+ else \
+ echo "Skipping GPG validation due to non-Apache build"; \
+ fi; \
+ { command -v gpgconf; gpgconf --kill all || :; }; \
+ rm -r "$GNUPGHOME"; \
+ tar -C /opt --extract --file "/opt/solr-$SOLR_VERSION.tgz";
+
diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle
index 0fb8629..e79f3b2 100644
--- a/solr/packaging/build.gradle
+++ b/solr/packaging/build.gradle
@@ -42,6 +42,7 @@ configurations {
docs
docker
solrTgz
+ solrTgzSignature
solrZip
}
@@ -193,6 +194,11 @@ task signDistTar(type: Sign) {
dependsOn failUnlessGpgKeyProperty
sign configurations.solrTgz
}
+artifacts {
+ solrTgzSignature(signDistTar.signatureFiles.singleFile) {
+ builtBy signDistTar
+ }
+}
task signDistZip(type: Sign) {
dependsOn failUnlessGpgKeyProperty
sign configurations.solrZip