From 211ac6fe23d33fe4bd38fb27a85add83d7a2738d Mon Sep 17 00:00:00 2001 From: "jonathan.janos@mongodb.com" Date: Mon, 29 Jun 2020 13:30:39 -0400 Subject: [PATCH 1/2] Initial commit for the MongoDB Enterprise Ops Manager Init container --- Dockerfile | 40 +++++ Jenkinsfile | 3 + LICENSE | 4 + README.md | 14 +- mmsconfiguration/edit_mms_configuration.go | 155 ++++++++++++++++++ .../edit_mms_configuration_test.go | 82 +++++++++ scripts/docker-entry-point.sh | 56 +++++++ 7 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 Jenkinsfile create mode 100644 LICENSE create mode 100755 mmsconfiguration/edit_mms_configuration.go create mode 100755 mmsconfiguration/edit_mms_configuration_test.go create mode 100755 scripts/docker-entry-point.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b486140 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# Dockerfile for Ops Manager init container. + +ARG BASE_REGISTRY=nexus-docker-secure.levelup-nexus.svc.cluster.local:18082 +ARG BASE_IMAGE=redhat/ubi/ubi7 +ARG BASE_TAG=7.8 + +FROM golang:1.13 as builder + +USER root +RUN mkdir /build +ADD . /build/ +WORKDIR /build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -a -i -o ./output/mmsconfiguration ./mmsconfiguration + +##### + +FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} + +ARG VERSION + +LABEL name="MongoDB Enterprise Ops Manager Init" \ + maintainer="support@mongodb.com" \ + vendor="MongoDB" \ + version="mongodb-enterprise-init-ops-manager-${VERSION}" \ + release="1" \ + summary="MongoDB Enterprise Ops Manager Init Image" \ + description="Startup Scripts for MongoDB Enterprise Ops Manager" + +RUN mkdir /scripts +COPY --from=builder /build/output/mmsconfiguration /scripts/ +COPY scripts/docker-entry-point.sh /scripts/ + +RUN mkdir -p /licenses +COPY LICENSE /licenses/mongodb-enterprise-ops-manager + +USER 2000 +ENTRYPOINT [ "/bin/cp", "-f", "/scripts/docker-entry-point.sh", "/scripts/mmsconfiguration", "/opt/scripts/" ] + +HEALTHCHECK --timeout=30s CMD ls /scripts/docker-entry-point.sh || exit 1 + diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..20beed0 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,3 @@ +@Library('DCCSCR@master') _ +dccscrPipeline(version: "1.5.3") + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b5ca950 --- /dev/null +++ b/LICENSE @@ -0,0 +1,4 @@ +Usage of the MongoDB Enterprise Operator for Kubernetes indicates +agreement with the MongoDB Development, Test, and Evaluation Agreement + +* https://www.mongodb.com/legal/evaluation-agreement diff --git a/README.md b/README.md index ea3706c..32031a8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ -# MongoDB Ops Manager Init +# MongoDB Enterprise Ops Manager - Init Container # + +Init container for MongoDB Enterprise Ops Manager. This container image is used exclusively by the MongoDB Enterprise Kubernetes Operator to deploy MongoDB Ops Manager to Kubernetes or OpenShift clusters. + +For more information about MongoDB Ops Manager, please visit . + +Information about MongoDB can be found at . + +## Documentation ## + +Documentation for MongoDB Ops Manager is available at . + +Documentation for the MongoDB Enterprise Kubernetes Operator is available at . diff --git a/mmsconfiguration/edit_mms_configuration.go b/mmsconfiguration/edit_mms_configuration.go new file mode 100755 index 0000000..faa8626 --- /dev/null +++ b/mmsconfiguration/edit_mms_configuration.go @@ -0,0 +1,155 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "strings" +) + +const ( + mmsJvmParamsVar = "JAVA_MMS_UI_OPTS" + backupDaemonJvmParamsVar = "JAVA_DAEMON_OPTS" + omPropertyPrefix = "OM_PROP_" + lineBreak = "\n" + commentPrefix = "#" + propOverwriteFmt = "%s=\"${%s} %s\"" + backupDaemon = "BACKUP_DAEMON" +) + +func updateConfFile(confFile string) error { + confFilePropertyName := mmsJvmParamsVar + if _, isBackupDaemon := os.LookupEnv(backupDaemon); isBackupDaemon { + confFilePropertyName = backupDaemonJvmParamsVar + } + + customJvmParamsVar := "CUSTOM_" + confFilePropertyName + jvmParams, jvmParamsEnvVarExists := os.LookupEnv(customJvmParamsVar) + + if !jvmParamsEnvVarExists || jvmParams == "" { + fmt.Printf("%s not specified, not modifying %s\n", customJvmParamsVar, confFile) + return nil + } + + newMmsJvmParams := fmt.Sprintf(propOverwriteFmt, confFilePropertyName, confFilePropertyName, jvmParams) + fmt.Printf("Appending %s to %s\n", newMmsJvmParams, confFile) + + err := appendLinesToFile(confFile, getJvmParamDocString()+newMmsJvmParams+lineBreak) + return err +} + +func updatePropertiesFile(propertiesFile string) error { + newProperties := getOmPropertiesFromEnvVars() + + // If there are no exported mms properties, we can stop here + if len(newProperties) == 0 { + return nil + } + + lines, err := readLinesFromFile(propertiesFile) + if err != nil { + return err + } + + lines = updateMmsProperties(lines, newProperties) + fmt.Printf("Updating configuration properties file %s\n", propertiesFile) + err = writeLinesToFile(propertiesFile, lines) + return err +} + +func readLinesFromFile(name string) ([]string, error) { + input, err := ioutil.ReadFile(name) + if err != nil { + return nil, fmt.Errorf("error reading file %s: %v", name, err) + } + return strings.Split(string(input), lineBreak), nil +} + +func writeLinesToFile(name string, lines []string) error { + output := strings.Join(lines, lineBreak) + + err := ioutil.WriteFile(name, []byte(output), 0775) + if err != nil { + return fmt.Errorf("error writing to file %s: %v", name, err) + } + return nil +} + +func appendLinesToFile(name string, lines string) error { + f, err := os.OpenFile(name, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("error opening file %s: %v", name, err) + } + + if _, err = f.WriteString(lines); err != nil { + return fmt.Errorf("error writing to file %s: %v", name, err) + } + + err = f.Close() + return err + +} + +func getOmPropertiesFromEnvVars() map[string]string { + props := map[string]string{} + for _, pair := range os.Environ() { + if !strings.HasPrefix(pair, omPropertyPrefix) { + continue + } + + p := strings.SplitN(pair, "=", 2) + key := strings.Replace(p[0], omPropertyPrefix, "", 1) + key = strings.ReplaceAll(key, "_", ".") + props[key] = p[1] + } + return props +} + +func updateMmsProperties(lines []string, newProperties map[string]string) []string { + seenProperties := map[string]bool{} + + // Overwrite existing properties + for i, line := range lines { + if strings.HasPrefix(line, commentPrefix) || !strings.Contains(line, "=") { + continue + } + + key := strings.Split(line, "=")[0] + if newVal, ok := newProperties[key]; ok { + lines[i] = fmt.Sprintf("%s=%s", key, newVal) + fmt.Printf("Setting %s=%s\n", key, newVal) + seenProperties[key] = true + } + } + + // Add new properties + for key, val := range newProperties { + if _, ok := seenProperties[key]; !ok { + lines = append(lines, fmt.Sprintf("%s=%s", key, val)) + fmt.Printf("Added %s=%s\n", key, val) + } + } + return lines +} + +func getJvmParamDocString() string { + commentMarker := strings.Repeat("#", 55) + return fmt.Sprintf("%s\n## This is the custom JVM configuration set by the Operator\n%s\n\n", commentMarker, commentMarker) +} + +func main() { + if len(os.Args) < 3 { + fmt.Printf("Incorrect arguments %s, must specify path to conf file and path to properties file"+lineBreak, os.Args[1:]) + os.Exit(1) + } + confFile := os.Args[1] + propertiesFile := os.Args[2] + if err := updateConfFile(confFile); err != nil { + fmt.Println(err) + os.Exit(1) + } + if err := updatePropertiesFile(propertiesFile); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/mmsconfiguration/edit_mms_configuration_test.go b/mmsconfiguration/edit_mms_configuration_test.go new file mode 100755 index 0000000..2812ab5 --- /dev/null +++ b/mmsconfiguration/edit_mms_configuration_test.go @@ -0,0 +1,82 @@ +package main + +import ( + "fmt" + "io/ioutil" + "math/rand" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEditMmsConfiguration_UpdateConfFile_Mms(t *testing.T) { + confFile := _createTestConfFile() + _ = os.Setenv("CUSTOM_JAVA_MMS_UI_OPTS", "-Xmx4000m -Xms4000m") + err := updateConfFile(confFile) + assert.NoError(t, err) + updatedContent := _readLinesFromFile(confFile) + assert.Equal(t, updatedContent[7], "JAVA_MMS_UI_OPTS=\"${JAVA_MMS_UI_OPTS} -Xmx4000m -Xms4000m\"") +} + +func TestEditMmsConfiguration_UpdateConfFile_BackupDaemon(t *testing.T) { + confFile := _createTestConfFile() + _ = os.Setenv("BACKUP_DAEMON", "something") + _ = os.Setenv("CUSTOM_JAVA_DAEMON_OPTS", "-Xmx4000m -Xms4000m") + err := updateConfFile(confFile) + assert.NoError(t, err) + updatedContent := _readLinesFromFile(confFile) + assert.Equal(t, updatedContent[7], "JAVA_DAEMON_OPTS=\"${JAVA_DAEMON_OPTS} -Xmx4000m -Xms4000m\"") +} + +func TestEditMmsConfiguration_UpdatePropertiesFile(t *testing.T) { + val := fmt.Sprintf("test%d", rand.Intn(1000)) + key := "OM_PROP_test_edit_mms_configuration_get_om_props" + _ = os.Setenv(key, val) + props := getOmPropertiesFromEnvVars() + assert.Equal(t, props["test.edit.mms.configuration.get.om.props"], val) + _ = os.Unsetenv(key) +} + +func TestEditMmsConfiguration_GetOmPropertiesFromEnvVars(t *testing.T) { + _ = os.Setenv("OM_PROP_mms_test_prop", "somethingNew") + _ = os.Setenv("OM_PROP_mms_test_prop_new", "400") + + propFile := _createTestPropertiesFile() + err := updatePropertiesFile(propFile) + assert.NoError(t, err) + + updatedContent := _readLinesFromFile(propFile) + assert.Equal(t, updatedContent[0], "mms.prop=1234") + assert.Equal(t, updatedContent[1], "mms.test.prop5=") + assert.Equal(t, updatedContent[2], "mms.test.prop=somethingNew") + assert.Equal(t, updatedContent[3], "mms.test.prop.new=400") +} + +func _createTestConfFile() string { + contents := "JAVA_MMS_UI_OPTS=\"${JAVA_MMS_UI_OPTS} -Xmx4352m -Xss328k -Xms4352m -XX:NewSize=600m -Xmn1500m -XX:ReservedCodeCacheSize=128m -XX:-OmitStackTraceInFastThrow\"\n" + contents += "JAVA_DAEMON_OPTS= \"${JAVA_DAEMON_OPTS} -DMONGO.BIN.PREFIX=\"\n\n" + return _writeTempFileWithContent(contents, "conf") +} + +func _createTestPropertiesFile() string { + contents := "mms.prop=1234\nmms.test.prop5=\nmms.test.prop=something" + return _writeTempFileWithContent(contents, "prop") +} + +func _readLinesFromFile(name string) []string { + content, _ := ioutil.ReadFile(name) + return strings.Split(string(content), "\n") +} + +func _writeTempFileWithContent(content string, prefix string) string { + tmpfile, _ := ioutil.TempFile("", prefix) + + _, _ = tmpfile.WriteString(content) + + _ = tmpfile.Close() + + return tmpfile.Name() + +} diff --git a/scripts/docker-entry-point.sh b/scripts/docker-entry-point.sh new file mode 100755 index 0000000..669380c --- /dev/null +++ b/scripts/docker-entry-point.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# the function reacting on SIGTERM command sent by the container on its shutdown. Redirects the signal +# to the child process ("tail" in this case) +cleanup () { + echo "Caught SIGTERM signal." + kill -TERM "$child" +} + +# we need to change the Home directory for current bash so that the gen key was found correctly +# (the key is searched in "${HOME}/.mongodb-mms/gen.key") +HOME=${MMS_HOME} + +# Execute script that updates properties and conf file used to start ops manager +echo "Updating configuration properties file ${MMS_PROP_FILE} and conf file ${MMS_CONF_FILE}" +/opt/scripts/mmsconfiguration ${MMS_CONF_FILE} ${MMS_PROP_FILE} + +if [[ -z ${BACKUP_DAEMON+x} ]]; then + echo "Starting Ops Manager" + ${MMS_HOME}/bin/mongodb-mms start_mms || { + echo "Startup of Ops Manager failed with code $?" + if [[ -f ${MMS_LOG_DIR}/mms0-startup.log ]]; then + echo + echo "mms0-startup.log:" + echo + cat "${MMS_LOG_DIR}/mms0-startup.log" + fi + if [[ -f ${MMS_LOG_DIR}/mms0.log ]]; then + echo + echo "mms0.log:" + echo + cat "${MMS_LOG_DIR}/mms0.log" + fi + if [[ -f ${MMS_LOG_DIR}/mms-migration.log ]]; then + echo + echo "mms-migration.log" + echo + cat "${MMS_LOG_DIR}/mms-migration.log" + fi + exit 1 + } + + trap cleanup SIGTERM + tail -F -n 1000 "${MMS_LOG_DIR}/mms0.log" "${MMS_LOG_DIR}/mms0-startup.log" "${MMS_LOG_DIR}/mms-migration.log" & +else + echo "Starting Ops Manager Backup Daemon" + ${MMS_HOME}/bin/mongodb-mms start_backup_daemon + trap cleanup SIGTERM + + tail -F "${MMS_LOG_DIR}/daemon.log" & +fi + +child=$! +wait "$child" -- GitLab From 9e5a597aacf4cc5f739695d024d415232ec64762 Mon Sep 17 00:00:00 2001 From: "jonathan.janos@mongodb.com" Date: Mon, 6 Jul 2020 14:27:25 -0400 Subject: [PATCH 2/2] updated per feedback. Added go.lang to download.json, consolidated RUN statements in Dockerfile. --- Dockerfile | 4 ++-- download.json | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100755 download.json diff --git a/Dockerfile b/Dockerfile index b486140..fddbaba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,11 +26,11 @@ LABEL name="MongoDB Enterprise Ops Manager Init" \ summary="MongoDB Enterprise Ops Manager Init Image" \ description="Startup Scripts for MongoDB Enterprise Ops Manager" -RUN mkdir /scripts +RUN mkdir /scripts && mkdir -p /licenses + COPY --from=builder /build/output/mmsconfiguration /scripts/ COPY scripts/docker-entry-point.sh /scripts/ -RUN mkdir -p /licenses COPY LICENSE /licenses/mongodb-enterprise-ops-manager USER 2000 diff --git a/download.json b/download.json new file mode 100755 index 0000000..92e0570 --- /dev/null +++ b/download.json @@ -0,0 +1,8 @@ +{ + "resources": [ + { + "url": "docker://docker.io/golang@sha256:2841da2227dc15e18ca5ec1e95f3cb166005361c0eb0313ce82d2e5ccff116dd", + "tag": "golang:1.13" + } + ] +} -- GitLab