Compare commits
153 Commits
v9.1.0
...
old-master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aab07c6ed | ||
|
|
556ac6aec2 | ||
|
|
950aecf300 | ||
|
|
492c6876ca | ||
|
|
9896e53cba | ||
|
|
f6ea588349 | ||
|
|
60fe631616 | ||
|
|
221796f746 | ||
|
|
97941bc87c | ||
|
|
b8a7167c4e | ||
|
|
dc00231ec1 | ||
|
|
8a8ea6c6a9 | ||
|
|
9714d0d513 | ||
|
|
6691438fa1 | ||
|
|
426c6c3b9a | ||
|
|
8088cb2902 | ||
|
|
a2ce23aa96 | ||
|
|
95ba16cdb2 | ||
|
|
83fe77b222 | ||
|
|
7ab30723cd | ||
|
|
3f9fc0eaa5 | ||
|
|
ee4351e55d | ||
|
|
ebf55608d7 | ||
|
|
b3fd5f7562 | ||
|
|
3c9ec5f14c | ||
|
|
81c0b70a6f | ||
|
|
a6f307c6b5 | ||
|
|
6f677e2a59 | ||
|
|
612fe3a9ec | ||
|
|
0a9c745d96 | ||
|
|
b64c060ef4 | ||
|
|
8e22763f16 | ||
|
|
40b64e620e | ||
|
|
700cc53c07 | ||
|
|
44d75b169c | ||
|
|
c0bf371b9e | ||
|
|
e362644a55 | ||
|
|
c079c1b60d | ||
|
|
4a3bdf3b53 | ||
|
|
5ff269d2e3 | ||
|
|
3fb2d3fe61 | ||
|
|
6c72c894f7 | ||
|
|
63af43f19d | ||
|
|
64bb5aed8a | ||
|
|
f05a7d3eaf | ||
|
|
723fe2b998 | ||
|
|
44d0e0a432 | ||
|
|
33defc0fc9 | ||
|
|
d69befed71 | ||
|
|
0934289b61 | ||
|
|
f6231cd51c | ||
|
|
d5f04bc470 | ||
|
|
d8cbf4566e | ||
|
|
493bc7bfd4 | ||
|
|
1fa4f6f148 | ||
|
|
ce664dd654 | ||
|
|
f8e057a1d6 | ||
|
|
efd550822d | ||
|
|
d4df05fd2a | ||
|
|
194b04ac13 | ||
|
|
6848038165 | ||
|
|
0d3e177147 | ||
|
|
13f620f21a | ||
|
|
d4a81741cc | ||
|
|
0047301335 | ||
|
|
5ef532d2c1 | ||
|
|
281cdc4578 | ||
|
|
d68c051104 | ||
|
|
c5a52e616c | ||
|
|
c441de7d26 | ||
|
|
a194545f08 | ||
|
|
4f57d1bae2 | ||
|
|
cc0f072908 | ||
|
|
d834ac7c9c | ||
|
|
2dbee560fe | ||
|
|
17d3238161 | ||
|
|
c08ca2e79f | ||
|
|
84df0e8362 | ||
|
|
cc213f429f | ||
|
|
c29159dd38 | ||
|
|
f345ccf920 | ||
|
|
d1b1cfc5d8 | ||
|
|
a19c455ea4 | ||
|
|
694b31d6e8 | ||
|
|
33f82d76ff | ||
|
|
77319629fe | ||
|
|
d9c70c48c5 | ||
|
|
599f5f4b53 | ||
|
|
6840a575f9 | ||
|
|
1b8c816f57 | ||
|
|
9a8ff9b524 | ||
|
|
f3c858184f | ||
|
|
43676049b7 | ||
|
|
df6ce917c2 | ||
|
|
d3eb6e0d3d | ||
|
|
2bfdd51a01 | ||
|
|
be11b3cda1 | ||
|
|
525ff82fe7 | ||
|
|
3e07814bf6 | ||
|
|
b1daacf377 | ||
|
|
9c8b3825be | ||
|
|
4145f077b6 | ||
|
|
c063ddd67d | ||
|
|
5f000ff891 | ||
|
|
e33710eb00 | ||
|
|
2fdd2c51ff | ||
|
|
b73ad12011 | ||
|
|
b276e0b4ef | ||
|
|
a3c0af9648 | ||
|
|
1208a5d08b | ||
|
|
fe8a87b39f | ||
|
|
9a34e9b15c | ||
|
|
78ce84b3a1 | ||
|
|
6d11b0d8ae | ||
|
|
d0fce28ef3 | ||
|
|
31f604cc47 | ||
|
|
c6a921efee | ||
|
|
822a073c4e | ||
|
|
a854c4c627 | ||
|
|
3989661778 | ||
|
|
9a7d44fef6 | ||
|
|
f73347a9cf | ||
|
|
ad1b2db1fd | ||
|
|
11b94de1c0 | ||
|
|
1906896038 | ||
|
|
d5ec1fa505 | ||
|
|
e03ceff7c9 | ||
|
|
64f5ce3624 | ||
|
|
b921bb82d3 | ||
|
|
92e57f2cb1 | ||
|
|
0281e52b47 | ||
|
|
8bdba41f75 | ||
|
|
aa04229d85 | ||
|
|
9e04bfc68a | ||
|
|
8fcdfeb1c4 | ||
|
|
a2d3abfb86 | ||
|
|
e66bcfd77f | ||
|
|
d2ea8d4f06 | ||
|
|
fc19776c04 | ||
|
|
3f07b1e77f | ||
|
|
00568adc6d | ||
|
|
ba1b4f8ef9 | ||
|
|
9b98555886 | ||
|
|
dbfc47591e | ||
|
|
b087f37505 | ||
|
|
08299dd0d1 | ||
|
|
b8f96d0148 | ||
|
|
6bb669d6ac | ||
|
|
80384a65a2 | ||
|
|
c11c0a2bf4 | ||
|
|
29dfe38d32 | ||
|
|
89eae35724 | ||
|
|
b751640b79 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -7,3 +7,8 @@ build
|
||||
coverage
|
||||
downloads
|
||||
incubating/mqipt/ms81*
|
||||
vendor/github.com/prometheus/client_model/bin/
|
||||
vendor/github.com/prometheus/client_model/.classpath
|
||||
vendor/github.com/prometheus/client_model/.project
|
||||
vendor/github.com/prometheus/client_model/.settings*
|
||||
gosec_results.json
|
||||
|
||||
58
.travis.yml
58
.travis.yml
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2018
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -12,6 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
dist: xenial
|
||||
|
||||
sudo: required
|
||||
language: go
|
||||
|
||||
@@ -27,43 +29,49 @@ cache:
|
||||
directories:
|
||||
- downloads
|
||||
|
||||
env:
|
||||
- BASE_IMAGE=ubuntu:16.04
|
||||
# Commented out temporarily until Issue 166 is resolved
|
||||
# - BASE_IMAGE=centos:latest
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- if: type IN (pull_request)
|
||||
env: DOCKER_DOWNGRADE="docker save -o images.tar mqadvanced-server-dev mq-dev-jms-test &&
|
||||
sudo apt-get autoremove -y docker-ce &&
|
||||
curl -fsSL \"https://apt.dockerproject.org/gpg\" | sudo apt-key add - &&
|
||||
sudo apt-add-repository \"deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -cs) main\" &&
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install docker-engine=1.12.6-0~ubuntu-$(lsb_release -cs) &&
|
||||
docker load -q -i images.tar &&
|
||||
export DOCKER_API_VERSION=\"1.24\""
|
||||
- env: DOCKER_DOWNGRADE="echo nothing to be done"
|
||||
- stage: build and test
|
||||
env:
|
||||
- BASE_IMAGE=ubuntu:16.04
|
||||
- DOCKER_DOWNGRADE="echo nothing to be done"
|
||||
# TEMPORARY removal of Docker 1.12 test, due to errors from apt repository
|
||||
# - if: type IN (pull_request) OR tag IS present
|
||||
# env:
|
||||
# - BASE_IMAGE=ubuntu:16.04
|
||||
# - DOCKER_DOWNGRADE="docker save -o images.tar mqadvanced-server-dev mq-dev-jms-test &&
|
||||
# sudo apt-get autoremove -y docker-ce &&
|
||||
# curl -fsSL \"https://apt.dockerproject.org/gpg\" | sudo apt-key add - &&
|
||||
# sudo apt-add-repository \"deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -cs) main\" &&
|
||||
# sudo apt-get update &&
|
||||
# sudo apt-get install docker-engine=1.12.6-0~ubuntu-$(lsb_release -cs) &&
|
||||
# docker load -q -i images.tar &&
|
||||
# export DOCKER_API_VERSION=\"1.24\""
|
||||
|
||||
before_install:
|
||||
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
||||
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -y install docker-ce
|
||||
- curl https://glide.sh/get | sh
|
||||
- sudo curl -Lo /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64
|
||||
- sudo chmod +x /usr/local/bin/dep
|
||||
- ./install-build-deps-ubuntu.sh
|
||||
|
||||
install:
|
||||
- echo nothing
|
||||
|
||||
script:
|
||||
before_script:
|
||||
- echo 'Downloading Go dependencies...' && echo -en 'travis_fold:start:deps\\r'
|
||||
- make deps
|
||||
- echo -en 'travis_fold:end:deps\\r'
|
||||
- echo 'Building Developer image...' && echo -en 'travis_fold:start:build-devserver\\r'
|
||||
- make build-devserver
|
||||
- echo -en 'travis_fold:end:build-devserver\\r'
|
||||
- echo 'Building Developer JMS test image...' && echo -en 'travis_fold:start:build-devjmstest\\r'
|
||||
- make build-devjmstest
|
||||
- echo -en 'travis_fold:end:build-devjmstest\\r'
|
||||
|
||||
script:
|
||||
- echo 'Downgrading Docker (if necessary)...' && echo -en 'travis_fold:start:docker-downgrade\\r'
|
||||
- eval "$DOCKER_DOWNGRADE"
|
||||
- echo -en 'travis_fold:end:docker-downgrade\\r'
|
||||
- echo 'Testing Developer image...' && echo -en 'travis_fold:start:test-devserver\\r'
|
||||
- make test-devserver
|
||||
- echo -en 'travis_fold:end:test-devserver\\r'
|
||||
|
||||
after_success:
|
||||
- go get golang.org/x/lint/golint
|
||||
- make lint
|
||||
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -1,5 +1,55 @@
|
||||
# Change log
|
||||
|
||||
## 9.1.3.0 (2019-07-19)
|
||||
|
||||
* Updated to MQ version 9.1.3.0
|
||||
* Allow generation of TLS certificate with given hostname
|
||||
* Fixes for the following issues:
|
||||
* `MQ_EPHEMERAL_PREFIX` UNIX sockets fix
|
||||
* Fix Makefile for Windows
|
||||
* Use -a option on crtmqdir
|
||||
* Remove check for certificate environment variable
|
||||
|
||||
## 9.1.2.0-UBI (2019-06-21)
|
||||
|
||||
**Breaking changes**:
|
||||
* UID of the mqm user is now 888. You need to run the container with an entrypoint of `runmqserver -i` under the root user to update any existing files.
|
||||
* MQSC files supplied will be verified before being run. Files containing invalid MQSC will cause the container to fail to start
|
||||
|
||||
**Other changes**:
|
||||
* Security fixes
|
||||
* Web console added to production image
|
||||
* Container built on RedHat host
|
||||
|
||||
## 9.1.2.0 (2019-03-21)
|
||||
|
||||
* Updated to MQ version 9.1.2.0
|
||||
* Now runs using the "mqm" user instead of root. See new [security doc](https://github.com/ibm-messaging/mq-container/blob/master/docs/security.md)
|
||||
* New [IGNSTATE](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.pro.doc/q132310_.htm#q132310___ignstateparm) parameter used in default developer config
|
||||
* Termination log moved from `/dev/termination-log` to `/run/termination-log`, to make permissions easier to handle
|
||||
* Fixes for the following issues:
|
||||
* Brackets no longer appear in termination log
|
||||
* Test timeouts weren't being used correctly
|
||||
* Building on subscribed and unsubscribed hosts ([#273](https://github.com/ibm-messaging/mq-container/pull/273))
|
||||
* Gosec failures ([#286](https://github.com/ibm-messaging/mq-container/pull/286))
|
||||
* Security fix for perl-base ([#253](https://github.com/ibm-messaging/mq-container/pull/253))
|
||||
|
||||
## 9.1.1.0 (2018-11-30)
|
||||
|
||||
* Updated to MQ version 9.1.1.0
|
||||
* Created seperate RedHat Makefile for building images on RedHat machines with buildah
|
||||
* Enabled REST messaging capability for app user.
|
||||
* Added support for container supplementary groups
|
||||
* Removed IBM MQ version 9.0.5 details.
|
||||
* Added additional Diagnostics ([#203](https://github.com/ibm-messaging/mq-container/pull/203))
|
||||
* Implementted GOSec to perform code scans for security vulnerabilities. (([#227](https://github.com/ibm-messaging/mq-container/pull/227)))
|
||||
* Removed Queue manager create option from the MQ Console.
|
||||
* Fixes for the following issues:
|
||||
* Check explicitly for `/mnt/mqm` ([#175](https://github.com/ibm-messaging/mq-container/pull/175))
|
||||
* Force string output in chkmqhealthy ([#174](https://github.com/ibm-messaging/mq-container/pull/174))
|
||||
* Use -aG not -G when adding a group for a user
|
||||
* Security fixes for libsystemd0 systemd systemd-sysv & libudev1
|
||||
|
||||
## 9.1.0.0 (2018-07-23)
|
||||
|
||||
* Updated to MQ version 9.1.0.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2015, 2018
|
||||
# © Copyright IBM Corporation 2015, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -12,24 +12,37 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG BASE_IMAGE=ubuntu:16.04
|
||||
ARG BUILDER_IMAGE=mq-golang-sdk:9.0.5.0-x86_64-ubuntu-16.04
|
||||
|
||||
ARG BASE_IMAGE=registry.access.redhat.com/ubi7/ubi-minimal
|
||||
ARG BASE_TAG=7.7-98
|
||||
###############################################################################
|
||||
# Build stage to build Go code
|
||||
###############################################################################
|
||||
FROM $BUILDER_IMAGE as builder
|
||||
WORKDIR /go/src/github.com/ibm-messaging/mq-container/
|
||||
FROM registry.access.redhat.com/devtools/go-toolset-7-rhel7 as builder
|
||||
# FROM docker.io/centos/go-toolset-7-centos7 as builder
|
||||
# The URL to download the MQ installer from in tar.gz format
|
||||
# This assumes an archive containing the MQ RPM install packages
|
||||
ARG MQ_URL="https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev912_linux_x86-64.tar.gz"
|
||||
ARG IMAGE_REVISION="Not specified"
|
||||
ARG IMAGE_CREATED="Not specified"
|
||||
ARG IMAGE_SOURCE="Not specified"
|
||||
ARG IMAGE_TAG="Not specified"
|
||||
ARG MQM_UID=888
|
||||
USER 0
|
||||
COPY install-mq.sh /usr/local/bin/
|
||||
RUN chmod a+x /usr/local/bin/install-mq.sh \
|
||||
&& sleep 1 \
|
||||
&& MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesSDK-*.rpm MQSeriesSamples*.rpm" install-mq.sh $MQM_UID
|
||||
WORKDIR /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/
|
||||
COPY cmd/ ./cmd
|
||||
COPY internal/ ./internal
|
||||
COPY vendor/ ./vendor
|
||||
RUN go build -ldflags "-X \"main.ImageCreated=$IMAGE_CREATED\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\"" ./cmd/runmqserver/
|
||||
ENV PATH="${PATH}:/opt/rh/go-toolset-7/root/usr/bin" \
|
||||
CGO_CFLAGS="-I/opt/mqm/inc/" \
|
||||
CGO_LDFLAGS_ALLOW="-Wl,-rpath.*"
|
||||
RUN go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\" -X \"main.ImageTag=$IMAGE_TAG\"" ./cmd/runmqserver/
|
||||
RUN go build ./cmd/chkmqready/
|
||||
RUN go build ./cmd/chkmqhealthy/
|
||||
# Run all unit tests
|
||||
RUN go build ./cmd/runmqdevserver/
|
||||
RUN go test -v ./cmd/runmqdevserver/...
|
||||
RUN go test -v ./cmd/runmqserver/
|
||||
RUN go test -v ./cmd/chkmqready/
|
||||
RUN go test -v ./cmd/chkmqhealthy/
|
||||
@@ -39,37 +52,101 @@ RUN go vet ./cmd/... ./internal/...
|
||||
###############################################################################
|
||||
# Main build stage, to build MQ image
|
||||
###############################################################################
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
# The URL to download the MQ installer from in tar.gz format
|
||||
# This assumes an archive containing the MQ Debian (.deb) install packages
|
||||
ARG MQ_URL
|
||||
|
||||
FROM $BASE_IMAGE:$BASE_TAG AS mq-server
|
||||
# The MQ packages to install - see install-mq.sh for default value
|
||||
ARG MQ_PACKAGES
|
||||
|
||||
ARG MQ_URL="https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev912_linux_x86-64.tar.gz"
|
||||
ARG MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesWeb*.rpm MQSeriesAMS-*.rpm"
|
||||
#ARG MQ_PACKAGES="ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-web ibmmq-ams"
|
||||
ARG MQM_UID=888
|
||||
ARG BASE_IMAGE
|
||||
ARG BASE_TAG
|
||||
LABEL summary="IBM MQ Advanced Server"
|
||||
LABEL description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the world’s most successful enterprises"
|
||||
LABEL vendor="IBM"
|
||||
LABEL distribution-scope="private"
|
||||
LABEL authoritative-source-url="https://www.ibm.com/software/passportadvantage/"
|
||||
LABEL url="https://www.ibm.com/products/mq/advanced"
|
||||
LABEL io.openshift.tags="mq messaging"
|
||||
LABEL io.k8s.display-name="IBM MQ Advanced Server"
|
||||
LABEL io.k8s.description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the world’s most successful enterprises"
|
||||
LABEL base-image=$BASE_IMAGE
|
||||
LABEL base-image-release=$BASE_TAG
|
||||
COPY install-mq.sh /usr/local/bin/
|
||||
|
||||
COPY install-mq-server-prereqs.sh /usr/local/bin/
|
||||
# Install MQ. To avoid a "text file busy" error here, we sleep before installing.
|
||||
RUN chmod u+x /usr/local/bin/install-mq.sh \
|
||||
RUN env && chmod u+x /usr/local/bin/install-*.sh \
|
||||
&& sleep 1 \
|
||||
&& install-mq.sh
|
||||
|
||||
&& install-mq-server-prereqs.sh $MQM_UID \
|
||||
&& install-mq.sh $MQM_UID
|
||||
# Create a directory for runtime data from runmqserver
|
||||
RUN mkdir -p /run/runmqserver \
|
||||
&& chown mqm:mqm /run/runmqserver
|
||||
|
||||
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
|
||||
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/chkmq* /usr/local/bin/
|
||||
COPY --from=builder /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
|
||||
COPY --from=builder /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/chkmq* /usr/local/bin/
|
||||
COPY NOTICES.txt /opt/mqm/licenses/notices-container.txt
|
||||
|
||||
# Copy web XML files
|
||||
COPY web /etc/mqm/web
|
||||
COPY etc/mqm/*.tpl /etc/mqm/
|
||||
RUN chmod ug+x /usr/local/bin/runmqserver \
|
||||
&& chown mqm:mqm /usr/local/bin/*mq* \
|
||||
&& chmod ug+xs /usr/local/bin/chkmq*
|
||||
|
||||
&& chmod ug+xs /usr/local/bin/chkmq* \
|
||||
&& chown -R mqm:mqm /etc/mqm/* \
|
||||
&& install --directory --mode 0775 --owner mqm --group root /run/runmqserver \
|
||||
&& touch /run/termination-log \
|
||||
&& chown mqm:root /run/termination-log \
|
||||
&& chmod 0660 /run/termination-log
|
||||
# Always use port 1414 for MQ & 9157 for the metrics
|
||||
EXPOSE 1414 9157
|
||||
|
||||
EXPOSE 1414 9157 9443
|
||||
ENV LANG=en_US.UTF-8 AMQ_DIAGNOSTIC_MSG_SEVERITY=1 AMQ_ADDITIONAL_JSON_LOG=1 LOG_FORMAT=basic
|
||||
|
||||
USER $MQM_UID
|
||||
ENTRYPOINT ["runmqserver"]
|
||||
|
||||
###############################################################################
|
||||
# Add default developer config
|
||||
###############################################################################
|
||||
FROM mq-server AS mq-dev-server
|
||||
ARG MQM_UID=888
|
||||
ARG BASE_IMAGE
|
||||
ARG BASE_TAG
|
||||
# Enable MQ developer default configuration
|
||||
ENV MQ_DEV=true
|
||||
# Default administrator password
|
||||
ENV MQ_ADMIN_PASSWORD=passw0rd
|
||||
LABEL summary="IBM MQ Advanced for Developers Server"
|
||||
LABEL description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the world’s most successful enterprises"
|
||||
LABEL vendor="IBM"
|
||||
LABEL distribution-scope="private"
|
||||
LABEL authoritative-source-url="https://www.ibm.com/software/passportadvantage/"
|
||||
LABEL url="https://www.ibm.com/products/mq/advanced"
|
||||
LABEL io.openshift.tags="mq messaging"
|
||||
LABEL io.k8s.display-name="IBM MQ Advanced for Developers Server"
|
||||
LABEL io.k8s.description="Simplify, accelerate and facilitate the reliable exchange of data with a security-rich messaging solution — trusted by the world’s most successful enterprises"
|
||||
LABEL base-image=$BASE_IMAGE
|
||||
LABEL base-image-release=$BASE_TAG
|
||||
USER 0
|
||||
COPY incubating/mqadvanced-server-dev/install-extra-packages.sh /usr/local/bin/
|
||||
RUN chmod u+x /usr/local/bin/install-extra-packages.sh \
|
||||
&& sleep 1 \
|
||||
&& install-extra-packages.sh
|
||||
# WARNING: This is what allows the mqm user to change the password of any other user
|
||||
# It's used by runmqdevserver to change the admin/app passwords.
|
||||
RUN echo "mqm ALL = NOPASSWD: /usr/sbin/chpasswd" > /etc/sudoers.d/mq-dev-config
|
||||
## Add admin and app users, and set a default password for admin
|
||||
RUN useradd admin -G mqm \
|
||||
&& groupadd mqclient \
|
||||
&& useradd app -G mqclient \
|
||||
&& echo admin:$MQ_ADMIN_PASSWORD | chpasswd
|
||||
# Create a directory for runtime data from runmqserver
|
||||
RUN mkdir -p /run/runmqdevserver \
|
||||
&& chown mqm:mqm /run/runmqdevserver
|
||||
COPY --from=builder /opt/app-root/src/go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/
|
||||
# Copy template files
|
||||
COPY incubating/mqadvanced-server-dev/*.tpl /etc/mqm/
|
||||
# Copy web XML files for default developer configuration
|
||||
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
|
||||
RUN chown -R mqm:mqm /etc/mqm/* \
|
||||
&& chmod +x /usr/local/bin/runmq* \
|
||||
&& install --directory --mode 0775 --owner mqm --group root /run/runmqdevserver
|
||||
ENV MQ_ENABLE_EMBEDDED_WEB_SERVER=1
|
||||
USER $MQM_UID
|
||||
ENTRYPOINT ["runmqdevserver"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -176,7 +176,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
© Copyright IBM Corporation. 2015, 2018
|
||||
© Copyright IBM Corporation. 2015, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
289
Makefile
289
Makefile
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2017, 2018
|
||||
# © Copyright IBM Corporation 2017, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -16,14 +16,14 @@
|
||||
# Conditional variables - you can override the values of these variables from
|
||||
# the command line
|
||||
###############################################################################
|
||||
# BASE_IMAGE is the base image to use for MQ, for example "ubuntu" or "rhel"
|
||||
BASE_IMAGE ?= ubuntu:16.04
|
||||
# MQ_VERSION is the fully qualified MQ version number to build
|
||||
MQ_VERSION ?= 9.1.0.0
|
||||
MQ_VERSION ?= 9.1.3.0
|
||||
# RELEASE shows what release of the container code has been built
|
||||
RELEASE ?= 2
|
||||
# MQ_ARCHIVE is the name of the file, under the downloads directory, from which MQ Advanced can
|
||||
# be installed. The default value is derived from MQ_VERSION, BASE_IMAGE and architecture
|
||||
# Does not apply to MQ Advanced for Developers.
|
||||
MQ_ARCHIVE ?= IBM_MQ_$(MQ_VERSION)_$(MQ_ARCHIVE_TYPE)_$(MQ_ARCHIVE_ARCH).tar.gz
|
||||
MQ_ARCHIVE ?= IBM_MQ_$(MQ_VERSION_VRM)_$(MQ_ARCHIVE_TYPE)_$(MQ_ARCHIVE_ARCH).tar.gz
|
||||
# MQ_ARCHIVE_DEV is the name of the file, under the downloads directory, from which MQ Advanced
|
||||
# for Developers can be installed
|
||||
MQ_ARCHIVE_DEV ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION))
|
||||
@@ -31,56 +31,56 @@ MQ_ARCHIVE_DEV ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION))
|
||||
MQ_SDK_ARCHIVE ?= $(MQ_ARCHIVE_DEV_$(MQ_VERSION))
|
||||
# Options to `go test` for the Docker tests
|
||||
TEST_OPTS_DOCKER ?=
|
||||
# MQ_IMAGE_ADVANCEDSERVER is the name and tag of the built MQ Advanced image
|
||||
MQ_IMAGE_ADVANCEDSERVER ?=mqadvanced-server:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||
# MQ_IMAGE_DEVSERVER is the name and tag of the built MQ Advanced for Developers image
|
||||
MQ_IMAGE_DEVSERVER ?=mqadvanced-server-dev:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||
# MQ_IMAGE_SDK is the name and tag of the built MQ Advanced for Developers SDK image
|
||||
MQ_IMAGE_SDK ?=mq-sdk:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||
# MQ_IMAGE_GOLANG_SDK is the name and tag of the built MQ Advanced for Developers SDK image, plus Go tools
|
||||
MQ_IMAGE_GOLANG_SDK ?=mq-golang-sdk:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||
# DOCKER is the Docker command to run
|
||||
DOCKER ?= docker
|
||||
# MQ_IMAGE_ADVANCEDSERVER is the name of the built MQ Advanced image
|
||||
MQ_IMAGE_ADVANCEDSERVER ?=mqadvanced-server
|
||||
# MQ_IMAGE_DEVSERVER is the name of the built MQ Advanced for Developers image
|
||||
MQ_IMAGE_DEVSERVER ?=mqadvanced-server-dev
|
||||
# MQ_TAG is the tag of the built MQ Advanced image & MQ Advanced for Developers image
|
||||
MQ_TAG ?=$(MQ_VERSION)-$(ARCH)
|
||||
# MQ_PACKAGES specifies the MQ packages (.deb or .rpm) to install. Defaults vary on base image.
|
||||
MQ_PACKAGES ?=
|
||||
MQ_PACKAGES ?=MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesWeb*.rpm MQSeriesAMS-*.rpm
|
||||
# MQM_UID is the UID to use for the "mqm" user
|
||||
MQM_UID ?= 888
|
||||
# COMMAND is the container command to run. "podman" or "docker"
|
||||
COMMAND ?=$(shell type -p podman 2>&1 >/dev/null && echo podman || echo docker)
|
||||
|
||||
###############################################################################
|
||||
# Other variables
|
||||
###############################################################################
|
||||
# ARCH is the platform architecture (e.g. x86_64, ppc64le or s390x)
|
||||
ARCH = $(shell uname -m)
|
||||
GO_PKG_DIRS = ./cmd ./internal ./test
|
||||
MQ_ARCHIVE_TYPE=LINUX
|
||||
MQ_ARCHIVE_DEV_PLATFORM=linux
|
||||
# ARCH is the platform architecture (e.g. amd64, ppc64le or s390x)
|
||||
ARCH=$(if $(findstring x86_64,$(shell uname -m)),amd64,$(shell uname -m))
|
||||
# BUILD_SERVER_CONTAINER is the name of the web server container used at build time
|
||||
BUILD_SERVER_CONTAINER=build-server
|
||||
# NUM_CPU is the number of CPUs available to Docker. Used to control how many
|
||||
# test run in parallel
|
||||
NUM_CPU = $(or $(shell docker info --format "{{ .NCPU }}"),2)
|
||||
# BASE_IMAGE_TAG is a normalized version of BASE_IMAGE, suitable for use in a Docker tag
|
||||
BASE_IMAGE_TAG=$(subst /,-,$(subst :,-,$(BASE_IMAGE)))
|
||||
MQ_IMAGE_DEVSERVER_BASE=mqadvanced-server-dev-base:$(MQ_VERSION)-$(ARCH)-$(BASE_IMAGE_TAG)
|
||||
BASE_IMAGE_TAG=$(lastword $(subst /, ,$(subst :,-,$(BASE_IMAGE))))
|
||||
#BASE_IMAGE_TAG=$(subst /,-,$(subst :,-,$(BASE_IMAGE)))
|
||||
MQ_IMAGE_DEVSERVER_BASE=mqadvanced-server-dev-base
|
||||
# Docker image name to use for JMS tests
|
||||
DEV_JMS_IMAGE=mq-dev-jms-test
|
||||
# Variables for versioning
|
||||
IMAGE_REVISION=$(shell git rev-parse HEAD)
|
||||
IMAGE_SOURCE=$(shell git config --get remote.origin.url)
|
||||
IMAGE_CREATED=$(shell date -u +%Y-%m-%dT%H:%M:%S%:z)
|
||||
|
||||
EMPTY:=
|
||||
SPACE:= $(EMPTY) $(EMPTY)
|
||||
# MQ_VERSION_VRM is MQ_VERSION with only the Version, Release and Modifier fields (no Fix field). e.g. 9.1.3 instead of 9.1.3.0
|
||||
MQ_VERSION_VRM=$(subst $(SPACE),.,$(wordlist 1,3,$(subst .,$(SPACE),$(MQ_VERSION))))
|
||||
|
||||
ifneq (,$(findstring Microsoft,$(shell uname -r)))
|
||||
DOWNLOADS_DIR=$(patsubst /mnt/c%,C:%,$(realpath ./downloads/))
|
||||
else ifneq (,$(findstring Windows,$(shell echo ${OS})))
|
||||
DOWNLOADS_DIR=$(shell pwd)/downloads/
|
||||
else
|
||||
DOWNLOADS_DIR=$(realpath ./downloads/)
|
||||
endif
|
||||
|
||||
# Try to figure out which archive to use from the BASE_IMAGE
|
||||
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu"
|
||||
MQ_ARCHIVE_TYPE=UBUNTU
|
||||
MQ_ARCHIVE_DEV_PLATFORM=ubuntu
|
||||
else
|
||||
MQ_ARCHIVE_TYPE=LINUX
|
||||
MQ_ARCHIVE_DEV_PLATFORM=linux
|
||||
endif
|
||||
# Try to figure out which archive to use from the architecture
|
||||
ifeq "$(ARCH)" "x86_64"
|
||||
ifeq "$(ARCH)" "amd64"
|
||||
MQ_ARCHIVE_ARCH=X86-64
|
||||
MQ_DEV_ARCH=x86-64
|
||||
else ifeq "$(ARCH)" "ppc64le"
|
||||
@@ -91,45 +91,31 @@ else ifeq "$(ARCH)" "s390x"
|
||||
MQ_DEV_ARCH=s390x
|
||||
endif
|
||||
# Archive names for IBM MQ Advanced for Developers
|
||||
MQ_ARCHIVE_DEV_9.0.5.0=mqadv_dev905_$(MQ_ARCHIVE_DEV_PLATFORM)_x86-64.tar.gz
|
||||
MQ_ARCHIVE_DEV_9.1.0.0=mqadv_dev910_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz
|
||||
MQ_ARCHIVE_DEV_9.1.1.0=mqadv_dev911_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz
|
||||
MQ_ARCHIVE_DEV_9.1.2.0=mqadv_dev912_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz
|
||||
MQ_ARCHIVE_DEV_9.1.3.0=mqadv_dev913_$(MQ_ARCHIVE_DEV_PLATFORM)_$(MQ_DEV_ARCH).tar.gz
|
||||
|
||||
###############################################################################
|
||||
# Build targets
|
||||
###############################################################################
|
||||
.PHONY: vars
|
||||
vars:
|
||||
#ifeq "$(findstring ubuntu,$(BASE_IMAGE))","ubuntu"
|
||||
@echo $(MQ_ARCHIVE_ARCH)
|
||||
@echo $(MQ_ARCHIVE_TYPE)
|
||||
@echo $(MQ_ARCHIVE)
|
||||
|
||||
.PHONY: default
|
||||
default: build-devserver test
|
||||
default: build-devserver
|
||||
|
||||
# Build all components (except incubating ones)
|
||||
.PHONY: all
|
||||
all: build-devserver build-advancedserver
|
||||
|
||||
.PHONY: test-all
|
||||
test-all: test-devserver test-advancedserver
|
||||
|
||||
.PHONY: precommit
|
||||
precommit: fmt lint all test-all
|
||||
test-all: build-devjmstest test-devserver test-advancedserver
|
||||
|
||||
.PHONY: devserver
|
||||
devserver: build-devserver test-devserver
|
||||
devserver: build-devserver build-devjmstest test-devserver
|
||||
|
||||
# Build incubating components
|
||||
.PHONY: incubating
|
||||
incubating: build-explorer
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ./coverage
|
||||
rm -rf ./build
|
||||
rm -rf ./deps
|
||||
|
||||
downloads/$(MQ_ARCHIVE_DEV):
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Downloading IBM MQ Advanced for Developers "$(MQ_VERSION)$(END)))
|
||||
mkdir -p downloads
|
||||
@@ -143,19 +129,10 @@ downloads/$(MQ_SDK_ARCHIVE):
|
||||
.PHONY: downloads
|
||||
downloads: downloads/$(MQ_ARCHIVE_DEV) downloads/$(MQ_SDK_ARCHIVE)
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
glide install --strip-vendor
|
||||
|
||||
# Vendor Go dependencies for the Docker tests
|
||||
test/docker/vendor:
|
||||
cd test/docker && dep ensure -vendor-only
|
||||
|
||||
.PHONY: build-cov
|
||||
build-cov:
|
||||
mkdir -p build
|
||||
cd build; go test -c -covermode=count ../cmd/runmqserver
|
||||
|
||||
# Shortcut to just run the unit tests
|
||||
.PHONY: test-unit
|
||||
test-unit:
|
||||
@@ -163,8 +140,9 @@ test-unit:
|
||||
|
||||
.PHONY: test-advancedserver
|
||||
test-advancedserver: test/docker/vendor
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER) on $(shell docker --version)"$(END)))
|
||||
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) EXPECTED_LICENSE=Production go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER)
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) on $(shell docker --version)"$(END)))
|
||||
docker inspect $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)
|
||||
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) EXPECTED_LICENSE=Production go test -parallel $(NUM_CPU) $(TEST_OPTS_DOCKER)
|
||||
|
||||
.PHONY: build-devjmstest
|
||||
build-devjmstest:
|
||||
@@ -173,15 +151,17 @@ build-devjmstest:
|
||||
|
||||
.PHONY: test-devserver
|
||||
test-devserver: test/docker/vendor
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_DEVSERVER) on $(shell docker --version)"$(END)))
|
||||
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) go test -parallel $(NUM_CPU) -tags mqdev $(TEST_OPTS_DOCKER)
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_DEVSERVER):$(MQ_TAG) on $(shell docker --version)"$(END)))
|
||||
docker inspect $(MQ_IMAGE_DEVSERVER):$(MQ_TAG)
|
||||
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_DEVSERVER):$(MQ_TAG) EXPECTED_LICENSE=Developer DEV_JMS_IMAGE=$(DEV_JMS_IMAGE) IBMJRE=true go test -parallel $(NUM_CPU) -tags mqdev $(TEST_OPTS_DOCKER)
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
mkdir coverage
|
||||
|
||||
.PHONY: test-advancedserver-cover
|
||||
test-advancedserver-cover: test/docker/vendor coverage
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER) with code coverage on $(shell docker --version)"$(END)))
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Test $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) with code coverage on $(shell docker --version)"$(END)))
|
||||
rm -f ./coverage/unit*.cov
|
||||
# Run unit tests with coverage, for each package under 'internal'
|
||||
go list -f '{{.Name}}' ./internal/... | xargs -I {} go test -cover -covermode count -coverprofile ./coverage/unit-{}.cov ./internal/{}
|
||||
@@ -193,7 +173,7 @@ test-advancedserver-cover: test/docker/vendor coverage
|
||||
rm -f ./test/docker/coverage/*.cov
|
||||
rm -f ./coverage/docker.*
|
||||
mkdir -p ./test/docker/coverage/
|
||||
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER)-cover TEST_COVER=true go test $(TEST_OPTS_DOCKER)
|
||||
cd test/docker && TEST_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)-cover TEST_COVER=true go test $(TEST_OPTS_DOCKER)
|
||||
echo 'mode: count' > ./coverage/docker.cov
|
||||
tail -q -n +2 ./test/docker/coverage/*.cov >> ./coverage/docker.cov
|
||||
go tool cover -html=./coverage/docker.cov -o ./coverage/docker.html
|
||||
@@ -202,93 +182,150 @@ test-advancedserver-cover: test/docker/vendor coverage
|
||||
tail -q -n +2 ./coverage/unit.cov ./coverage/docker.cov >> ./coverage/combined.cov
|
||||
go tool cover -html=./coverage/combined.cov -o ./coverage/combined.html
|
||||
|
||||
define docker-build-mq
|
||||
# Create a temporary network to use for the build
|
||||
$(DOCKER) network create build
|
||||
# Start a web server to host the MQ downloadable (tar.gz) file
|
||||
$(DOCKER) run \
|
||||
--rm \
|
||||
--name $(BUILD_SERVER_CONTAINER) \
|
||||
--network build \
|
||||
--network-alias build \
|
||||
--volume $(DOWNLOADS_DIR):/usr/share/nginx/html:ro \
|
||||
--detach \
|
||||
nginx:alpine
|
||||
# Build an MQ image. The commands used are slightly different between Docker and Podman
|
||||
define build-mq
|
||||
$(if $(findstring docker,$(COMMAND)), @docker network create build,)
|
||||
$(if $(findstring docker,$(COMMAND)), @docker run --rm --name $(BUILD_SERVER_CONTAINER) --network build --network-alias build --volume $(DOWNLOADS_DIR):/usr/share/nginx/html:ro --detach docker.io/nginx:alpine,)
|
||||
$(eval EXTRA_ARGS=$(if $(findstring docker,$(COMMAND)), --network build --build-arg MQ_URL=http://build:80/$4, --volume $(DOWNLOADS_DIR):/var/downloads --build-arg MQ_URL=file:///var/downloads/$4))
|
||||
# Build the new image
|
||||
$(DOCKER) build \
|
||||
--tag $1 \
|
||||
--file $2 \
|
||||
--network build \
|
||||
--build-arg MQ_URL=http://build:80/$3 \
|
||||
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
|
||||
--build-arg BUILDER_IMAGE=$(MQ_IMAGE_GOLANG_SDK) \
|
||||
--build-arg IMAGE_REVISION="$(IMAGE_REVISION)" \
|
||||
--build-arg IMAGE_CREATED="$(IMAGE_CREATED)" \
|
||||
--build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" \
|
||||
--label IBM_PRODUCT_ID=$4 \
|
||||
--label IBM_PRODUCT_NAME=$5 \
|
||||
--label IBM_PRODUCT_VERSION=$6 \
|
||||
$(COMMAND) build \
|
||||
--tag $1:$2 \
|
||||
--file $3 \
|
||||
$(EXTRA_ARGS) \
|
||||
--build-arg MQ_PACKAGES="$(MQ_PACKAGES)" \
|
||||
. ; $(DOCKER) kill $(BUILD_SERVER_CONTAINER) && $(DOCKER) network rm build
|
||||
--build-arg IMAGE_REVISION="$(IMAGE_REVISION)" \
|
||||
--build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" \
|
||||
--build-arg IMAGE_TAG="$1:$2" \
|
||||
--build-arg MQM_UID=$(MQM_UID) \
|
||||
--label version=$(MQ_VERSION) \
|
||||
--label name=$1 \
|
||||
--label build-date=$(shell date +%Y-%m-%dT%H:%M:%S%z) \
|
||||
--label release="$(RELEASE)" \
|
||||
--label architecture="$(ARCH)" \
|
||||
--label run="docker run -d -e LICENSE=accept $1:$2" \
|
||||
--label vcs-ref=$(IMAGE_REVISION) \
|
||||
--label vcs-type=git \
|
||||
--label vcs-url=$(IMAGE_SOURCE) \
|
||||
--target $5 \
|
||||
.
|
||||
$(if $(findstring docker,$(COMMAND)), @docker kill $(BUILD_SERVER_CONTAINER))
|
||||
$(if $(findstring docker,$(COMMAND)), @docker network rm build)
|
||||
endef
|
||||
|
||||
DOCKER_SERVER_VERSION=$(shell docker version --format "{{ .Server.Version }}")
|
||||
DOCKER_CLIENT_VERSION=$(shell docker version --format "{{ .Client.Version }}")
|
||||
.PHONY: docker-version
|
||||
docker-version:
|
||||
PODMAN_VERSION=$(shell podman version --format "{{ .Version }}")
|
||||
.PHONY: command-version
|
||||
command-version:
|
||||
# If we're using Docker, then check it's recent enough to support multi-stage builds
|
||||
ifneq (,$(findstring docker,$(COMMAND)))
|
||||
@test "$(word 1,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "17" || ("$(word 1,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -eq "17" && "$(word 2,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "05") || (echo "Error: Docker client 17.05 or greater is required" && exit 1)
|
||||
@test "$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -ge "17" || ("$(word 1,$(subst ., ,$(DOCKER_SERVER_VERSION)))" -eq "17" && "$(word 2,$(subst ., ,$(DOCKER_CLIENT_VERSION)))" -ge "05") || (echo "Error: Docker server 17.05 or greater is required" && exit 1)
|
||||
endif
|
||||
ifneq (,$(findstring podman,$(COMMAND)))
|
||||
@test "$(word 1,$(subst ., ,$(PODMAN_VERSION)))" -ge "1" || (echo "Error: Podman version 1.0 or greater is required" && exit 1)
|
||||
endif
|
||||
|
||||
.PHONY: build-advancedserver-host
|
||||
build-advancedserver-host: build-advancedserver
|
||||
|
||||
.PHONY: build-advancedserver
|
||||
build-advancedserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE)
|
||||
build-advancedserver: downloads/$(MQ_ARCHIVE) docker-version build-golang-sdk
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER)"$(END)))
|
||||
$(call docker-build-mq,$(MQ_IMAGE_ADVANCEDSERVER),Dockerfile-server,$(MQ_ARCHIVE),"4486e8c4cc9146fd9b3ce1f14a2dfc5b","IBM MQ Advanced",$(MQ_VERSION))
|
||||
build-advancedserver: log-build-env downloads/$(MQ_ARCHIVE) command-version
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Build $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)"$(END)))
|
||||
$(call build-mq,$(MQ_IMAGE_ADVANCEDSERVER),$(MQ_TAG),Dockerfile-server,$(MQ_ARCHIVE),mq-server)
|
||||
|
||||
.PHONY: build-devserver-host
|
||||
build-devserver-host: build-devserver
|
||||
|
||||
.PHONY: build-devserver
|
||||
# Target-specific variable to add web server into devserver image
|
||||
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu"
|
||||
build-devserver: MQ_PACKAGES=ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams ibmmq-web
|
||||
else
|
||||
build-devserver: MQ_PACKAGES=MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm MQSeriesWeb-*.rpm
|
||||
endif
|
||||
build-devserver: MQ_SDK_ARCHIVE=$(MQ_ARCHIVE_DEV)
|
||||
build-devserver: downloads/$(MQ_ARCHIVE_DEV) docker-version build-golang-sdk
|
||||
$(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_DEVSERVER_BASE)"$(END)))
|
||||
$(call docker-build-mq,$(MQ_IMAGE_DEVSERVER_BASE),Dockerfile-server,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION))
|
||||
$(DOCKER) build --tag $(MQ_IMAGE_DEVSERVER) --build-arg IMAGE_SOURCE="$(IMAGE_SOURCE)" --build-arg IMAGE_REVISION="$(IMAGE_REVISION)" --build-arg IMAGE_CREATED="$(IMAGE_CREATED)" --build-arg BASE_IMAGE=$(MQ_IMAGE_DEVSERVER_BASE) --build-arg BUILDER_IMAGE=$(MQ_IMAGE_GOLANG_SDK) --file incubating/mqadvanced-server-dev/Dockerfile .
|
||||
build-devserver: log-build-env downloads/$(MQ_ARCHIVE_DEV) command-version
|
||||
$(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_DEVSERVER):$(MQ_TAG)"$(END)))
|
||||
$(call build-mq,$(MQ_IMAGE_DEVSERVER),$(MQ_TAG),Dockerfile-server,$(MQ_ARCHIVE_DEV),mq-dev-server)
|
||||
|
||||
.PHONY: build-advancedserver-cover
|
||||
build-advancedserver-cover: docker-version
|
||||
$(DOCKER) build --build-arg BASE_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER) -t $(MQ_IMAGE_ADVANCEDSERVER)-cover -f Dockerfile-server.cover .
|
||||
build-advancedserver-cover: command-version
|
||||
$(COMMAND) build --build-arg BASE_IMAGE=$(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG) -t $(MQ_IMAGE_ADVANCEDSERVER):$(MQ_TAG)-cover -f Dockerfile-server.cover .
|
||||
|
||||
.PHONY: build-explorer docker-pull
|
||||
.PHONY: build-explorer
|
||||
build-explorer: downloads/$(MQ_ARCHIVE_DEV)
|
||||
$(call docker-build-mq,mq-explorer:latest-$(ARCH),incubating/mq-explorer/Dockerfile-mq-explorer,$(MQ_ARCHIVE_DEV),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers (Non-Warranted)",$(MQ_VERSION))
|
||||
$(call build-mq,mq-explorer,latest-$(ARCH),incubating/mq-explorer/Dockerfile,$(MQ_ARCHIVE_DEV),mq-explorer)
|
||||
|
||||
ifeq "$(findstring ubuntu,$(BASE_IMAGE))" "ubuntu"
|
||||
build-sdk: MQ_PACKAGES=ibmmq-sdk ibmmq-samples build-essential
|
||||
else
|
||||
build-sdk: MQ_PACKAGES=MQSeriesRuntime-*.rpm MQSeriesSDK-*.rpm MQSeriesSamples*.rpm
|
||||
endif
|
||||
build-sdk: downloads/$(MQ_SDK_ARCHIVE) docker-version docker-pull
|
||||
$(call docker-build-mq,$(MQ_IMAGE_SDK),incubating/mq-sdk/Dockerfile,$(MQ_SDK_ARCHIVE),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers SDK (Non-Warranted)",$(MQ_VERSION))
|
||||
.PHONY: build-sdk
|
||||
build-sdk: downloads/$(MQ_ARCHIVE_DEV)
|
||||
$(info $(shell printf $(TITLE)"Build $(MQ_IMAGE_SDK)"$(END)))
|
||||
$(call build-mq,mq-sdk,$(MQ_TAG),incubating/mq-sdk/Dockerfile,$(MQ_SDK_ARCHIVE),mq-sdk)
|
||||
|
||||
build-golang-sdk: downloads/$(MQ_SDK_ARCHIVE) docker-version build-sdk
|
||||
$(DOCKER) build --build-arg BASE_IMAGE=$(MQ_IMAGE_SDK) -t $(MQ_IMAGE_GOLANG_SDK) -f incubating/mq-golang-sdk/Dockerfile .
|
||||
# $(call docker-build-mq,$(MQ_IMAGE_GOLANG_SDK),incubating/mq-golang-sdk/Dockerfile,$(MQ_IMAGE_SDK),"98102d16795c4263ad9ca075190a2d4d","IBM MQ Advanced for Developers SDK (Non-Warranted)",$(MQ_VERSION))
|
||||
.PHONY: log-build-env
|
||||
log-build-vars:
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Build environment"$(END)))
|
||||
@echo ARCH=$(ARCH)
|
||||
@echo MQ_VERSION=$(MQ_VERSION)
|
||||
@echo MQ_ARCHIVE=$(MQ_ARCHIVE)
|
||||
@echo MQ_IMAGE_DEVSERVER=$(MQ_IMAGE_DEVSERVER)
|
||||
@echo MQ_IMAGE_ADVANCEDSERVER=$(MQ_IMAGE_ADVANCEDSERVER)
|
||||
@echo COMMAND=$(COMMAND)
|
||||
@echo MQM_UID=$(MQM_UID)
|
||||
|
||||
docker-pull:
|
||||
$(DOCKER) pull $(BASE_IMAGE)
|
||||
.PHONY: log-build-env
|
||||
log-build-env: log-build-vars
|
||||
$(info $(SPACER)$(shell printf $(TITLE)"Build environment - $(COMMAND) info"$(END)))
|
||||
@echo Command version: $(shell $(COMMAND) --version)
|
||||
$(COMMAND) info
|
||||
|
||||
GO_PKG_DIRS = ./cmd ./internal ./test
|
||||
include formatting.mk
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ./coverage
|
||||
rm -rf ./build
|
||||
rm -rf ./deps
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
glide install --strip-vendor
|
||||
|
||||
.PHONY: build-cov
|
||||
build-cov:
|
||||
mkdir -p build
|
||||
cd build; go test -c -covermode=count ../cmd/runmqserver
|
||||
|
||||
.PHONY: precommit
|
||||
precommit: fmt lint
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: $(addsuffix /$(wildcard *.go), $(GO_PKG_DIRS))
|
||||
go fmt $(addsuffix /..., $(GO_PKG_DIRS))
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(addsuffix /$(wildcard *.go), $(GO_PKG_DIRS))
|
||||
@# This expression is necessary because /... includes the vendor directory in golint
|
||||
@# As of 11/04/2018 there is an open issue to fix it: https://github.com/golang/lint/issues/320
|
||||
golint -set_exit_status $(sort $(dir $(wildcard $(addsuffix /*/*.go, $(GO_PKG_DIRS)))))
|
||||
|
||||
.PHONY: gosec
|
||||
gosec: $(info $(SPACER)$(shell printf "Running gosec test"$(END)))
|
||||
@gosec -fmt=json -out=gosec_results.json cmd/... internal/... 2> /dev/null ;\
|
||||
cat "gosec_results.json" ;\
|
||||
cat gosec_results.json | grep HIGH | grep severity > /dev/null ;\
|
||||
if [ $$? -eq 0 ]; then \
|
||||
printf "\nFAILURE: gosec found files containing HIGH severity issues - see results.json\n" ;\
|
||||
exit 1 ;\
|
||||
else \
|
||||
printf "\ngosec found no HIGH severity issues\n" ;\
|
||||
fi ;\
|
||||
cat gosec_results.json | grep MEDIUM | grep severity > /dev/null ;\
|
||||
if [ $$? -eq 0 ]; then \
|
||||
printf "\nFAILURE: gosec found files containing MEDIUM severity issues - see results.json\n" ;\
|
||||
exit 1 ;\
|
||||
else \
|
||||
printf "\ngosec found no MEDIUM severity issues\n" ;\
|
||||
fi ;\
|
||||
cat gosec_results.json | grep LOW | grep severity > /dev/null;\
|
||||
if [ $$? -eq 0 ]; then \
|
||||
printf "\nFAILURE: gosec found files containing LOW severity issues - see results.json\n" ;\
|
||||
exit 1;\
|
||||
else \
|
||||
printf "\ngosec found no LOW severity issues\n" ;\
|
||||
fi ;\
|
||||
|
||||
include formatting.mk
|
||||
|
||||
33
README.md
33
README.md
@@ -1,26 +1,27 @@
|
||||

|
||||
# IBM MQ container
|
||||
|
||||
# Overview
|
||||
[](https://travis-ci.org/ibm-messaging/mq-container)
|
||||
|
||||
**Note**: The `master` branch may be in an *unstable or even broken state* during development.
|
||||
To get a stable version, please use the correct [branch](https://github.com/ibm-messaging/mq-container/branches) for your MQ version, instead of the `master` branch.
|
||||
|
||||
## Overview
|
||||
|
||||
Run [IBM® MQ](http://www-03.ibm.com/software/products/en/ibm-mq) in a container.
|
||||
|
||||
You can build an image containing either IBM MQ Advanced, or IBM MQ Advanced for Developers. The developer image includes a [default developer configuration](docs/developer-config.md), to make it easier to get started. There is also an [incubating](incubating) folder for additional images for other MQ components, which you might find useful.
|
||||
|
||||
# Current status
|
||||
|
||||
MQ Advanced for Developers image [](https://travis-ci.org/ibm-messaging/mq-container)
|
||||
|
||||
# Build
|
||||
## Build
|
||||
|
||||
After extracting the code from this repository, you can follow the [build documentation](docs/building.md) to build an image.
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
See the [usage documentation](docs/usage.md) for details on how to run a container.
|
||||
|
||||
Note that in order to use the image, it is necessary to accept the terms of the [IBM MQ license](#license).
|
||||
|
||||
## Environment variables supported by this image
|
||||
### Environment variables supported by this image
|
||||
|
||||
- **LICENSE** - Set this to `accept` to agree to the MQ Advanced for Developers license. If you wish to see the license you can set this to `view`.
|
||||
- **LANG** - Set this to the language you would like the license to be printed in.
|
||||
@@ -30,25 +31,25 @@ Note that in order to use the image, it is necessary to accept the terms of the
|
||||
|
||||
See the [default developer configuration docs](docs/developer-config.md) for the extra environment variables supported by the MQ Advanced for Developers image.
|
||||
|
||||
## Kubernetes
|
||||
### Kubernetes
|
||||
|
||||
If you want to use IBM MQ in [Kubernetes](https://kubernetes.io), you can find an example [Helm](https://helm.sh/) chart here: [IBM charts](https://github.com/IBM/charts). This can be used to run the container on a cluster, such as [IBM Cloud Private](https://www.ibm.com/cloud-computing/products/ibm-cloud-private/) or the [IBM Cloud Kubernetes Service](https://www.ibm.com/cloud/container-service).
|
||||
|
||||
# Issues and contributions
|
||||
## Issues and contributions
|
||||
|
||||
For issues relating specifically to the container image or Helm chart, please use the [GitHub issue tracker](https://github.com/ibm-messaging/mq-container/issues). If you do submit a Pull Request related to this Docker image, please indicate in the Pull Request that you accept and agree to be bound by the terms of the [IBM Contributor License Agreement](CLA.md).
|
||||
|
||||
# License
|
||||
## License
|
||||
|
||||
The Dockerfiles and associated code and scripts are licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
|
||||
Licenses for the products installed within the images are as follows:
|
||||
|
||||
- [IBM MQ Advanced for Developers](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-AKHJY4) (International License Agreement for Non-Warranted Programs). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above.
|
||||
- [IBM MQ Advanced](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-AKHJJP) (International Program License Agreement). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above.
|
||||
- [IBM MQ Advanced for Developers](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-BBZHCQ) (International License Agreement for Non-Warranted Programs). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above.
|
||||
- [IBM MQ Advanced](http://www14.software.ibm.com/cgi-bin/weblap/lap.pl?la_formnum=Z125-3301-14&li_formnum=L-APIG-BBSHJL) (International Program License Agreement). This license may be viewed from an image using the `LICENSE=view` environment variable as described above or by following the link above.
|
||||
- License information for Ubuntu packages may be found in `/usr/share/doc/${package}/copyright`
|
||||
|
||||
Note: The IBM MQ Advanced for Developers license does not permit further distribution and the terms restrict usage to a developer machine.
|
||||
|
||||
# Copyright
|
||||
## Copyright
|
||||
|
||||
© Copyright IBM Corporation 2015, 2018
|
||||
© Copyright IBM Corporation 2015, 2019
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -32,6 +32,7 @@ func queueManagerHealthy() (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
// Specify the queue manager name, just in case someone's created a second queue manager
|
||||
// #nosec G204
|
||||
cmd := exec.Command("dspmq", "-n", "-m", name)
|
||||
// Run the command and wait for completion
|
||||
out, err := cmd.CombinedOutput()
|
||||
@@ -39,8 +40,8 @@ func queueManagerHealthy() (bool, error) {
|
||||
fmt.Println(err)
|
||||
return false, err
|
||||
}
|
||||
fmt.Println(out)
|
||||
if !strings.Contains(string(out), "(RUNNING)") {
|
||||
fmt.Printf("%s", out)
|
||||
if !strings.Contains(string(out), "(RUNNING)") && !strings.Contains(string(out), "(RUNNING AS STANDBY)") && !strings.Contains(string(out), "(STARTING)") {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/name"
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
)
|
||||
|
||||
@@ -31,11 +32,25 @@ func main() {
|
||||
if !r || err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
name, err := name.GetQueueManagerName()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if the queue manager has a running listener
|
||||
if standby, _ := ready.IsRunningAsStandbyQM(name); !standby {
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:1414")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
conn.Close()
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Detected queue manager running in standby mode")
|
||||
os.Exit(10)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -23,23 +23,30 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
"github.com/ibm-messaging/mq-container/internal/containerruntimelogger"
|
||||
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
|
||||
"github.com/ibm-messaging/mq-container/internal/name"
|
||||
)
|
||||
|
||||
var log *logger.Logger
|
||||
|
||||
func setPassword(user string, password string) error {
|
||||
cmd := exec.Command("chpasswd")
|
||||
// #nosec G204
|
||||
cmd := exec.Command("sudo", "chpasswd")
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(stdin, "%s:%s", user, password)
|
||||
stdin.Close()
|
||||
_, _, err = command.RunCmd(cmd)
|
||||
err = stdin.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("Error closing password stdin: %v", err)
|
||||
}
|
||||
out, _, err := command.RunCmd(cmd)
|
||||
if err != nil {
|
||||
// Include the command output in the error
|
||||
return fmt.Errorf("%v: %v", err.Error(), out)
|
||||
}
|
||||
log.Printf("Set password for \"%v\" user", user)
|
||||
return nil
|
||||
@@ -85,20 +92,20 @@ func configureLogger() error {
|
||||
|
||||
func configureWeb(qmName string) error {
|
||||
out := "/etc/mqm/web/installations/Installation1/angular.persistence/admin.json"
|
||||
return processTemplateFile("/etc/mqm/admin.json.tpl", out, map[string]string{"QueueManagerName": qmName})
|
||||
return mqtemplate.ProcessTemplateFile("/etc/mqm/admin.json.tpl", out, map[string]string{"QueueManagerName": qmName}, log)
|
||||
}
|
||||
|
||||
func logTerminationf(format string, args ...interface{}) {
|
||||
logTermination(fmt.Sprintf(format, args))
|
||||
logTermination(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// TODO: Duplicated code
|
||||
func logTermination(args ...interface{}) {
|
||||
msg := fmt.Sprint(args)
|
||||
// Write the message to the termination log. This is the default place
|
||||
msg := fmt.Sprint(args...)
|
||||
// Write the message to the termination log. This is not the default place
|
||||
// that Kubernetes will look for termination information.
|
||||
log.Debugf("Writing termination message: %v", msg)
|
||||
err := ioutil.WriteFile("/dev/termination-log", []byte(msg), 0660)
|
||||
err := ioutil.WriteFile("/run/termination-log", []byte(msg), 0660)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
@@ -111,6 +118,13 @@ func doMain() error {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = containerruntimelogger.LogContainerDetails(log)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
adminPassword, set := os.LookupEnv("MQ_ADMIN_PASSWORD")
|
||||
if set {
|
||||
err = setPassword("admin", adminPassword)
|
||||
@@ -139,14 +153,6 @@ func doMain() error {
|
||||
logTerminationf("Error getting queue manager name: %v", err)
|
||||
return err
|
||||
}
|
||||
ks, set := os.LookupEnv("MQ_TLS_KEYSTORE")
|
||||
if set {
|
||||
err = configureTLS(name, ks, os.Getenv("MQ_TLS_PASSPHRASE"))
|
||||
if err != nil {
|
||||
logTerminationf("Error configuring TLS: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = configureWeb(name)
|
||||
if err != nil {
|
||||
@@ -165,6 +171,10 @@ func main() {
|
||||
osExit(1)
|
||||
} else {
|
||||
// Replace this process with runmqserver
|
||||
syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver"}, os.Environ())
|
||||
// #nosec G204
|
||||
err = syscall.Exec("/usr/local/bin/runmqserver", []string{"runmqserver", "-dev"}, os.Environ())
|
||||
if err != nil {
|
||||
log.Errorf("Error replacing this process with runmqserver: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,8 @@ package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
|
||||
)
|
||||
|
||||
func updateMQSC(appPasswordRequired bool) error {
|
||||
@@ -30,12 +32,19 @@ func updateMQSC(appPasswordRequired bool) error {
|
||||
if os.Getenv("MQ_DEV") == "true" {
|
||||
const mqscTemplate string = mqsc + ".tpl"
|
||||
// Re-configure channel if app password not set
|
||||
err := processTemplateFile(mqsc+".tpl", mqsc, map[string]string{"ChckClnt": checkClient})
|
||||
err := mqtemplate.ProcessTemplateFile(mqsc+".tpl", mqsc, map[string]string{"ChckClnt": checkClient}, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
os.Remove(mqsc)
|
||||
_, err := os.Stat(mqsc)
|
||||
if !os.IsNotExist(err) {
|
||||
err = os.Remove(mqsc)
|
||||
if err != nil {
|
||||
log.Errorf("Error removing file %s: %v", mqsc, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
)
|
||||
|
||||
func configureWebTLS(cms *KeyStore) error {
|
||||
dir := "/run/runmqdevserver/tls"
|
||||
ks := NewJKSKeyStore(filepath.Join(dir, "key.jks"), cms.Password)
|
||||
ts := NewJKSKeyStore(filepath.Join(dir, "trust.jks"), cms.Password)
|
||||
|
||||
log.Debug("Creating key store")
|
||||
err := ks.Create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("Creating trust store")
|
||||
err = ts.Create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("Importing keys")
|
||||
err = ks.Import(cms.Filename, cms.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
webConfigDir := "/etc/mqm/web/installations/Installation1/servers/mqweb"
|
||||
tlsConfig := filepath.Join(webConfigDir, "tls.xml")
|
||||
newTLSConfig := filepath.Join(webConfigDir, "tls-dev.xml")
|
||||
err = os.Remove(tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we symlink here to prevent issues on restart
|
||||
err = os.Symlink(newTLSConfig, tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mqmUID, mqmGID, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
err = os.Chown(tlsConfig, mqmUID, mqmGID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureTLS(qmName string, inputFile string, passPhrase string) error {
|
||||
log.Debug("Configuring TLS")
|
||||
|
||||
_, err := os.Stat(inputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Use a persisted file (on the volume) instead?
|
||||
dir := "/run/runmqdevserver/tls"
|
||||
keyFile := filepath.Join(dir, "key.kdb")
|
||||
|
||||
_, err = os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0770)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mqmUID, mqmGID, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
err = os.Chown(dir, mqmUID, mqmGID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cms := NewCMSKeyStore(keyFile, passPhrase)
|
||||
|
||||
err = cms.Create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cms.CreateStash()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cms.Import(inputFile, passPhrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labels, err := cms.GetCertificateLabels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(labels) == 0 {
|
||||
return fmt.Errorf("unable to find certificate label")
|
||||
}
|
||||
log.Debugf("Renaming certificate from %v", labels[0])
|
||||
const newLabel string = "devcert"
|
||||
err = cms.RenameCertificate(labels[0], newLabel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sslCipherSpec string
|
||||
if os.Getenv("MQ_DEV") == "true" {
|
||||
sslCipherSpec = "TLS_RSA_WITH_AES_128_CBC_SHA256"
|
||||
} else {
|
||||
sslCipherSpec = "' '"
|
||||
}
|
||||
|
||||
const mqsc string = "/etc/mqm/20-dev-tls.mqsc"
|
||||
const mqscTemplate string = mqsc + ".tpl"
|
||||
|
||||
err = processTemplateFile(mqscTemplate, mqsc, map[string]string{
|
||||
"SSLKeyR": filepath.Join(dir, "key"),
|
||||
"CertificateLabel": newLabel,
|
||||
"SSLCipherSpec": sslCipherSpec,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = configureWebTLS(cms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,18 +17,17 @@ package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
)
|
||||
|
||||
func createVolume(path string) error {
|
||||
dataPath := filepath.Join(path, "data")
|
||||
func createVolume(dataPath string) error {
|
||||
fi, err := os.Stat(dataPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// #nosec G301
|
||||
err = os.MkdirAll(dataPath, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -78,6 +78,7 @@ func checkLicense() (bool, error) {
|
||||
return true, nil
|
||||
case ok && lic == "view":
|
||||
file := filepath.Join("/opt/mqm/licenses", resolveLicenseFile())
|
||||
// #nosec G304
|
||||
buf, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -21,9 +21,12 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqini"
|
||||
)
|
||||
@@ -31,20 +34,26 @@ import (
|
||||
// var debug = false
|
||||
var log *logger.Logger
|
||||
|
||||
var collectDiagOnFail = false
|
||||
|
||||
func logTerminationf(format string, args ...interface{}) {
|
||||
logTermination(fmt.Sprintf(format, args))
|
||||
logTermination(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func logTermination(args ...interface{}) {
|
||||
msg := fmt.Sprint(args)
|
||||
// Write the message to the termination log. This is the default place
|
||||
msg := fmt.Sprint(args...)
|
||||
// Write the message to the termination log. This is not the default place
|
||||
// that Kubernetes will look for termination information.
|
||||
log.Debugf("Writing termination message: %v", msg)
|
||||
err := ioutil.WriteFile("/dev/termination-log", []byte(msg), 0660)
|
||||
err := ioutil.WriteFile("/run/termination-log", []byte(msg), 0660)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
log.Error(msg)
|
||||
|
||||
if collectDiagOnFail {
|
||||
logDiagnostics()
|
||||
}
|
||||
}
|
||||
|
||||
func getLogFormat() string {
|
||||
@@ -58,7 +67,7 @@ func formatSimple(datetime string, message string) string {
|
||||
// mirrorSystemErrorLogs starts a goroutine to mirror the contents of the MQ system error logs
|
||||
func mirrorSystemErrorLogs(ctx context.Context, wg *sync.WaitGroup, mf mirrorFunc) (chan error, error) {
|
||||
// Always use the JSON log as the source
|
||||
return mirrorLog(ctx, wg, "/var/mqm/errors/AMQERR01.json", false, mf)
|
||||
return mirrorLog(ctx, wg, "/var/mqm/errors/AMQERR01.json", false, mf, false)
|
||||
}
|
||||
|
||||
// mirrorQueueManagerErrorLogs starts a goroutine to mirror the contents of the MQ queue manager error logs
|
||||
@@ -70,7 +79,7 @@ func mirrorQueueManagerErrorLogs(ctx context.Context, wg *sync.WaitGroup, name s
|
||||
return nil, err
|
||||
}
|
||||
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json")
|
||||
return mirrorLog(ctx, wg, f, fromStart, mf)
|
||||
return mirrorLog(ctx, wg, f, fromStart, mf, true)
|
||||
}
|
||||
|
||||
func getDebug() bool {
|
||||
@@ -91,17 +100,35 @@ func configureLogger(name string) (mirrorFunc, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return log.LogDirect, nil
|
||||
return func(msg string, isQMLog bool) bool {
|
||||
obj, err := processLogMessage(msg)
|
||||
if err == nil && isQMLog && filterQMLogMessage(obj) {
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshall JSON - %v", msg)
|
||||
} else {
|
||||
fmt.Println(msg)
|
||||
}
|
||||
return true
|
||||
}, nil
|
||||
case "basic":
|
||||
log, err = logger.NewLogger(os.Stderr, d, false, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(msg string) {
|
||||
return func(msg string, isQMLog bool) bool {
|
||||
// Parse the JSON message, and print a simplified version
|
||||
var obj map[string]interface{}
|
||||
json.Unmarshal([]byte(msg), &obj)
|
||||
obj, err := processLogMessage(msg)
|
||||
if err == nil && isQMLog && filterQMLogMessage(obj) {
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshall JSON - %v", err)
|
||||
} else {
|
||||
fmt.Printf(formatSimple(obj["ibm_datetime"].(string), obj["message"].(string)))
|
||||
}
|
||||
return true
|
||||
}, nil
|
||||
default:
|
||||
log, err = logger.NewLogger(os.Stdout, d, false, name)
|
||||
@@ -111,3 +138,57 @@ func configureLogger(name string) (mirrorFunc, error) {
|
||||
return nil, fmt.Errorf("invalid value for LOG_FORMAT: %v", f)
|
||||
}
|
||||
}
|
||||
|
||||
func processLogMessage(msg string) (map[string]interface{}, error) {
|
||||
var obj map[string]interface{}
|
||||
err := json.Unmarshal([]byte(msg), &obj)
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func filterQMLogMessage(obj map[string]interface{}) bool {
|
||||
hostname, err := os.Hostname()
|
||||
if os.Getenv("MQ_MULTI_INSTANCE") == "true" && err == nil && !strings.Contains(obj["host"].(string), hostname) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func logDiagnostics() {
|
||||
log.Debug("--- Start Diagnostics ---")
|
||||
|
||||
// show the directory ownership/permissions
|
||||
// #nosec G104
|
||||
out, _, _ := command.Run("ls", "-l", "/mnt/")
|
||||
log.Debugf("/mnt/:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm")
|
||||
log.Debugf("/mnt/mqm:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm/data")
|
||||
log.Debugf("/mnt/mqm/data:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm-log/log")
|
||||
log.Debugf("/mnt/mqm-log/log:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/mnt/mqm-data/qmgrs")
|
||||
log.Debugf("/mnt/mqm-data/qmgrs:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/var/mqm")
|
||||
log.Debugf("/var/mqm:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/var/mqm/errors")
|
||||
log.Debugf("/var/mqm/errors:\n%s", out)
|
||||
// #nosec G104
|
||||
out, _, _ = command.Run("ls", "-l", "/etc/mqm")
|
||||
log.Debugf("/etc/mqm:\n%s", out)
|
||||
|
||||
// Print out summary of any FDCs
|
||||
// #nosec G204
|
||||
cmd := exec.Command("/opt/mqm/bin/ffstsummary")
|
||||
cmd.Dir = "/var/mqm/errors"
|
||||
// #nosec G104
|
||||
outB, _ := cmd.CombinedOutput()
|
||||
log.Debugf("ffstsummary:\n%s", string(outB))
|
||||
|
||||
log.Debug("--- End Diagnostics ---")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,21 +20,48 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/containerruntimelogger"
|
||||
"github.com/ibm-messaging/mq-container/internal/metrics"
|
||||
"github.com/ibm-messaging/mq-container/internal/name"
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
"github.com/ibm-messaging/mq-container/internal/tls"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqini"
|
||||
)
|
||||
|
||||
func doMain() error {
|
||||
var initFlag = flag.Bool("i", false, "initialize volume only, then exit")
|
||||
var infoFlag = flag.Bool("info", false, "Display debug info, then exit")
|
||||
var devFlag = flag.Bool("dev", false, "used when running this program from runmqdevserver to control log output")
|
||||
flag.Parse()
|
||||
|
||||
name, nameErr := name.GetQueueManagerName()
|
||||
mf, err := configureLogger(name)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check whether they only want debug info
|
||||
if *infoFlag {
|
||||
logVersionInfo()
|
||||
err = containerruntimelogger.LogContainerDetails(log)
|
||||
if err != nil {
|
||||
log.Printf("Error displaying container details: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = verifySingleProcess()
|
||||
if err != nil {
|
||||
// We don't do the normal termination here as it would create a termination file.
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if nameErr != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
@@ -58,27 +85,60 @@ func doMain() error {
|
||||
|
||||
// Start signal handler
|
||||
signalControl := signalHandler(name)
|
||||
// Enable diagnostic collecting on failure
|
||||
collectDiagOnFail = true
|
||||
|
||||
err = logConfig()
|
||||
if *devFlag == false {
|
||||
err = containerruntimelogger.LogContainerDetails(log)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
err = createVolume("/mnt/mqm")
|
||||
}
|
||||
|
||||
err = createVolume("/mnt/mqm/data")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
err = createVolume("/mnt/mqm-log/log")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
err = createVolume("/mnt/mqm-data/qmgrs")
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = createDirStructure()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// If init flag is set, exit now
|
||||
if *initFlag {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print out versioning information
|
||||
logVersionInfo()
|
||||
|
||||
err = postInit(name)
|
||||
keylabel, cmsDB, p12Trust, _, err := tls.ConfigureTLSKeystores(keyDir, trustDir, keyStoreDir)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = configureTLS(keylabel, cmsDB, *devFlag)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = postInit(name, keylabel, p12Trust)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
@@ -115,12 +175,25 @@ func doMain() error {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
err = startQueueManager()
|
||||
|
||||
err = mqini.AddStanzas(name)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
configureQueueManager()
|
||||
|
||||
err = startQueueManager(name)
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
if standby, _ := ready.IsRunningAsStandbyQM(name); !standby {
|
||||
err = configureQueueManager()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
enableMetrics := os.Getenv("MQ_ENABLE_METRICS")
|
||||
if enableMetrics == "true" || enableMetrics == "1" {
|
||||
@@ -136,7 +209,11 @@ func doMain() error {
|
||||
// Reap zombies now, just in case we've already got some
|
||||
signalControl <- reapNow
|
||||
// Write a file to indicate that chkmqready should now work as normal
|
||||
ready.Set()
|
||||
err = ready.Set()
|
||||
if err != nil {
|
||||
logTermination(err)
|
||||
return err
|
||||
}
|
||||
// Wait for terminate signal
|
||||
<-signalControl
|
||||
return nil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -44,23 +44,23 @@ func waitForFile(ctx context.Context, path string) (os.FileInfo, error) {
|
||||
return nil, fmt.Errorf("mirror: unable to get info on file %v", path)
|
||||
}
|
||||
}
|
||||
log.Debugf("File exists: %v, %v", path, fi.Size())
|
||||
return fi, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mirrorFunc func(msg string)
|
||||
type mirrorFunc func(msg string, isQMLog bool) bool
|
||||
|
||||
// mirrorAvailableMessages prints lines from the file, until no more are available
|
||||
func mirrorAvailableMessages(f *os.File, mf mirrorFunc) {
|
||||
func mirrorAvailableMessages(f *os.File, mf mirrorFunc, isQMLog bool) {
|
||||
scanner := bufio.NewScanner(f)
|
||||
count := 0
|
||||
for scanner.Scan() {
|
||||
t := scanner.Text()
|
||||
mf(t)
|
||||
if mf(t, isQMLog) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
log.Debugf("Mirrored %v log entries from %v", count, f.Name())
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func mirrorAvailableMessages(f *os.File, mf mirrorFunc) {
|
||||
// mirrorLog tails the specified file, and logs each line to stdout.
|
||||
// This is useful for usability, as the container console log can show
|
||||
// messages from the MQ error logs.
|
||||
func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart bool, mf mirrorFunc) (chan error, error) {
|
||||
func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart bool, mf mirrorFunc, isQMLog bool) (chan error, error) {
|
||||
errorChannel := make(chan error, 1)
|
||||
var offset int64 = -1
|
||||
var f *os.File
|
||||
@@ -121,6 +121,7 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
|
||||
if fi == nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("File exists: %v, %v", path, fi.Size())
|
||||
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -139,12 +140,15 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
|
||||
// Always start at the beginning if we've been told to go from the start
|
||||
if offset != 0 && !fromStart {
|
||||
log.Debugf("Seeking offset %v in file %v", offset, path)
|
||||
f.Seek(offset, 0)
|
||||
_, err = f.Seek(offset, 0)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to return to offset %v: %v", offset, err)
|
||||
}
|
||||
}
|
||||
closing := false
|
||||
for {
|
||||
// If there's already data there, mirror it now.
|
||||
mirrorAvailableMessages(f, mf)
|
||||
mirrorAvailableMessages(f, mf, isQMLog)
|
||||
// Wait for the new log file (after rotation)
|
||||
newFI, err := waitForFile(ctx, path)
|
||||
if err != nil {
|
||||
@@ -158,8 +162,11 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
|
||||
// log rotation happens before we can open the new file, then we
|
||||
// could skip all those messages. This could happen with a very small
|
||||
// MQ error log size.
|
||||
mirrorAvailableMessages(f, mf)
|
||||
f.Close()
|
||||
mirrorAvailableMessages(f, mf, isQMLog)
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to close mirror file handle: %v", err)
|
||||
}
|
||||
// Re-open file
|
||||
log.Debugf("Re-opening error log file %v", path)
|
||||
f, err = os.OpenFile(path, os.O_RDONLY, 0)
|
||||
@@ -170,7 +177,7 @@ func mirrorLog(ctx context.Context, wg *sync.WaitGroup, path string, fromStart b
|
||||
}
|
||||
fi = newFI
|
||||
// Don't seek this time, because we know it's a new file
|
||||
mirrorAvailableMessages(f, mf)
|
||||
mirrorAvailableMessages(f, mf, isQMLog)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -41,9 +41,10 @@ func TestMirrorLogWithoutRotation(t *testing.T) {
|
||||
count := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string) {
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string, isQMLog bool) bool {
|
||||
count++
|
||||
})
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -82,9 +83,10 @@ func TestMirrorLogWithRotation(t *testing.T) {
|
||||
count := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string) {
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), true, func(msg string, isQMLog bool) bool {
|
||||
count++
|
||||
})
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -135,9 +137,10 @@ func testMirrorLogExistingFile(t *testing.T, newQM bool) int {
|
||||
count := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), newQM, func(msg string) {
|
||||
_, err = mirrorLog(ctx, &wg, tmp.Name(), newQM, func(msg string, isQMLog bool) bool {
|
||||
count++
|
||||
})
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -179,8 +182,9 @@ func TestMirrorLogCancelWhileWaiting(t *testing.T) {
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}()
|
||||
_, err := mirrorLog(ctx, &wg, "fake.log", true, func(msg string) {
|
||||
})
|
||||
_, err := mirrorLog(ctx, &wg, "fake.log", true, func(msg string, isQMLog bool) bool {
|
||||
return true
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/genuinetools/amicontained/container"
|
||||
)
|
||||
|
||||
func logContainerRuntime() error {
|
||||
r, err := container.DetectRuntime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Container runtime: %v", r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func logBaseImage() error {
|
||||
buf, err := ioutil.ReadFile("/etc/os-release")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(string(buf), "\n")
|
||||
for _, l := range lines {
|
||||
if strings.HasPrefix(l, "PRETTY_NAME=") {
|
||||
words := strings.Split(l, "\"")
|
||||
if len(words) >= 2 {
|
||||
log.Printf("Base image: %v", words[1])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logUser() {
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
g, err := u.GroupIds()
|
||||
if err != nil {
|
||||
log.Printf("Running as user ID %v (%v) with primary group %v", u.Uid, u.Name, u.Gid)
|
||||
} else {
|
||||
// Look for the primary group in the list of group IDs
|
||||
for i, v := range g {
|
||||
if v == u.Gid {
|
||||
// Remove the element from the slice
|
||||
g = append(g[:i], g[i+1:]...)
|
||||
}
|
||||
}
|
||||
log.Printf("Running as user ID %v (%v) with primary group %v, and supplemental groups %v", u.Uid, u.Name, u.Gid, strings.Join(g, ","))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logCapabilities logs the Linux capabilities (e.g. setuid, setgid). See https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
|
||||
func logCapabilities() error {
|
||||
caps, err := container.Capabilities()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range caps {
|
||||
if len(v) > 0 {
|
||||
log.Printf("Capabilities (%s set): %v", strings.ToLower(k), strings.Join(v, ","))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// logSeccomp logs the seccomp enforcing mode, which affects which kernel calls can be made
|
||||
func logSeccomp() error {
|
||||
s, err := container.SeccompEnforcingMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("seccomp enforcing mode: %v", s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// logSecurityAttributes logs the security attributes of the current process.
|
||||
// The security attributes indicate whether AppArmor or SELinux are being used,
|
||||
// and what the level of confinement is.
|
||||
func logSecurityAttributes() error {
|
||||
a, err := readProc("/proc/self/attr/current")
|
||||
// On some systems, if AppArmor or SELinux are not installed, you get an
|
||||
// error when you try and read `/proc/self/attr/current`, even though the
|
||||
// file exists.
|
||||
if err != nil || a == "" {
|
||||
a = "none"
|
||||
}
|
||||
log.Printf("Process security attributes: %v", a)
|
||||
return nil
|
||||
}
|
||||
|
||||
func readProc(filename string) (value string, err error) {
|
||||
buf, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(buf)), nil
|
||||
}
|
||||
|
||||
func readMounts() error {
|
||||
all, err := readProc("/proc/mounts")
|
||||
if err != nil {
|
||||
log.Print("Error: Couldn't read /proc/mounts")
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(all, "\n")
|
||||
detected := false
|
||||
for i := range lines {
|
||||
parts := strings.Split(lines[i], " ")
|
||||
//dev := parts[0]
|
||||
mountPoint := parts[1]
|
||||
fsType := parts[2]
|
||||
if strings.Contains(mountPoint, "/mnt") {
|
||||
log.Printf("Detected '%v' volume mounted to %v", fsType, mountPoint)
|
||||
detected = true
|
||||
}
|
||||
}
|
||||
if !detected {
|
||||
log.Print("No volume detected. Persistent messages may be lost")
|
||||
} else {
|
||||
return checkFS("/mnt/mqm")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logConfig() error {
|
||||
log.Printf("CPU architecture: %v", runtime.GOARCH)
|
||||
if runtime.GOOS == "linux" {
|
||||
var err error
|
||||
osr, err := readProc("/proc/sys/kernel/osrelease")
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
} else {
|
||||
log.Printf("Linux kernel version: %v", osr)
|
||||
}
|
||||
logContainerRuntime()
|
||||
logBaseImage()
|
||||
fileMax, err := readProc("/proc/sys/fs/file-max")
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
} else {
|
||||
log.Printf("Maximum file handles: %v", fileMax)
|
||||
}
|
||||
logUser()
|
||||
logCapabilities()
|
||||
logSeccomp()
|
||||
logSecurityAttributes()
|
||||
err = readMounts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Unsupported platform: %v", runtime.GOOS)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// +build mqdev
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -19,23 +17,26 @@ package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/tls"
|
||||
)
|
||||
|
||||
// postInit is run after /var/mqm is set up
|
||||
// This version of postInit is only included as part of the MQ Advanced for Developers build
|
||||
func postInit(name string) error {
|
||||
disable := os.Getenv("MQ_DISABLE_WEB_CONSOLE")
|
||||
if disable != "true" && disable != "1" {
|
||||
// Configure the web server (if installed)
|
||||
err := configureWebServer()
|
||||
func postInit(name, keylabel string, p12Trust tls.KeyStoreData) error {
|
||||
enableWebServer := os.Getenv("MQ_ENABLE_EMBEDDED_WEB_SERVER")
|
||||
if enableWebServer == "true" || enableWebServer == "1" {
|
||||
// Configure the web server (if enabled)
|
||||
keystore, err := configureWebServer(keylabel, p12Trust)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Start the web server, in the background (if installed)
|
||||
// WARNING: No error handling or health checking available for the web server,
|
||||
// which is why it's limited to use with MQ Advanced for Developers only
|
||||
// WARNING: No error handling or health checking available for the web server
|
||||
go func() {
|
||||
startWebServer()
|
||||
err = startWebServer(keystore, p12Trust.Password)
|
||||
if err != nil {
|
||||
log.Printf("Error starting web server: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
64
cmd/runmqserver/process.go
Normal file
64
cmd/runmqserver/process.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
)
|
||||
|
||||
// Verifies that we are the main or only instance of this program
|
||||
func verifySingleProcess() error {
|
||||
programName, err := determineExecutable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to determine name of this program - %v", err)
|
||||
}
|
||||
|
||||
// Verify that there is only one runmqserver
|
||||
_, err = verifyOnlyOne(programName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("You cannot run more than one instance of this program")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verifies that there is only one instance running of the given program name.
|
||||
func verifyOnlyOne(programName string) (int, error) {
|
||||
// #nosec G104
|
||||
out, _, _ := command.Run("ps", "-e", "--format", "cmd")
|
||||
//if this goes wrong then assume we are the only one
|
||||
numOfProg := strings.Count(out, programName)
|
||||
if numOfProg != 1 {
|
||||
return numOfProg, fmt.Errorf("Expected there to be only 1 instance of %s but found %d", programName, numOfProg)
|
||||
}
|
||||
return numOfProg, nil
|
||||
}
|
||||
|
||||
// Determines the name of the currently running executable.
|
||||
func determineExecutable() (string, error) {
|
||||
file, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, exec := filepath.Split(file)
|
||||
return exec, nil
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,8 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -24,11 +25,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
containerruntime "github.com/ibm-messaging/mq-container/internal/containerruntime"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqscredact"
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
)
|
||||
|
||||
// createDirStructure creates the default MQ directory structure under /var/mqm
|
||||
func createDirStructure() error {
|
||||
out, _, err := command.Run("/opt/mqm/bin/crtmqdir", "-f", "-s")
|
||||
out, _, err := command.Run("/opt/mqm/bin/crtmqdir", "-f", "-a")
|
||||
if err != nil {
|
||||
log.Printf("Error creating directory structure: %v\n", string(out))
|
||||
return err
|
||||
@@ -38,20 +42,48 @@ func createDirStructure() error {
|
||||
}
|
||||
|
||||
// createQueueManager creates a queue manager, if it doesn't already exist.
|
||||
// It returns true if one was created, or false if one already existed
|
||||
// It returns true if one was created (or a standby was created), or false if one already existed
|
||||
func createQueueManager(name string) (bool, error) {
|
||||
log.Printf("Creating queue manager %v", name)
|
||||
out, rc, err := command.Run("crtmqm", "-q", "-p", "1414", name)
|
||||
if err != nil {
|
||||
// 8=Queue manager exists, which is fine
|
||||
if rc == 8 {
|
||||
|
||||
// Run 'dspmqinf' to check if 'mqs.ini' configuration file exists
|
||||
// If command succeeds, the queue manager (or standby queue manager) has already been created
|
||||
_, _, err := command.Run("dspmqinf", name)
|
||||
if err == nil {
|
||||
log.Printf("Detected existing queue manager %v", name)
|
||||
return false, nil
|
||||
}
|
||||
log.Printf("crtmqm returned %v", rc)
|
||||
log.Println(string(out))
|
||||
|
||||
mounts, err := containerruntime.GetMounts()
|
||||
if err != nil {
|
||||
log.Printf("Error getting mounts for queue manager")
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if 'qm.ini' configuration file exists for the queue manager
|
||||
// TODO : handle possible race condition - use a file lock?
|
||||
dataDir := getQueueManagerDataDir(mounts, name)
|
||||
_, err = os.Stat(filepath.Join(dataDir, "qm.ini"))
|
||||
if err != nil {
|
||||
// If 'qm.ini' is not found - run 'crtmqm' to create a new queue manager
|
||||
args := getCreateQueueManagerArgs(mounts, name)
|
||||
out, rc, err := command.Run("crtmqm", args...)
|
||||
if err != nil {
|
||||
log.Printf("Error %v creating queue manager: %v", rc, string(out))
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
// If 'qm.ini' is found - run 'addmqinf' to create a standby queue manager with existing configuration
|
||||
args := getCreateStandbyQueueManagerArgs(name)
|
||||
out, rc, err := command.Run("addmqinf", args...)
|
||||
if err != nil {
|
||||
log.Printf("Error %v creating standby queue manager: %v", rc, string(out))
|
||||
return false, err
|
||||
}
|
||||
log.Println("Created standby queue manager")
|
||||
return true, nil
|
||||
}
|
||||
log.Println("Created queue manager")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -68,10 +100,15 @@ func updateCommandLevel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func startQueueManager() error {
|
||||
func startQueueManager(name string) error {
|
||||
log.Println("Starting queue manager")
|
||||
out, rc, err := command.Run("strmqm")
|
||||
out, rc, err := command.Run("strmqm", "-x", name)
|
||||
if err != nil {
|
||||
// 30=standby queue manager started, which is fine
|
||||
if rc == 30 {
|
||||
log.Printf("Started standby queue manager")
|
||||
return nil
|
||||
}
|
||||
log.Printf("Error %v starting queue manager: %v", rc, string(out))
|
||||
return err
|
||||
}
|
||||
@@ -86,35 +123,49 @@ func configureQueueManager() error {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if strings.HasSuffix(file.Name(), ".mqsc") {
|
||||
abs := filepath.Join(configDir, file.Name())
|
||||
// #nosec G204
|
||||
verify := exec.Command("runmqsc", "-v", "-e")
|
||||
// #nosec G204 - command is fixed, no injection vector
|
||||
cmd := exec.Command("runmqsc")
|
||||
stdin, err := cmd.StdinPipe()
|
||||
// Read mqsc file into variable
|
||||
// #nosec G304 - filename variable is derived from contents of 'configDir' which is a defined constant
|
||||
mqsc, err := ioutil.ReadFile(abs)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
log.Printf("Error reading file %v: %v", abs, err)
|
||||
continue
|
||||
}
|
||||
// Open the MQSC file for reading
|
||||
f, err := os.Open(abs)
|
||||
// Write mqsc to buffer
|
||||
var buffer bytes.Buffer
|
||||
_, err = buffer.Write(mqsc)
|
||||
if err != nil {
|
||||
log.Printf("Error opening %v: %v", abs, err)
|
||||
log.Printf("Error writing MQSC file %v to buffer: %v", abs, err)
|
||||
continue
|
||||
}
|
||||
// Copy the contents to stdin of the runmqsc process
|
||||
_, err = io.Copy(stdin, f)
|
||||
verifyBuffer := buffer
|
||||
|
||||
// Buffer mqsc to stdin of runmqsc
|
||||
cmd.Stdin = &buffer
|
||||
verify.Stdin = &verifyBuffer
|
||||
|
||||
// Verify the MQSC commands
|
||||
out, err := verify.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Error reading %v: %v", abs, err)
|
||||
log.Errorf("Error verifying MQSC file %v (%v):\n\t%v", file.Name(), err, formatMQSCOutput(string(out)))
|
||||
return fmt.Errorf("Error verifying MQSC file %v (%v):\n\t%v", file.Name(), err, formatMQSCOutput(string(out)))
|
||||
}
|
||||
f.Close()
|
||||
stdin.Close()
|
||||
// Run the command and wait for completion
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
||||
// Run runmqsc command
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
log.Errorf("Error running MQSC file %v (%v):\n\t%v", file.Name(), err, formatMQSCOutput(string(out)))
|
||||
continue
|
||||
} else {
|
||||
// Print the runmqsc output, adding tab characters to make it more readable as part of the log
|
||||
log.Printf("Output for \"runmqsc\" with %v:\n\t%v", abs, strings.Replace(string(out), "\n", "\n\t", -1))
|
||||
log.Printf("Output for \"runmqsc\" with %v:\n\t%v", abs, formatMQSCOutput(string(out)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -122,11 +173,77 @@ func configureQueueManager() error {
|
||||
|
||||
func stopQueueManager(name string) error {
|
||||
log.Println("Stopping queue manager")
|
||||
out, _, err := command.Run("endmqm", "-w", name)
|
||||
isStandby, err := ready.IsRunningAsStandbyQM(name)
|
||||
if err != nil {
|
||||
log.Printf("Error stopping queue manager: %v", string(out))
|
||||
log.Printf("Error getting status for queue manager %v: ", name, err.Error())
|
||||
return err
|
||||
}
|
||||
args := []string{"-w", "-r", name}
|
||||
if os.Getenv("MQ_MULTI_INSTANCE") == "true" {
|
||||
if isStandby {
|
||||
args = []string{"-x", name}
|
||||
} else {
|
||||
args = []string{"-s", "-w", "-r", name}
|
||||
}
|
||||
}
|
||||
out, rc, err := command.Run("endmqm", args...)
|
||||
if err != nil {
|
||||
log.Printf("Error %v stopping queue manager: %v", rc, string(out))
|
||||
return err
|
||||
}
|
||||
if isStandby {
|
||||
log.Printf("Stopped standby queue manager")
|
||||
} else {
|
||||
log.Println("Stopped queue manager")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatMQSCOutput(out string) string {
|
||||
// redact sensitive information
|
||||
out, _ = mqscredact.Redact(out)
|
||||
|
||||
// add tab characters to make it more readable as part of the log
|
||||
return strings.Replace(string(out), "\n", "\n\t", -1)
|
||||
}
|
||||
|
||||
func isStandbyQueueManager(name string) (bool, error) {
|
||||
out, rc, err := command.Run("dspmq", "-n", "-m", name)
|
||||
if err != nil {
|
||||
log.Printf("Error %v getting status for queue manager %v: %v", rc, name, string(out))
|
||||
return false, err
|
||||
}
|
||||
if strings.Contains(string(out), "(RUNNING AS STANDBY)") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func getQueueManagerDataDir(mounts map[string]string, name string) string {
|
||||
dataDir := filepath.Join("/var/mqm/qmgrs", name)
|
||||
if _, ok := mounts["/mnt/mqm-data"]; ok {
|
||||
dataDir = filepath.Join("/mnt/mqm-data/qmgrs", name)
|
||||
}
|
||||
return dataDir
|
||||
}
|
||||
|
||||
func getCreateQueueManagerArgs(mounts map[string]string, name string) []string {
|
||||
args := []string{"-q", "-p", "1414"}
|
||||
if _, ok := mounts["/mnt/mqm-log"]; ok {
|
||||
args = append(args, "-ld", "/mnt/mqm-log/log")
|
||||
}
|
||||
if _, ok := mounts["/mnt/mqm-data"]; ok {
|
||||
args = append(args, "-md", "/mnt/mqm-data/qmgrs")
|
||||
}
|
||||
args = append(args, name)
|
||||
return args
|
||||
}
|
||||
|
||||
func getCreateStandbyQueueManagerArgs(name string) []string {
|
||||
args := []string{"-s", "QueueManager"}
|
||||
args = append(args, "-v", fmt.Sprintf("Name=%v", name))
|
||||
args = append(args, "-v", fmt.Sprintf("Directory=%v", name))
|
||||
args = append(args, "-v", "Prefix=/var/mqm")
|
||||
args = append(args, "-v", fmt.Sprintf("DataPath=/mnt/mqm-data/qmgrs/%v", name))
|
||||
return args
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ func signalHandler(qmgr string) chan int {
|
||||
log.Printf("Signal received: %v", sig)
|
||||
signal.Stop(reapSignals)
|
||||
signal.Stop(stopSignals)
|
||||
metrics.StopMetricsGathering()
|
||||
metrics.StopMetricsGathering(log)
|
||||
// #nosec G104
|
||||
stopQueueManager(qmgr)
|
||||
// One final reap
|
||||
reapZombies()
|
||||
|
||||
163
cmd/runmqserver/tls.go
Normal file
163
cmd/runmqserver/tls.go
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
"github.com/ibm-messaging/mq-container/internal/keystore"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
|
||||
"github.com/ibm-messaging/mq-container/internal/tls"
|
||||
)
|
||||
|
||||
// Location to store the keystores
|
||||
const keyStoreDir = "/run/runmqserver/tls/"
|
||||
|
||||
// KeyDir is the location of the certificate keys to import
|
||||
const keyDir = "/etc/mqm/pki/keys"
|
||||
|
||||
// TrustDir is the location of the Certifates to add
|
||||
const trustDir = "/etc/mqm/pki/trust"
|
||||
|
||||
// configureWebTLS configures TLS for Web Console
|
||||
func configureWebTLS(label string) error {
|
||||
// Return immediately if we have no certificate to use as identity
|
||||
if label == "" && os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
webConfigDir := "/etc/mqm/web/installations/Installation1/servers/mqweb"
|
||||
tls := "tls.xml"
|
||||
|
||||
tlsConfig := filepath.Join(webConfigDir, tls)
|
||||
newTLSConfig := filepath.Join(webConfigDir, tls+".tpl")
|
||||
err := os.Remove(tlsConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not delete file %s: %v", tlsConfig, err)
|
||||
}
|
||||
// we symlink here to prevent issues on restart
|
||||
err = os.Symlink(newTLSConfig, tlsConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not create symlink %s->%s: %v", newTLSConfig, tlsConfig, err)
|
||||
}
|
||||
mqmUID, mqmGID, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not find mqm user or group: %v", err)
|
||||
}
|
||||
err = os.Chown(tlsConfig, mqmUID, mqmGID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could change ownership of %s to mqm: %v", tlsConfig, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureTLSDev configures TLS for developer defaults
|
||||
func configureTLSDev() error {
|
||||
const mqsc string = "/etc/mqm/20-dev-tls.mqsc"
|
||||
const mqscTemplate string = mqsc + ".tpl"
|
||||
|
||||
if os.Getenv("MQ_DEV") == "true" {
|
||||
err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{}, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err := os.Stat(mqsc)
|
||||
if !os.IsNotExist(err) {
|
||||
err = os.Remove(mqsc)
|
||||
if err != nil {
|
||||
log.Errorf("Error removing file %s: %v", mqsc, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureTLS configures TLS for queue manager
|
||||
func configureTLS(certLabel string, cmsKeystore tls.KeyStoreData, devmode bool) error {
|
||||
log.Debug("Configuring TLS")
|
||||
|
||||
const mqsc string = "/etc/mqm/15-tls.mqsc"
|
||||
const mqscTemplate string = mqsc + ".tpl"
|
||||
|
||||
err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{
|
||||
"SSLKeyR": strings.TrimSuffix(cmsKeystore.Keystore.Filename, ".kdb"),
|
||||
"CertificateLabel": certLabel,
|
||||
}, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if devmode && certLabel != "" {
|
||||
err = configureTLSDev()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureSSOTLS configures MQ Console TLS for Single Sign-On
|
||||
func configureSSOTLS(p12TrustStore tls.KeyStoreData) (string, error) {
|
||||
// TODO find way to supply this
|
||||
// Override the webstore variables to hard coded defaults
|
||||
webKeyStoreName := tls.IntegrationDefaultLabel + ".p12"
|
||||
|
||||
// Check keystore exists
|
||||
ks := filepath.Join(keyStoreDir, webKeyStoreName)
|
||||
_, err := os.Stat(ks)
|
||||
// Now we know if the file exists let's check whether we should have it or not.
|
||||
// Check if we're being told to generate the certificate
|
||||
genHostName := os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME")
|
||||
if genHostName != "" {
|
||||
// We've got to generate the certificate with the hostname given
|
||||
if err == nil {
|
||||
log.Printf("Replacing existing keystore %s - generating new certificate", ks)
|
||||
}
|
||||
// Keystore doesn't exist so create it and populate a certificate
|
||||
newKS := keystore.NewPKCS12KeyStore(ks, p12TrustStore.Password)
|
||||
err = newKS.Create()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to create keystore %s: %v", ks, err)
|
||||
}
|
||||
|
||||
err = newKS.CreateSelfSignedCertificate("default", fmt.Sprintf("CN=%s", genHostName), genHostName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to generate certificate in keystore %s with DN of 'CN=%s': %v", ks, genHostName, err)
|
||||
}
|
||||
} else {
|
||||
// Keystore should already exist
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to find existing keystore %s: %v", ks, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check truststore exists
|
||||
_, err = os.Stat(p12TrustStore.Keystore.Filename)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to find existing truststore %s: %v", p12TrustStore.Keystore.Filename, err)
|
||||
}
|
||||
|
||||
return webKeyStoreName, nil
|
||||
}
|
||||
@@ -23,9 +23,14 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ImageCreated is the date the image was built
|
||||
ImageCreated = "Not specified"
|
||||
// ImageRevision is the source control revision identifier
|
||||
ImageRevision = "Not specified"
|
||||
// ImageSource is the URL to get source code for building the image
|
||||
ImageSource = "Not specified"
|
||||
// ImageTag is the tag of the image
|
||||
ImageTag = "Not specified"
|
||||
)
|
||||
|
||||
func logDateStamp() {
|
||||
@@ -33,11 +38,15 @@ func logDateStamp() {
|
||||
}
|
||||
|
||||
func logGitRepo() {
|
||||
log.Printf("Image revision: %v", ImageRevision)
|
||||
// log.Printf("Image revision: %v", ImageRevision)
|
||||
}
|
||||
|
||||
func logGitCommit() {
|
||||
log.Printf("Image source: %v", ImageSource)
|
||||
// log.Printf("Image source: %v", ImageSource)
|
||||
}
|
||||
|
||||
func logImageTag() {
|
||||
log.Printf("Image tag: %v", ImageTag)
|
||||
}
|
||||
|
||||
func logMQVersion() {
|
||||
@@ -64,5 +73,6 @@ func logVersionInfo() {
|
||||
logDateStamp()
|
||||
logGitRepo()
|
||||
logGitCommit()
|
||||
logImageTag()
|
||||
logMQVersion()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// +build mqdev
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -19,21 +17,62 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
"github.com/ibm-messaging/mq-container/internal/copy"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
|
||||
"github.com/ibm-messaging/mq-container/internal/tls"
|
||||
)
|
||||
|
||||
func startWebServer() error {
|
||||
func startWebServer(keystore, keystorepw string) error {
|
||||
_, err := os.Stat("/opt/mqm/bin/strmqweb")
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
log.Debug("Skipping web server, because it's not installed")
|
||||
return nil
|
||||
}
|
||||
log.Println("Starting web server")
|
||||
out, rc, err := command.RunAsMQM("strmqweb")
|
||||
// #nosec G204 - command is fixed, no injection vector
|
||||
cmd := exec.Command("strmqweb")
|
||||
// Set a default app password for the web server, if one isn't already set
|
||||
_, set := os.LookupEnv("MQ_APP_PASSWORD")
|
||||
if !set {
|
||||
// Take all current environment variables, and add the app password
|
||||
cmd.Env = append(os.Environ(), "MQ_APP_PASSWORD=passw0rd")
|
||||
} else {
|
||||
cmd.Env = os.Environ()
|
||||
}
|
||||
|
||||
// TLS enabled
|
||||
if keystore != "" {
|
||||
cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTORE="+keystore)
|
||||
cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTOREPW="+keystorepw)
|
||||
}
|
||||
|
||||
uid, gid, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentUID, err := strconv.Atoi(u.Uid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error converting UID to string: %v", err)
|
||||
}
|
||||
// Add credentials to run as 'mqm', only if we aren't already 'mqm'
|
||||
if currentUID != uid {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
}
|
||||
out, rc, err := command.RunCmd(cmd)
|
||||
if err != nil {
|
||||
log.Printf("Error %v starting web server: %v", rc, string(out))
|
||||
return err
|
||||
@@ -42,45 +81,82 @@ func startWebServer() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFile copies the specified file
|
||||
func CopyFile(src, dest string) error {
|
||||
log.Debugf("Copying file %v to %v", src, dest)
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
func configureSSO(p12TrustStore tls.KeyStoreData) (string, error) {
|
||||
// Ensure all required environment variables are set for SSO
|
||||
requiredEnvVars := []string{
|
||||
"MQ_WEB_ADMIN_USERS",
|
||||
"MQ_OIDC_CLIENT_ID",
|
||||
"MQ_OIDC_CLIENT_SECRET",
|
||||
"MQ_OIDC_UNIQUE_USER_IDENTIFIER",
|
||||
"MQ_OIDC_AUTHORIZATION_ENDPOINT",
|
||||
"MQ_OIDC_TOKEN_ENDPOINT",
|
||||
"MQ_OIDC_JWK_ENDPOINT",
|
||||
"MQ_OIDC_ISSUER_IDENTIFIER",
|
||||
}
|
||||
for _, envVar := range requiredEnvVars {
|
||||
if len(os.Getenv(envVar)) == 0 {
|
||||
return "", fmt.Errorf("%v must be set when MQ_BETA_ENABLE_SSO=true", envVar)
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0770)
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = out.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func configureWebServer() error {
|
||||
_, err := os.Stat("/opt/mqm/bin/strmqweb")
|
||||
// Check mqweb directory exists
|
||||
const mqwebDir string = "/etc/mqm/web/installations/Installation1/servers/mqweb"
|
||||
_, err := os.Stat(mqwebDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Process SSO template for generating file mqwebuser.xml
|
||||
adminUsers := strings.Split(os.Getenv("MQ_WEB_ADMIN_USERS"), "\n")
|
||||
err = mqtemplate.ProcessTemplateFile(mqwebDir+"/mqwebuser.xml.tpl", mqwebDir+"/mqwebuser.xml", map[string][]string{"AdminUser": adminUsers}, log)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Configure SSO TLS
|
||||
return configureSSOTLS(p12TrustStore)
|
||||
}
|
||||
|
||||
func configureWebServer(keyLabel string, p12Trust tls.KeyStoreData) (string, error) {
|
||||
var keystore string
|
||||
// Configure TLS for Web Console first if we have a certificate to use
|
||||
err := configureWebTLS(keyLabel)
|
||||
if err != nil {
|
||||
return keystore, err
|
||||
}
|
||||
if keyLabel != "" {
|
||||
keystore = keyLabel + ".p12"
|
||||
}
|
||||
|
||||
// Configure Single-Sign-On for the web server (if enabled)
|
||||
enableSSO := os.Getenv("MQ_BETA_ENABLE_SSO")
|
||||
if enableSSO == "true" || enableSSO == "1" {
|
||||
keystore, err = configureSSO(p12Trust)
|
||||
if err != nil {
|
||||
return keystore, err
|
||||
}
|
||||
}
|
||||
_, err = os.Stat("/opt/mqm/bin/strmqweb")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return keystore, nil
|
||||
}
|
||||
return keystore, err
|
||||
}
|
||||
const webConfigDir string = "/etc/mqm/web"
|
||||
_, err = os.Stat(webConfigDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
return keystore, nil
|
||||
}
|
||||
return err
|
||||
return keystore, err
|
||||
}
|
||||
uid, gid, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
return err
|
||||
return keystore, err
|
||||
}
|
||||
const prefix string = "/etc/mqm/web"
|
||||
err = filepath.Walk(prefix, func(from string, info os.FileInfo, err error) error {
|
||||
@@ -99,6 +175,7 @@ func configureWebServer() error {
|
||||
}
|
||||
if info.IsDir() {
|
||||
if !exists {
|
||||
// #nosec G301 - write group permissions are required
|
||||
err := os.MkdirAll(to, 0770)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,7 +188,7 @@ func configureWebServer() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := CopyFile(from, to)
|
||||
err := copy.CopyFile(from, to)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
@@ -123,5 +200,5 @@ func configureWebServer() error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
return keystore, err
|
||||
}
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
# Building a Docker image
|
||||
# Building a container image
|
||||
|
||||
## Prerequisites
|
||||
You need to ensure you have the following tools installed:
|
||||
* [Docker](https://www.docker.com/) V17.06.1 or later
|
||||
|
||||
You need to have the following tools installed:
|
||||
|
||||
* [Docker](https://www.docker.com/) V17.06.1 or later, or [Podman](https://podman.io) V1.0 or later
|
||||
* [GNU make](https://www.gnu.org/software/make/)
|
||||
|
||||
If you are working in the Windows Subsystem for Linux, follow [this guide by Microsoft to set up Docker](https://blogs.msdn.microsoft.com/commandline/2017/12/08/cross-post-wsl-interoperability-with-docker/) first.
|
||||
|
||||
## Building a production image
|
||||
This procedure works for building the MQ Continuous Delivery release, on `x86_64`, `ppc64le` and `s390x` architectures.
|
||||
|
||||
This procedure works for building the MQ Continuous Delivery release, on `amd64`, `ppc64le` and `s390x` architectures.
|
||||
|
||||
1. Create a `downloads` directory in the root of this repository
|
||||
2. Download MQ from IBM Passport Advantage, and place the downloaded file (for example, `IBM_MQ_9.1.0.0_UBUNTU_X86-64.tar.gz` for MQ V9.1.0 for Ubuntu on x86_64 architecture) in the `downloads` directory
|
||||
2. Run `make build-advancedserver`
|
||||
2. Download MQ from [IBM Passport Advantage](https://www.ibm.com/software/passportadvantage/) or [IBM Fix Central](https://www.ibm.com/support/fixcentral), and place the downloaded file (for example, `IBM_MQ_9.1.3_LINUX_X86-64.tar.gz`) in the `downloads` directory
|
||||
3. Run `make build-advancedserver`
|
||||
|
||||
> **Warning**: Note that MQ offers two different sets of packaging on Linux: one is called "MQ for Linux" and contains RPM files for installing on Red Hat Enterprise Linux and SUSE Linux Enterprise Server. The other package is called "MQ for Ubuntu", and contains DEB files for installing on Ubuntu.
|
||||
|
||||
You can build a different version of MQ by setting the `MQ_VERSION` environment variable, for example:
|
||||
|
||||
```bash
|
||||
MQ_VERSION=9.0.5.0 make build-advancedserver
|
||||
```
|
||||
> **Warning**: Note that MQ offers two different sets of packaging on Linux: one is called "MQ for Linux" and contains RPM files for installing on Red Hat Enterprise Linux and SUSE Linux Enterprise Server; the other is for Ubuntu. The MQ container build uses a Red Hat Universal Base Image, so you need the "MQ for Linux" RPM files.
|
||||
|
||||
If you have an MQ archive file with a different file name, you can specify a particular file (which must be in the `downloads` directory). You should also specify the MQ version, so that the resulting image is tagged correctly, for example:
|
||||
|
||||
@@ -29,22 +26,10 @@ MQ_ARCHIVE=mq-1.2.3.4.tar.gz MQ_VERSION=1.2.3.4 make build-advancedserver
|
||||
```
|
||||
|
||||
## Building a developer image
|
||||
Run `make build-devserver`, which will download the latest version of MQ Advanced for Developers from IBM developerWorks. This is currently only available on the `x86_64` architecture.
|
||||
Run `make build-devserver`, which will download the latest version of MQ Advanced for Developers from IBM developerWorks. This is currently only available on the `amd64` architecture.
|
||||
|
||||
You can use the environment variable `MQ_ARCHIVE_DEV` to specify an alternative local file to install from (which must be in the `downloads` directory).
|
||||
|
||||
## Building on a different base image
|
||||
By default, the MQ images use Ubuntu as the base layer. You can build using a Red Hat Enterprise Linux compatible base layer by setting the `BASE_IMAGE` environment variable. For example:
|
||||
|
||||
```
|
||||
BASE_IMAGE=centos:7 make build-advancedserver
|
||||
```
|
||||
|
||||
The `make` tool will try and locate the right archive file under the `downloads` directory, based on your platform architecture and your `MQ_VERSION` environment variable, for example `IBM_MQ_9.1.0.0_LINUX_X86_64.tar.gz` for MQ V9.1.0.0 on x86_64. You can also set the `MQ_ARCHIVE` environment variable to set the specific file name.
|
||||
|
||||
Note that if you are using Red Hat Enterprise Linux, you will need to create your own base image layer, with your subscription enabled, as described [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_atomic_host/7/html/getting_started_with_containers/get_started_with_docker_formatted_container_images). The MQ image build needs to install some additional packages, and a subscription is required to access the Red Hat repositories.
|
||||
|
||||
|
||||
## Installed components
|
||||
|
||||
This image includes the core MQ server, Java, language packs, and GSKit. This can be configured by setting the `MQ_PACKAGES` argument to `make`, or directly as a [Docker build argument](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables-build-arg).
|
||||
This image includes the core MQ server, Java, language packs, GSKit, and web server. This can be configured by setting the `MQ_PACKAGES` argument to `make`.
|
||||
|
||||
@@ -9,8 +9,8 @@ The MQ Developer Defaults supports some customization options, these are all con
|
||||
* **MQ_DEV** - Set this to `false` to stop the default objects being created.
|
||||
* **MQ_ADMIN_PASSWORD** - Changes the password of the `admin` user. Must be at least 8 characters long.
|
||||
* **MQ_APP_PASSWORD** - Changes the password of the app user. If set, this will cause the `DEV.APP.SVRCONN` channel to become secured and only allow connections that supply a valid userid and password. Must be at least 8 characters long.
|
||||
* **MQ_TLS_KEYSTORE** - Allows you to supply the location of a PKCS#12 keystore containing a single certificate which you want to use in both the web console and the queue manager. Requires `MQ_TLS_PASSPHRASE`. When enabled the channels created will be secured using the `TLS_RSA_WITH_AES_128_CBC_SHA256` CipherSpec. *Note*: you will need to make the keystore available inside your container, this can be done by mounting a volume to your container.
|
||||
* **MQ_TLS_PASSPHRASE** - Passphrase for the keystore referenced in `MQ_TLS_KEYSTORE`.
|
||||
* **MQ_TLS_KEYSTORE** - **DEPRECATED**. See section `Supplying TLS certificates` in [usage document](usage.md). Allows you to supply the location of a PKCS#12 keystore containing a single certificate which you want to use in both the web console and the queue manager. Requires `MQ_TLS_PASSPHRASE`. When enabled the channels created will be secured using the `TLS_RSA_WITH_AES_128_CBC_SHA256` CipherSpec. *Note*: you will need to make the keystore available inside your container, this can be done by mounting a volume to your container.
|
||||
* **MQ_TLS_PASSPHRASE** - **DEPRECATED**. See section `Supplying TLS certificates` in [usage document](usage.md). Passphrase for the keystore referenced in `MQ_TLS_KEYSTORE`.
|
||||
|
||||
## Details of the default configuration
|
||||
|
||||
@@ -52,4 +52,4 @@ If you choose to accept the security warning, you will be presented with the log
|
||||
|
||||
If you wish to change the password for the admin user, this can be done using the `MQ_ADMIN_PASSWORD` environment variable. If you supply a PKCS#12 keystore using the `MQ_TLS_KEYSTORE` environment variable, then the web console will be configured to use the certificate inside the keystore for HTTPS operations.
|
||||
|
||||
If you do not wish the web console to run, you can disable it by setting the environment variable `MQ_DISABLE_WEB_CONSOLE` to `true`.
|
||||
If you do not wish the web console to run, you can disable it by setting the environment variable `MQ_ENABLE_EMBEDDED_WEB_SERVER` to `false`.
|
||||
|
||||
@@ -24,6 +24,7 @@ The `runmqserver` command has the following responsibilities:
|
||||
- Works as PID 1, so is responsible for [reaping zombie processes](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/)
|
||||
* Creating and starting a queue manager
|
||||
* Configuring the queue manager, by running any MQSC scripts found under `/etc/mqm`
|
||||
* Starts the MQ web server (if enabled)
|
||||
* Starting Prometheus metrics generation for the queue manager (if enabled)
|
||||
* Indicates to the `chkmqready` command that configuration is complete, and that normal readiness checking can happen. This is done by writing a file into `/run/runmqserver`
|
||||
|
||||
@@ -36,8 +37,6 @@ The `runmqdevserver` command is added to the MQ Advanced for Developers image on
|
||||
2. Generates MQSC files to put in `/etc/mqm`, based on a template, which is updated with values based on supplied environment variables.
|
||||
3. If requested, it creates TLS key stores under `/run/runmqdevserver`, and configures MQ and the web server to use them
|
||||
|
||||
A special version of `runmqserver` is used in the developer image, which performs extra actions like starting the web server. This is built using the `mqdev` [build constraint](https://golang.org/pkg/go/build/#hdr-Build_Constraints).
|
||||
|
||||
## Prometheus metrics
|
||||
[Prometheus](https://prometheus.io) metrics are generated for the queue manager as follows:
|
||||
|
||||
|
||||
39
docs/security.md
Normal file
39
docs/security.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Security
|
||||
|
||||
## Container runtime
|
||||
|
||||
### User
|
||||
|
||||
The MQ server image is run using the "mqm" user, with a fixed UID and GID of 888.
|
||||
|
||||
### Capabilities
|
||||
|
||||
The MQ Advanced image requires no Linux capabilities, so you can drop any capabilities which are added by default. For example, in Docker you could do the following:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--cap-drop=ALL \
|
||||
--env LICENSE=accept \
|
||||
--env MQ_QMGR_NAME=QM1 \
|
||||
--detach \
|
||||
mqadvanced-server:9.1.3.0-amd64
|
||||
```
|
||||
|
||||
The MQ Advanced for Developers image does require the "chown", "setuid", "setgid" and "audit_write" capabilities (plus "dac_override" if you're using an image based on Red Hat Enterprise Linux). This is because it uses the "sudo" command to change passwords inside the container. For example, in Docker, you could do the following:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--cap-drop=ALL \
|
||||
--cap-add=CHOWN \
|
||||
--cap-add=SETUID \
|
||||
--cap-add=SETGID \
|
||||
--cap-add=AUDIT_WRITE \
|
||||
--env LICENSE=accept \
|
||||
--env MQ_QMGR_NAME=QM1 \
|
||||
--detach \
|
||||
mqadvanced-server-dev:9.1.3.0-amd64
|
||||
```
|
||||
|
||||
### SELinux
|
||||
|
||||
The SELinux label "spc_t" (super-privileged container) is needed to run the MQ container on a host with SELinux enabled. This is due to a current limitation in how MQ data is stored on volumes, which violates the usual policy applied when using the standard "container_t" label.
|
||||
@@ -6,7 +6,6 @@ You need to ensure you have the following tools installed:
|
||||
* [GNU make](https://www.gnu.org/software/make/)
|
||||
* [Go](https://golang.org/) - only needed for running the tests
|
||||
* [dep](https://github.com/golang/dep) (official Go dependency management tool) - needed to prepare for running the tests
|
||||
* [Helm](https://helm.sh) - only needed for running the Kubernetes tests
|
||||
|
||||
## Running the tests
|
||||
There are two main sets of tests:
|
||||
@@ -25,7 +24,7 @@ make test-advancedserver
|
||||
You can specify the image to use directly by using the `MQ_IMAGE_ADVANCEDSERVER` or `MQ_IMAGE_DEVSERVER` variables, for example:
|
||||
|
||||
```
|
||||
MQ_IMAGE_ADVANCEDSERVER=mqadvanced-server:9.1.0.0-x86_64-ubuntu-16.04 make test-advancedserver
|
||||
MQ_IMAGE_ADVANCEDSERVER=mqadvanced-server:9.1.3.0-amd64 make test-advancedserver
|
||||
```
|
||||
|
||||
You can pass parameters to `go test` with an environment variable. For example, to run the "TestGoldenPath" test, run the following command::
|
||||
@@ -34,10 +33,10 @@ You can pass parameters to `go test` with an environment variable. For example,
|
||||
TEST_OPTS_DOCKER="-run TestGoldenPath" make test-advancedserver
|
||||
```
|
||||
|
||||
You can also use the same environment variables you specified when [building](./building), for example, the following will try and test an image called `mqadvanced-server:9.0.5.0-x86_64-ubuntu-16.04`:
|
||||
You can also use the same environment variables you specified when [building](./building), for example, the following will try and test an image called `mqadvanced-server:9.1.3.0-amd64`:
|
||||
|
||||
```
|
||||
MQ_VERSION=9.0.5.0 make test-advancedserver
|
||||
MQ_VERSION=9.1.3.0 make test-advancedserver
|
||||
```
|
||||
|
||||
### Running the Docker tests with code coverage
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
In order to use the image, it is necessary to accept the terms of the IBM MQ license. This is achieved by specifying the environment variable `LICENSE` equal to `accept` when running the image. You can also view the license terms by setting this variable to `view`. Failure to set the variable will result in the termination of the container with a usage statement. You can view the license in a different language by also setting the `LANG` environment variable.
|
||||
|
||||
> **Note**: You can use `podman` instead of `docker` in any of the examples on this page.
|
||||
|
||||
## Running with the default configuration
|
||||
You can run a queue manager with the default configuration and a listener on port 1414 using the following command. For example, the following command creates and starts a queue manager called `QM1`, and maps port 1414 on the host to the MQ listener on port 1414 inside the container, as well as port 9443 on the host to the web console on port 9443 inside the container:
|
||||
|
||||
```
|
||||
```sh
|
||||
docker run \
|
||||
--env LICENSE=accept \
|
||||
--env MQ_QMGR_NAME=QM1 \
|
||||
@@ -16,15 +18,15 @@ docker run \
|
||||
```
|
||||
|
||||
## Running with the default configuration and a volume
|
||||
The above example will not persist any configuration data or messages across container runs. In order to do this, you need to use a [volume](https://docs.docker.com/engine/admin/volumes/volumes/). For example, you can create a volume with the following command:
|
||||
The above example will not persist any configuration data or messages across container runs. In order to do this, you need to use a [volume](https://docs.docker.com/storage/volumes/). For example, you can create a volume with the following command:
|
||||
|
||||
```
|
||||
```sh
|
||||
docker volume create qm1data
|
||||
```
|
||||
|
||||
You can then run a queue manager using this volume as follows:
|
||||
|
||||
```
|
||||
```sh
|
||||
docker run \
|
||||
--env LICENSE=accept \
|
||||
--env MQ_QMGR_NAME=QM1 \
|
||||
@@ -40,7 +42,7 @@ The Docker image always uses `/mnt/mqm` for MQ data, which is correctly linked f
|
||||
## Running with the default configuration and Prometheus metrics enabled
|
||||
You can run a queue manager with [Prometheus](https://prometheus.io) metrics enabled. The following command will generate Prometheus metrics for your queue manager on `/metrics` port `9157`:
|
||||
|
||||
```
|
||||
```sh
|
||||
docker run \
|
||||
--env LICENSE=accept \
|
||||
--env MQ_QMGR_NAME=QM1 \
|
||||
@@ -58,34 +60,36 @@ You can customize the configuration in several ways:
|
||||
|
||||
1. For getting started, you can use the [default developer configuration](developer-config.md), which is available out-of-the-box for the MQ Advanced for Developers image
|
||||
2. By creating your own image and adding your own MQSC file into the `/etc/mqm` directory on the image. This file will be run when your queue manager is created.
|
||||
3. By using [remote MQ administration](http://www-01.ibm.com/support/knowledgecenter/SSFKSJ_9.0.0/com.ibm.mq.adm.doc/q021090_.htm), via an MQ command server, the MQ HTTP APIs, or using a tool such as the MQ web console or MQ Explorer.
|
||||
3. By using [remote MQ administration](https://www.ibm.com/support/knowledgecenter/SSFKSJ_9.1.0/com.ibm.mq.adm.doc/q021090_.htm), via an MQ command server, the MQ HTTP APIs, or using a tool such as the MQ web console or MQ Explorer.
|
||||
|
||||
Note that a listener is always created on port 1414 inside the container. This port can be mapped to any port on the Docker host.
|
||||
|
||||
The following is an *example* `Dockerfile` for creating your own pre-configured image, which adds a custom `config.mqsc` and an administrative user `alice`. Note that it is not normally recommended to include passwords in this way:
|
||||
The following is an *example* `Dockerfile` for creating your own pre-configured image, which adds a custom MQ configuration file, and an administrative user `alice`. Note that it is not normally recommended to include passwords in this way:
|
||||
|
||||
```dockerfile
|
||||
FROM ibmcom/mq
|
||||
USER root
|
||||
RUN useradd alice -G mqm && \
|
||||
echo alice:passw0rd | chpasswd
|
||||
USER mqm
|
||||
COPY 20-config.mqsc /etc/mqm/
|
||||
```
|
||||
|
||||
Here is an example corresponding `20-config.mqsc` script from the [mqdev blog](https://www.ibm.com/developerworks/community/blogs/messaging/entry/getting_going_without_turning_off_mq_security?lang=en), which allows users with passwords to connect on the `PASSWORD.SVRCONN` channel:
|
||||
The `USER` instructions are necessary to ensure that the `useradd` and `chpasswd` commands are run as the root user.
|
||||
|
||||
Here is an example corresponding `20-config.mqsc` script, which creates two local queues:
|
||||
|
||||
```mqsc
|
||||
DEFINE QLOCAL(MY.QUEUE.1) REPLACE
|
||||
DEFINE QLOCAL(MY.QUEUE.2) REPLACE
|
||||
```
|
||||
DEFINE CHANNEL(PASSWORD.SVRCONN) CHLTYPE(SVRCONN) REPLACE
|
||||
SET CHLAUTH(PASSWORD.SVRCONN) TYPE(BLOCKUSER) USERLIST('nobody') DESCR('Allow privileged users on this channel')
|
||||
SET CHLAUTH('*') TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(NOACCESS) DESCR('BackStop rule')
|
||||
SET CHLAUTH(PASSWORD.SVRCONN) TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(CHANNEL) CHCKCLNT(REQUIRED)
|
||||
ALTER AUTHINFO(SYSTEM.DEFAULT.AUTHINFO.IDPWOS) AUTHTYPE(IDPWOS) ADOPTCTX(YES)
|
||||
REFRESH SECURITY TYPE(CONNAUTH)
|
||||
```
|
||||
|
||||
The file `20-config.mqsc` should be saved into the same directory as the `Dockerfile`.
|
||||
|
||||
## Running MQ commands
|
||||
It is recommended that you configure MQ in your own custom image. However, you may need to run MQ commands directly inside the process space of the container. To run a command against a running queue manager, you can use `docker exec`, for example:
|
||||
|
||||
```
|
||||
```sh
|
||||
docker exec \
|
||||
--tty \
|
||||
--interactive \
|
||||
@@ -94,3 +98,22 @@ docker exec \
|
||||
```
|
||||
|
||||
Using this technique, you can have full control over all aspects of the MQ installation. Note that if you use this technique to make changes to the filesystem, then those changes would be lost if you re-created your container unless you make those changes in volumes.
|
||||
|
||||
## Supplying TLS certificates
|
||||
|
||||
If you wish to supply TLS Certificates that the queue manager and MQ Console should use for TLS operations then you must supply a PKCS#1 or unencrypted PKCS#8 PEM files for both the certificates and private keys in the following directories:
|
||||
|
||||
* `/etc/mqm/pki/keys/<Label>` - for certificates with public and private keys
|
||||
* `/etc/mqm/pki/trust/<index>` - for certificates with only the public key
|
||||
|
||||
For example, if you have an identity certificate you wish to add with the label `mykey` and 2 certificates you wish to add as trusted then you would need to add the files into the following locations where files ending in `.key` contain private keys and `.crt` contain certificates:
|
||||
|
||||
- `/etc/mqm/pki/keys/mykey/tls.key`
|
||||
- `/etc/mqm/pki/keys/mykey/tls.crt`
|
||||
- `/etc/mqm/pki/keys/mykey/ca.crt`
|
||||
- `/etc/mqm/pki/trust/0/tls.crt`
|
||||
- `/etc/mqm/pki/trust/1/tls.crt`
|
||||
|
||||
This can be achieved by either mounting the directories or files into the container when you run it or by baking the files into the correct location in the image.
|
||||
|
||||
If you supply multiple identity certificates then the first label alphabetically will be chosen as the certificate to be used by the MQ Console and the default certificate for the queue manager. If you wish to use a different certificate on the queue manager then you can change the certificate to use at runtime by executing the MQSC command `ALTER QMGR CERTLABL('<newlabel>')`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
* © Copyright IBM Corporation 2017
|
||||
* © Copyright IBM Corporation 2019
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -12,4 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
REFRESH SECURITY TYPE(CONNAUTH)
|
||||
* Set the keystore location for the queue manager
|
||||
ALTER QMGR SSLKEYR('{{ .SSLKeyR }}')
|
||||
ALTER QMGR CERTLABL('{{ .CertificateLabel }}')
|
||||
REFRESH SECURITY(*) TYPE(SSL)
|
||||
8
glide.lock
generated
8
glide.lock
generated
@@ -1,5 +1,5 @@
|
||||
hash: b02555ebf3957ece0ae5ecf132fa4e415a4f66a7f4c27a82d484f4fb78f56e41
|
||||
updated: 2018-07-13T08:50:32.923040349+01:00
|
||||
hash: 6ebd5fb1c39729378c7256da6f312e9699bff1ddff9941d3c8c1ba785e22acfd
|
||||
updated: 2019-05-21T10:38:01.227081+01:00
|
||||
imports:
|
||||
- name: github.com/beorn7/perks
|
||||
version: 3a771d992973f24aa725d07868b467d1ddfceafb
|
||||
@@ -50,4 +50,8 @@ imports:
|
||||
version: 1b2967e3c290b7c545b3db0deeda16e9be4f98a2
|
||||
subpackages:
|
||||
- unix
|
||||
- name: software.sslmate.com/src/go-pkcs12
|
||||
version: 6e380ad96778cc63c6ea17649a9b74224bceafe9
|
||||
subpackages:
|
||||
- internal/rc2
|
||||
testImports: []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2017
|
||||
# © Copyright IBM Corporation 2017, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -26,3 +26,5 @@ import:
|
||||
version: 2.0.0
|
||||
- package: github.com/genuinetools/amicontained
|
||||
version: 0.4.0
|
||||
- package: software.sslmate.com/src/go-pkcs12
|
||||
commit: 6e380ad96778cc63c6ea17649a9b74224bceafe9
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2015, 2017
|
||||
# © Copyright IBM Corporation 2015, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,14 +15,16 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
# The URL to download the MQ installer from in tar.gz format
|
||||
ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev903_ubuntu_x86-64.tar.gz
|
||||
ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev911_ubuntu_x86-64.tar.gz
|
||||
|
||||
# The MQ packages to install
|
||||
ARG MQ_PACKAGES="ibmmq-sfbridge"
|
||||
|
||||
ARG MQM_UID=999
|
||||
|
||||
ADD install-mq.sh /usr/local/bin/
|
||||
RUN chmod u+x /usr/local/bin/install-mq.sh \
|
||||
&& install-mq.sh
|
||||
&& install-mq.sh $MQM_UID
|
||||
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
This is a work-in-progress for a Docker image based on Red Hat Enterprise Linux (RHEL).
|
||||
|
||||
The current MQ container build requires Docker V17.05 or greater (required features include multi-stage Docker build, and "ARG"s in the "FROM" statement). Red Hat Enterprise Linux V7.5 includes Docker up to version V1.13.
|
||||
|
||||
In order to build images with Red Hat Enterprise Linux, license registration is required. The license of the host server can be used, as long as you either use Red Hat's patched version of Docker (which is an old version), or if you use alternative container management tools such as [`buildah`](https://github.com/projectatomic/buildah/) and `podman` (from [`libpod`](https://github.com/projectatomic/libpod)).
|
||||
|
||||
This directory contains scripts for building with `buildah`. The build itself isn't containerized, so more software than usual is needed on the RHEL host, so an Ansible playbook is also provided to help set up the host.
|
||||
@@ -1 +0,0 @@
|
||||
hosts
|
||||
@@ -1,62 +0,0 @@
|
||||
# © Copyright IBM Corporation 2018
|
||||
#
|
||||
# Licensed 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.
|
||||
---
|
||||
# Sets up a server for building the Red Hat image.
|
||||
- hosts: rhbuild
|
||||
become: true
|
||||
any_errors_fatal: true
|
||||
tasks:
|
||||
- name: install buildah
|
||||
package:
|
||||
name: buildah
|
||||
state: present
|
||||
when: ansible_distribution == "RedHat"
|
||||
- name: install podman
|
||||
package:
|
||||
name: buildah
|
||||
state: present
|
||||
when: ansible_distribution == "RedHat"
|
||||
- name: install golang
|
||||
package:
|
||||
name: golang
|
||||
state: absent
|
||||
- name: install make
|
||||
package:
|
||||
name: make
|
||||
state: present
|
||||
- name: install git
|
||||
package:
|
||||
name: git
|
||||
state: present
|
||||
- name: install golang 1.10 from TAR
|
||||
unarchive:
|
||||
src: "https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz"
|
||||
dest: "/usr/local"
|
||||
remote_src: yes
|
||||
# TODO: Re-factor to use get_url first, so we can use the checksum
|
||||
#checksum: sha256:fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035
|
||||
creates: /usr/local/go/doc/go1.10.html
|
||||
- name: add golang to PATH
|
||||
copy:
|
||||
dest: "/etc/profile.d/golang.sh"
|
||||
content: |
|
||||
PATH=$PATH:/usr/local/go/bin
|
||||
- name: install dep from GitHub
|
||||
get_url:
|
||||
url: https://github.com/golang/dep/releases/download/v0.4.1/dep-linux-amd64
|
||||
dest: /usr/local/bin/dep
|
||||
mode: 0755
|
||||
checksum: sha256:31144e465e52ffbc0035248a10ddea61a09bf28b00784fd3fdd9882c8cbb2315
|
||||
when: ansible_architecture == "x86_64"
|
||||
# TODO: Install MQ SDK
|
||||
@@ -1,141 +0,0 @@
|
||||
#!/bin/bash
|
||||
# -*- mode: sh -*-
|
||||
# © Copyright IBM Corporation 2018
|
||||
#
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
# Build a RHEL image, using the buildah tool
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
MQ_ARCHIVE=downloads/mqadv_dev905_linux_x86-64.tar.gz
|
||||
MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm"
|
||||
|
||||
# Use a "scratch" container, so the resulting image has minimal files
|
||||
# Resulting image won't have yum, for example
|
||||
ctr=$(buildah from scratch)
|
||||
scratchmnt=$(buildah mount $ctr)
|
||||
|
||||
# Initialize yum for use with the scratch container
|
||||
rpm --root $scratchmnt --initdb
|
||||
yum install yum-utils
|
||||
yumdownloader --destdir=/tmp redhat-release-server
|
||||
rpm --root $scratchmnt -ihv /tmp/redhat-release-server*.rpm
|
||||
|
||||
# Install the packages required by MQ
|
||||
yum install -y --installroot=$scratchmnt \
|
||||
bash \
|
||||
bc \
|
||||
coreutils \
|
||||
file \
|
||||
findutils \
|
||||
gawk \
|
||||
glibc-common \
|
||||
grep \
|
||||
passwd \
|
||||
procps-ng \
|
||||
sed \
|
||||
tar \
|
||||
util-linux
|
||||
|
||||
# Clean up cached files
|
||||
yum clean all --installroot=$scratchmnt
|
||||
rm -rf $scratchmnt/var/cache/yum/*
|
||||
|
||||
groupadd --root $scratchmnt --system --gid 888 mqm
|
||||
useradd --root $scratchmnt --system --uid 888 --gid mqm mqm
|
||||
usermod --root $scratchmnt -G root mqm
|
||||
|
||||
DIR_EXTRACT=$scratchmnt/tmp/extract
|
||||
mkdir -p $scratchmnt/tmp/extract
|
||||
tar -zxvf ${MQ_ARCHIVE} -C ${DIR_EXTRACT}
|
||||
DIR_RPM=$(find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
|
||||
DIR_RPM=${DIR_RPM#$scratchmnt}
|
||||
#DIR_RPM=$(buildah run $ctr -- find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
|
||||
# Find location of mqlicense.sh
|
||||
#MQLICENSE=$(buildah run $ctr -- find ${DIR_EXTRACT} -name "mqlicense.sh")
|
||||
MQLICENSE=$(find ${DIR_EXTRACT} -name "mqlicense.sh")
|
||||
MQLICENSE=${MQLICENSE#$scratchmnt}
|
||||
|
||||
# Accept the MQ license
|
||||
buildah run $ctr -- ${MQLICENSE} -text_only -accept
|
||||
|
||||
buildah run $ctr -- bash -c "cd $DIR_RPM && rpm -ivh $MQ_PACKAGES"
|
||||
rm -rf ${DIR_EXTRACT}
|
||||
|
||||
# Remove 32-bit libraries from 64-bit container
|
||||
find $scratchmnt/opt/mqm $scratchmnt/var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
|
||||
|
||||
# Remove tar.gz files unpacked by RPM postinst scripts
|
||||
find $scratchmnt/opt/mqm -name '*.tar.gz' -delete
|
||||
|
||||
# Recommended: Set the default MQ installation (makes the MQ commands available on the PATH)
|
||||
buildah run $ctr -- /opt/mqm/bin/setmqinst -p /opt/mqm -i
|
||||
|
||||
# Remove the directory structure under /var/mqm which was created by the installer
|
||||
rm -rf $scratchmnt/var/mqm
|
||||
|
||||
# Create the mount point for volumes
|
||||
mkdir -p $scratchmnt/mnt/mqm
|
||||
|
||||
# Create the directory for MQ configuration files
|
||||
mkdir -p $scratchmnt/etc/mqm
|
||||
|
||||
# Create a symlink for /var/mqm -> /mnt/mqm/data
|
||||
buildah run $ctr ln -s /mnt/mqm/data /var/mqm
|
||||
|
||||
# Optional: Set these values for the Bluemix Vulnerability Report
|
||||
sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' $scratchmnt/etc/login.defs
|
||||
sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' $scratchmnt/etc/login.defs
|
||||
sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' $scratchmnt/etc/pam.d/password-auth
|
||||
|
||||
# Build and test the Go code
|
||||
go build ./cmd/runmqserver/
|
||||
go build ./cmd/chkmqready/
|
||||
go build ./cmd/chkmqhealthy/
|
||||
go test -v ./cmd/runmqserver/
|
||||
go test -v ./cmd/chkmqready/
|
||||
go test -v ./cmd/chkmqhealthy/
|
||||
go test -v ./internal/...
|
||||
go vet ./cmd/... ./internal/...
|
||||
# Install the Go binaries into the image
|
||||
cp runmqserver $scratchmnt/usr/local/bin/
|
||||
cp chkmq* $scratchmnt/usr/local/bin/
|
||||
cp NOTICES.txt $scratchmnt/opt/mqm/licenses/notices-container.txt
|
||||
chmod ug+x $scratchmnt/usr/local/bin/runmqserver
|
||||
chown mqm:mqm $scratchmnt/usr/local/bin/*mq*
|
||||
chmod ug+xs $scratchmnt/usr/local/bin/chkmq*
|
||||
|
||||
buildah config \
|
||||
--port 1414/tcp \
|
||||
--port 9157/tcp \
|
||||
--os linux \
|
||||
--label architecture=x86_64 \
|
||||
--label io.openshift.tags="mq messaging" \
|
||||
--label io.k8s.display-name="IBM MQ Advanced Server" \
|
||||
--label io.k8s.description="IBM MQ is messaging middleware that simplifies and accelerates the integration of diverse applications and business data across multiple platforms. It uses message queues to facilitate the exchanges of information and offers a single messaging solution for cloud, mobile, Internet of Things (IoT) and on-premises environments." \
|
||||
--label name="mqadvanced-server" \
|
||||
--label vendor="IBM" \
|
||||
--label version="9.0.5.0" \
|
||||
--env AMQ_ADDITIONAL_JSON_LOG=1 \
|
||||
--env LANG=en_US.UTF-8 \
|
||||
--env LOG_FORMAT=basic \
|
||||
--entrypoint runmqserver \
|
||||
--user 888 \
|
||||
$ctr
|
||||
buildah unmount $ctr
|
||||
buildah commit $ctr mymq
|
||||
|
||||
# TODO: Leaves the working container lying around. Good for dev.
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2015, 2017
|
||||
# © Copyright IBM Corporation 2015, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -12,27 +12,33 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM ubuntu:16.04
|
||||
FROM registry.access.redhat.com/ubi7/ubi-minimal AS mq-explorer
|
||||
|
||||
# The URL to download the MQ installer from in tar.gz format
|
||||
ARG MQ_URL=https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev903_ubuntu_x86-64.tar.gz
|
||||
ARG MQ_URL="https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev912_linux_x86-64.tar.gz"
|
||||
|
||||
# The MQ packages to install
|
||||
ARG MQ_PACKAGES="ibmmq-explorer"
|
||||
ENV MQ_PACKAGES="MQSeriesRuntime*.rpm MQSeriesJRE*.rpm MQSeriesExplorer*.rpm"
|
||||
|
||||
RUN export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y \
|
||||
libgtk2.0-0 \
|
||||
libxtst6
|
||||
ARG MQM_UID=888
|
||||
|
||||
RUN microdnf install -y --nodocs gtk2 libXtst \
|
||||
&& microdnf clean all
|
||||
|
||||
ADD install-mq.sh /usr/local/bin/
|
||||
|
||||
# Install MQ Explorer. To avoid a "text file busy" error here, we sleep before installing.
|
||||
# Need to re-instate the `/var/mqm` directory after installation, to avoid MQ
|
||||
# errors with some commands (e.g. `dspmqver`)
|
||||
RUN chmod u+x /usr/local/bin/install-mq.sh \
|
||||
&& install-mq.sh
|
||||
&& sleep 1 \
|
||||
&& install-mq.sh $MQM_UID \
|
||||
&& rm -rf /var/mqm \
|
||||
&& /opt/mqm/bin/crtmqdir -f -s
|
||||
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
# Run as mqm (999)
|
||||
USER 999
|
||||
# Run as mqm
|
||||
USER $MQM_UID
|
||||
|
||||
ENTRYPOINT ["MQExplorer"]
|
||||
@@ -1,5 +0,0 @@
|
||||
# IBM MQ Software Developer Kit (SDK) with Go
|
||||
|
||||
This image contains the MQ SDK, Git, the Go compiler, and the `build-essential` package (which includes GNU C and C++ compilers plus other essential tools like `make`).
|
||||
|
||||
This image doesn't contain any Go code for MQ. You can add a CGO wrapper for the MQ C client, for example [mq-golang](https://github.com/ibm-messaging/mq-golang), via your vendor directory, or directly using `go get`.
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2018
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -12,17 +12,19 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG BASE_IMAGE=ubuntu:16.04
|
||||
|
||||
FROM $BASE_IMAGE
|
||||
FROM registry.access.redhat.com/rhscl/devtoolset-7-toolchain-rhel7 AS mq-sdk
|
||||
#FROM docker.io/centos/devtoolset-7-toolchain-centos7 AS mq-sdk
|
||||
|
||||
# The URL to download the MQ installer from in tar.gz format
|
||||
# This assumes an archive containing the MQ Debian (.deb) install packages
|
||||
ARG MQ_URL
|
||||
|
||||
# The packages to install in install-mq.sh
|
||||
ARG MQ_PACKAGES
|
||||
ENV MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesSDK-*.rpm MQSeriesSamples*.rpm"
|
||||
|
||||
ENV MQM_UID=888
|
||||
|
||||
USER 0
|
||||
COPY install-mq.sh /usr/local/bin/
|
||||
|
||||
# Install MQ. To avoid a "text file busy" error here, we sleep before installing.
|
||||
@@ -30,6 +32,7 @@ COPY install-mq.sh /usr/local/bin/
|
||||
# errors with some commands (e.g. `dspmqver`)
|
||||
RUN chmod u+x /usr/local/bin/install-mq.sh \
|
||||
&& sleep 1 \
|
||||
&& install-mq.sh \
|
||||
&& install-mq.sh $MQM_UID \
|
||||
&& rm -rf /var/mqm \
|
||||
&& /opt/mqm/bin/crtmqdir -f -s
|
||||
USER 1001
|
||||
@@ -1,4 +1,4 @@
|
||||
* © Copyright IBM Corporation 2017, 2018
|
||||
* © Copyright IBM Corporation 2017, 2019
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -13,7 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
STOP LISTENER('SYSTEM.LISTENER.TCP.1')
|
||||
STOP LISTENER('SYSTEM.LISTENER.TCP.1') IGNSTATE(YES)
|
||||
ALTER LISTENER('SYSTEM.LISTENER.TCP.1') TRPTYPE(TCP) CONTROL(MANUAL)
|
||||
|
||||
* Developer queues
|
||||
DEFINE QLOCAL('DEV.QUEUE.1') REPLACE
|
||||
@@ -50,4 +51,4 @@ SET AUTHREC PROFILE('DEV.**') GROUP('mqclient') OBJTYPE(TOPIC) AUTHADD(PUB,SUB)
|
||||
|
||||
* Developer listener
|
||||
DEFINE LISTENER('DEV.LISTENER.TCP') TRPTYPE(TCP) PORT(1414) CONTROL(QMGR) REPLACE
|
||||
START LISTENER('DEV.LISTENER.TCP')
|
||||
START LISTENER('DEV.LISTENER.TCP') IGNSTATE(YES)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
* © Copyright IBM Corporation 2018
|
||||
* © Copyright IBM Corporation 2018, 2019
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -13,10 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Set the keystore location for the queue manager
|
||||
ALTER QMGR SSLKEYR('{{ .SSLKeyR }}')
|
||||
ALTER QMGR CERTLABL('{{ .CertificateLabel }}')
|
||||
|
||||
* Set the cipherspec for dev channels
|
||||
ALTER CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH({{ .SSLCipherSpec }}) SSLCAUTH(OPTIONAL)
|
||||
ALTER CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH({{ .SSLCipherSpec }}) SSLCAUTH(OPTIONAL)
|
||||
ALTER CHANNEL('DEV.APP.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH(ANY_TLS12) SSLCAUTH(OPTIONAL)
|
||||
ALTER CHANNEL('DEV.ADMIN.SVRCONN') CHLTYPE(SVRCONN) SSLCIPH(ANY_TLS12) SSLCAUTH(OPTIONAL)
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# © Copyright IBM Corporation 2015, 2018
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
ARG BASE_IMAGE=mqadvanced-server-dev-base:9.0.5.0-x86_64-ubuntu-16.04
|
||||
ARG BUILDER_IMAGE=mq-golang-sdk:9.0.5.0-x86_64-ubuntu-16.04
|
||||
|
||||
###############################################################################
|
||||
# Build stage to build Go code
|
||||
###############################################################################
|
||||
FROM $BUILDER_IMAGE as builder
|
||||
ARG IMAGE_REVISION="Not specified"
|
||||
ARG IMAGE_CREATED="Not specified"
|
||||
ARG IMAGE_SOURCE="Not specified"
|
||||
WORKDIR /go/src/github.com/ibm-messaging/mq-container/
|
||||
COPY cmd/ ./cmd
|
||||
COPY internal/ ./internal
|
||||
COPY vendor/ ./vendor
|
||||
# Re-build runmqserver, with code tagged with 'mqdev' enabled
|
||||
RUN go build -ldflags "-X \"main.ImageCreated=$IMAGE_CREATED\" -X \"main.ImageRevision=$IMAGE_REVISION\" -X \"main.ImageSource=$IMAGE_SOURCE\"" --tags 'mqdev' ./cmd/runmqserver
|
||||
RUN go build ./cmd/runmqdevserver/
|
||||
# Run all unit tests
|
||||
RUN go test -v ./cmd/runmqdevserver/...
|
||||
|
||||
###############################################################################
|
||||
# Main build stage
|
||||
###############################################################################
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
# Enable MQ developer default configuration
|
||||
ENV MQ_DEV=true
|
||||
|
||||
# Default administrator password
|
||||
ENV MQ_ADMIN_PASSWORD=passw0rd
|
||||
|
||||
## Add admin and app users, and set a default password for admin
|
||||
RUN useradd admin -G mqm \
|
||||
&& groupadd mqclient \
|
||||
&& useradd app -G mqclient \
|
||||
&& echo admin:$MQ_ADMIN_PASSWORD | chpasswd
|
||||
|
||||
# Create a directory for runtime data from runmqserver
|
||||
RUN mkdir -p /run/runmqdevserver \
|
||||
&& chown mqm:mqm /run/runmqdevserver
|
||||
|
||||
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqserver /usr/local/bin/
|
||||
COPY --from=builder /go/src/github.com/ibm-messaging/mq-container/runmqdevserver /usr/local/bin/
|
||||
# Copy template files
|
||||
COPY incubating/mqadvanced-server-dev/*.tpl /etc/mqm/
|
||||
# Copy web XML files for default developer configuration
|
||||
COPY incubating/mqadvanced-server-dev/web /etc/mqm/web
|
||||
RUN chmod +x /usr/local/bin/runmq*
|
||||
|
||||
EXPOSE 9443
|
||||
|
||||
ENTRYPOINT ["runmqdevserver"]
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Change admin password
|
||||
if [ -n "${MQ_ADMIN_PASSWORD}" ]; then
|
||||
echo admin:${MQ_ADMIN_PASSWORD} | chpasswd
|
||||
fi
|
||||
# Change app password
|
||||
if [ -n "${MQ_APP_PASSWORD}" ]; then
|
||||
echo app:${MQ_APP_PASSWORD} | chpasswd
|
||||
fi
|
||||
|
||||
# Delete the MQSC with developer defaults, if requested
|
||||
if [ "${MQ_DEV}" != "true" ]; then
|
||||
rm -f /etc/mqm/dev.mqsc
|
||||
fi
|
||||
|
||||
exec runmqserver
|
||||
38
incubating/mqadvanced-server-dev/install-extra-packages.sh
Normal file
38
incubating/mqadvanced-server-dev/install-extra-packages.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# -*- mode: sh -*-
|
||||
# © Copyright IBM Corporation 2019
|
||||
#
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
test -f /usr/bin/yum && YUM=true || YUM=false
|
||||
test -f /usr/bin/microdnf && MICRODNF=true || MICRODNF=false
|
||||
test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
|
||||
|
||||
if ($UBUNTU); then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends sudo
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
fi
|
||||
|
||||
if ($YUM); then
|
||||
yum -y install sudo
|
||||
yum -y clean all
|
||||
rm -rf /var/cache/yum/*
|
||||
fi
|
||||
|
||||
if ($MICRODNF); then
|
||||
microdnf install --nodocs sudo
|
||||
microdnf clean all
|
||||
fi
|
||||
@@ -16,14 +16,25 @@
|
||||
<security-role name="MQWebAdmin">
|
||||
<group name="MQWebUI" realm="defaultRealm"/>
|
||||
</security-role>
|
||||
<security-role name="MQWebUser">
|
||||
<group name="MQWebMessaging" realm="defaultRealm"/>
|
||||
</security-role>
|
||||
</application-bnd>
|
||||
</enterpriseApplication>
|
||||
<basicRegistry id="basic" realm="defaultRealm">
|
||||
<user name="admin" password="${env.MQ_ADMIN_PASSWORD}"/>
|
||||
<!-- The app user will always get a default password of "passw0rd",
|
||||
even if you don't set the environment variable.
|
||||
See `webserver.go` -->
|
||||
<user name="app" password="${env.MQ_APP_PASSWORD}"/>
|
||||
<group name="MQWebUI">
|
||||
<member name="admin"/>
|
||||
</group>
|
||||
<group name="MQWebMessaging">
|
||||
<member name="app"/>
|
||||
</group>
|
||||
</basicRegistry>
|
||||
<variable name="httpHost" value="*"/>
|
||||
<variable name="managementMode" value="externallyprovisioned"/>
|
||||
<include location="tls.xml"/>
|
||||
</server>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<server>
|
||||
<keyStore id="MQWebKeyStore" location="/run/runmqdevserver/tls/key.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
|
||||
<keyStore id="MQWebTrustStore" location="/run/runmqdevserver/tls/trust.jks" type="JKS" password="${env.MQ_TLS_PASSPHRASE}"/>
|
||||
<ssl id="thisSSLConfig" clientAuthenticationSupported="true" keyStoreRef="MQWebKeyStore" trustStoreRef="MQWebTrustStore" sslProtocol="TLSv1.2" serverKeyAlias="devcert"/>
|
||||
<sslDefault sslRef="thisSSLConfig"/>
|
||||
</server>
|
||||
26
install-build-deps-ubuntu.sh
Executable file
26
install-build-deps-ubuntu.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# -*- mode: sh -*-
|
||||
# © Copyright IBM Corporation 2015, 2019
|
||||
#
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
# Install Docker and dep, required by build (assumes Ubuntu host, as used by Travis build)
|
||||
|
||||
set -ex
|
||||
|
||||
curl https://glide.sh/get | sh
|
||||
sudo curl -Lo /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
|
||||
sudo chmod +x /usr/local/bin/dep
|
||||
|
||||
go get -u golang.org/x/lint/golint
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# -*- mode: sh -*-
|
||||
# © Copyright IBM Corporation 2018
|
||||
# © Copyright IBM Corporation 2015, 2019
|
||||
#
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -18,7 +18,9 @@
|
||||
# Fail on any non-zero return code
|
||||
set -ex
|
||||
|
||||
test -f /usr/bin/yum && RHEL=true || RHEL=false
|
||||
test -f /usr/bin/yum && YUM=true || YUM=false
|
||||
test -f /usr/bin/microdnf && MICRODNF=true || MICRODNF=false
|
||||
test -f /usr/bin/rpm && RPM=true || RPM=false
|
||||
test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
|
||||
|
||||
if ($UBUNTU); then
|
||||
@@ -37,37 +39,43 @@ if ($UBUNTU); then
|
||||
# This ensures no unsupported code gets installed, and makes the build faster
|
||||
echo "deb ${APT_URL} ${UBUNTU_CODENAME} main restricted" > /etc/apt/sources.list
|
||||
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-updates main restricted" >> /etc/apt/sources.list
|
||||
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-backports main restricted universe" >> /etc/apt/sources.list;
|
||||
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-security main restricted" >> /etc/apt/sources.list
|
||||
|
||||
# Install additional packages required by MQ, this install process and the runtime scripts
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
golang-${GO_VERSION} \
|
||||
git \
|
||||
ca-certificates
|
||||
fi
|
||||
|
||||
if ($RHEL); then
|
||||
# Install additional packages required by MQ, this install process and the runtime scripts
|
||||
yum -y install \
|
||||
git \
|
||||
bash \
|
||||
bc \
|
||||
ca-certificates \
|
||||
coreutils \
|
||||
curl \
|
||||
debianutils \
|
||||
file \
|
||||
findutils \
|
||||
gawk \
|
||||
grep \
|
||||
libc-bin \
|
||||
mount \
|
||||
passwd \
|
||||
procps \
|
||||
sed \
|
||||
tar \
|
||||
gcc
|
||||
|
||||
cd /tmp
|
||||
curl -LO https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz
|
||||
tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
|
||||
util-linux
|
||||
fi
|
||||
|
||||
# Remove any orphaned packages
|
||||
$UBUNTU && apt-get autoremove -y
|
||||
if ($RPM); then
|
||||
EXTRA_RPMS="bash bc ca-certificates coreutils file findutils gawk glibc-common grep passwd procps-ng sed shadow-utils tar util-linux which"
|
||||
# Install additional packages required by MQ, this install process and the runtime scripts
|
||||
$YUM && yum -y install --setopt install_weak_deps=false ${EXTRA_RPMS}
|
||||
$MICRODNF && microdnf install --nodocs ${EXTRA_RPMS}
|
||||
fi
|
||||
|
||||
# Apply any bug fixes not included in base Ubuntu or MQ image.
|
||||
# Don't upgrade everything based on Docker best practices https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run
|
||||
$UBUNTU && apt-get install -y libapparmor1 libsystemd0 systemd systemd-sysv libudev1 perl-base --only-upgrade
|
||||
# End of bug fixes
|
||||
|
||||
# Clean up cached files
|
||||
$UBUNTU && rm -rf /var/lib/apt/lists/*
|
||||
$RHEL && yum -y clean all
|
||||
$RHEL && rm -rf /var/cache/yum/*
|
||||
|
||||
# Make the GOLANG directories
|
||||
mkdir -p $GOPATH/src $GOPATH/bin
|
||||
chmod -R 777 $GOPATH
|
||||
$YUM && yum -y clean all
|
||||
$YUM && rm -rf /var/cache/yum/*
|
||||
$MICRODNF && microdnf clean all
|
||||
136
install-mq.sh
136
install-mq.sh
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# -*- mode: sh -*-
|
||||
# © Copyright IBM Corporation 2015, 2018
|
||||
# © Copyright IBM Corporation 2015, 2019
|
||||
#
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -18,99 +18,27 @@
|
||||
# Fail on any non-zero return code
|
||||
set -ex
|
||||
|
||||
test -f /usr/bin/yum && RHEL=true || RHEL=false
|
||||
mqm_uid=${1:-888}
|
||||
|
||||
test -f /usr/bin/yum && YUM=true || YUM=false
|
||||
test -f /usr/bin/microdnf && MICRODNF=true || MICRODNF=false
|
||||
test -f /usr/bin/rpm && RPM=true || RPM=false
|
||||
test -f /usr/bin/apt-get && UBUNTU=true || UBUNTU=false
|
||||
|
||||
# If MQ_PACKAGES isn't specifically set, then choose a valid set of defaults
|
||||
if [ -z "$MQ_PACKAGES" ]; then
|
||||
$UBUNTU && MQ_PACKAGES="ibmmq-server ibmmq-java ibmmq-jre ibmmq-gskit ibmmq-msg-.* ibmmq-samples ibmmq-ams"
|
||||
$RHEL && MQ_PACKAGES="MQSeriesRuntime-*.rpm MQSeriesServer-*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesMsg*.rpm MQSeriesSamples*.rpm MQSeriesAMS-*.rpm"
|
||||
fi
|
||||
|
||||
if ($UBUNTU); then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
# Use a reduced set of apt repositories.
|
||||
# This ensures no unsupported code gets installed, and makes the build faster
|
||||
source /etc/os-release
|
||||
# Figure out the correct apt URL based on the CPU architecture
|
||||
CPU_ARCH=$(uname -p)
|
||||
if [ ${CPU_ARCH} == "x86_64" ]; then
|
||||
APT_URL="http://archive.ubuntu.com/ubuntu/"
|
||||
else
|
||||
APT_URL="http://ports.ubuntu.com/ubuntu-ports/"
|
||||
fi
|
||||
# Use a reduced set of apt repositories.
|
||||
# This ensures no unsupported code gets installed, and makes the build faster
|
||||
echo "deb ${APT_URL} ${UBUNTU_CODENAME} main restricted" > /etc/apt/sources.list
|
||||
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-updates main restricted" >> /etc/apt/sources.list
|
||||
echo "deb ${APT_URL} ${UBUNTU_CODENAME}-security main restricted" >> /etc/apt/sources.list
|
||||
# Install additional packages required by MQ, this install process and the runtime scripts
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
bc \
|
||||
ca-certificates \
|
||||
coreutils \
|
||||
curl \
|
||||
debianutils \
|
||||
file \
|
||||
findutils \
|
||||
gawk \
|
||||
grep \
|
||||
libc-bin \
|
||||
mount \
|
||||
passwd \
|
||||
procps \
|
||||
sed \
|
||||
tar \
|
||||
util-linux
|
||||
fi
|
||||
|
||||
# Install additional packages required by MQ, this install process and the runtime scripts
|
||||
$RHEL && yum -y install \
|
||||
bash \
|
||||
bc \
|
||||
ca-certificates \
|
||||
coreutils \
|
||||
curl \
|
||||
file \
|
||||
findutils \
|
||||
gawk \
|
||||
glibc-common \
|
||||
grep \
|
||||
passwd \
|
||||
procps-ng \
|
||||
sed \
|
||||
tar \
|
||||
util-linux
|
||||
|
||||
# Download and extract the MQ installation files
|
||||
DIR_EXTRACT=/tmp/mq
|
||||
mkdir -p ${DIR_EXTRACT}
|
||||
cd ${DIR_EXTRACT}
|
||||
curl -LO $MQ_URL
|
||||
tar -zxvf ./*.tar.gz
|
||||
|
||||
# Remove packages only needed by this script
|
||||
$UBUNTU && apt-get purge -y \
|
||||
ca-certificates \
|
||||
curl
|
||||
|
||||
# Note: ca-certificates and curl are installed by default in RHEL
|
||||
|
||||
# Remove any orphaned packages
|
||||
$UBUNTU && apt-get autoremove -y
|
||||
tar -zxf ./*.tar.gz
|
||||
|
||||
# Recommended: Create the mqm user ID with a fixed UID and group, so that the file permissions work between different images
|
||||
$UBUNTU && groupadd --system --gid 999 mqm
|
||||
$UBUNTU && useradd --system --uid 999 --gid mqm mqm
|
||||
$RHEL && groupadd --system --gid 888 mqm
|
||||
$RHEL && useradd --system --uid 888 --gid mqm mqm
|
||||
usermod -G mqm root
|
||||
groupadd --system --gid ${mqm_uid} mqm
|
||||
useradd --system --uid ${mqm_uid} --gid mqm --groups 0 mqm
|
||||
|
||||
# Find directory containing .deb files
|
||||
$UBUNTU && DIR_DEB=$(find ${DIR_EXTRACT} -name "*.deb" -printf "%h\n" | sort -u | head -1)
|
||||
$RHEL && DIR_RPM=$(find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
|
||||
$RPM && DIR_RPM=$(find ${DIR_EXTRACT} -name "*.rpm" -printf "%h\n" | sort -u | head -1)
|
||||
# Find location of mqlicense.sh
|
||||
MQLICENSE=$(find ${DIR_EXTRACT} -name "mqlicense.sh")
|
||||
|
||||
@@ -122,10 +50,11 @@ $UBUNTU && echo "deb [trusted=yes] file:${DIR_DEB} ./" > /etc/apt/sources.list.d
|
||||
$UBUNTU && apt-get update
|
||||
$UBUNTU && apt-get install -y $MQ_PACKAGES
|
||||
|
||||
$RHEL && cd $DIR_RPM && rpm -ivh $MQ_PACKAGES
|
||||
$RPM && cd $DIR_RPM && rpm -ivh $MQ_PACKAGES
|
||||
|
||||
# Remove 32-bit libraries from 64-bit container
|
||||
find /opt/mqm /var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
|
||||
# The "file" utility isn't installed by default in UBI, so only try this if it's installed
|
||||
which file && find /opt/mqm /var/mqm -type f -exec file {} \; | awk -F: '/ELF 32-bit/{print $1}' | xargs --no-run-if-empty rm -f
|
||||
|
||||
# Remove tar.gz files unpacked by RPM postinst scripts
|
||||
find /opt/mqm -name '*.tar.gz' -delete
|
||||
@@ -137,35 +66,44 @@ find /opt/mqm -name '*.tar.gz' -delete
|
||||
$UBUNTU && rm -f /etc/apt/sources.list.d/IBM_MQ.list
|
||||
rm -rf ${DIR_EXTRACT}
|
||||
|
||||
# Apply any bug fixes not included in base Ubuntu or MQ image.
|
||||
# Don't upgrade everything based on Docker best practices https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run
|
||||
$UBUNTU && apt-get install -y gcc-5-base gnupg gpgv libgcrypt20 libstdc++6 perl-base --only-upgrade
|
||||
# End of bug fixes
|
||||
|
||||
# Clean up cached files
|
||||
$UBUNTU && rm -rf /var/lib/apt/lists/*
|
||||
$RHEL && yum -y clean all
|
||||
$RHEL && rm -rf /var/cache/yum/*
|
||||
|
||||
# Optional: Update the command prompt with the MQ version
|
||||
$UBUNTU && echo "mq:$(dspmqver -b -f 2)" > /etc/debian_chroot
|
||||
|
||||
# Remove the directory structure under /var/mqm which was created by the installer
|
||||
rm -rf /var/mqm
|
||||
|
||||
# Create the mount point for volumes
|
||||
mkdir -p /mnt/mqm
|
||||
# Create the mount point for volumes, ensuring MQ has permissions to all directories
|
||||
install --directory --mode 0775 --owner mqm --group root /mnt
|
||||
install --directory --mode 0775 --owner mqm --group root /mnt/mqm
|
||||
install --directory --mode 0775 --owner mqm --group root /mnt/mqm/data
|
||||
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-log
|
||||
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-log/log
|
||||
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-data
|
||||
install --directory --mode 0775 --owner mqm --group root /mnt/mqm-data/qmgrs
|
||||
|
||||
# Create the directory for MQ configuration files
|
||||
mkdir -p /etc/mqm
|
||||
install --directory --mode 0775 --owner mqm --group root /etc/mqm
|
||||
|
||||
# Create the directory for MQ runtime files
|
||||
install --directory --mode 0775 --owner mqm --group root /run/mqm
|
||||
|
||||
# Create a symlink for /var/mqm -> /mnt/mqm/data
|
||||
ln -s /mnt/mqm/data /var/mqm
|
||||
|
||||
# Optional: Set these values for the Bluemix Vulnerability Report
|
||||
# Optional: Ensure any passwords expire in a timely manner
|
||||
sed -i 's/PASS_MAX_DAYS\t99999/PASS_MAX_DAYS\t90/' /etc/login.defs
|
||||
sed -i 's/PASS_MIN_DAYS\t0/PASS_MIN_DAYS\t1/' /etc/login.defs
|
||||
sed -i 's/PASS_MIN_LEN\t5/PASS_MIN_LEN\t8/' /etc/login.defs
|
||||
sed -i 's/# minlen = 9/minlen = 8/' /etc/security/pwquality.conf
|
||||
|
||||
$UBUNTU && PAM_FILE=/etc/pam.d/common-password
|
||||
$RHEL && PAM_FILE=/etc/pam.d/password-auth
|
||||
$RPM && PAM_FILE=/etc/pam.d/password-auth
|
||||
sed -i 's/password\t\[success=1 default=ignore\]\tpam_unix\.so obscure sha512/password\t[success=1 default=ignore]\tpam_unix.so obscure sha512 minlen=8/' $PAM_FILE
|
||||
|
||||
# List all the installed packages, for the build log
|
||||
$RPM && rpm -q --all || true
|
||||
$UBUNTU && dpkg --list || true
|
||||
|
||||
# Copy MQ Licenses into the correct location
|
||||
mkdir -p /licenses
|
||||
cp /opt/mqm/licenses/*.txt /licenses/
|
||||
|
||||
@@ -53,11 +53,13 @@ func RunCmd(cmd *exec.Cmd) (string, int, error) {
|
||||
// Do not use this function to run shell built-ins (like "cd"), because
|
||||
// the error handling works differently
|
||||
func Run(name string, arg ...string) (string, int, error) {
|
||||
// #nosec G204
|
||||
return RunCmd(exec.Command(name, arg...))
|
||||
}
|
||||
|
||||
// RunAsMQM runs the specified command as the mqm user
|
||||
func RunAsMQM(name string, arg ...string) (string, int, error) {
|
||||
// #nosec G204
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
uid, gid, err := LookupMQM()
|
||||
|
||||
128
internal/containerruntime/runtime.go
Normal file
128
internal/containerruntime/runtime.go
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package containerruntime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/genuinetools/amicontained/container"
|
||||
)
|
||||
|
||||
func GetContainerRuntime() (string, error) {
|
||||
return container.DetectRuntime()
|
||||
}
|
||||
|
||||
func GetBaseImage() (string, error) {
|
||||
buf, err := ioutil.ReadFile("/etc/os-release")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read /etc/os-release: %v", err)
|
||||
}
|
||||
lines := strings.Split(string(buf), "\n")
|
||||
for _, l := range lines {
|
||||
if strings.HasPrefix(l, "PRETTY_NAME=") {
|
||||
words := strings.Split(l, "\"")
|
||||
if len(words) >= 2 {
|
||||
return words[1], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "unknown", nil
|
||||
}
|
||||
|
||||
// GetCapabilities gets the Linux capabilities (e.g. setuid, setgid). See https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
|
||||
func GetCapabilities() (map[string][]string, error) {
|
||||
return container.Capabilities()
|
||||
}
|
||||
|
||||
// GetSeccomp gets the seccomp enforcing mode, which affects which kernel calls can be made
|
||||
func GetSeccomp() (string, error) {
|
||||
s, err := container.SeccompEnforcingMode()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to get container SeccompEnforcingMode: %v", err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetSecurityAttributes gets the security attributes of the current process.
|
||||
// The security attributes indicate whether AppArmor or SELinux are being used,
|
||||
// and what the level of confinement is.
|
||||
func GetSecurityAttributes() string {
|
||||
a, err := readProc("/proc/self/attr/current")
|
||||
// On some systems, if AppArmor or SELinux are not installed, you get an
|
||||
// error when you try and read `/proc/self/attr/current`, even though the
|
||||
// file exists.
|
||||
if err != nil || a == "" {
|
||||
a = "none"
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func readProc(filename string) (value string, err error) {
|
||||
// #nosec G304
|
||||
buf, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(buf)), nil
|
||||
}
|
||||
|
||||
func GetMounts() (map[string]string, error) {
|
||||
all, err := readProc("/proc/mounts")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't read /proc/mounts")
|
||||
}
|
||||
result := make(map[string]string)
|
||||
lines := strings.Split(all, "\n")
|
||||
for i := range lines {
|
||||
parts := strings.Split(lines[i], " ")
|
||||
//dev := parts[0]
|
||||
mountPoint := parts[1]
|
||||
fsType := parts[2]
|
||||
if strings.Contains(mountPoint, "/mnt/mqm") {
|
||||
result[mountPoint] = fsType
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetKernelVersion() (string, error) {
|
||||
return readProc("/proc/sys/kernel/osrelease")
|
||||
}
|
||||
|
||||
func GetMaxFileHandles() (string, error) {
|
||||
return readProc("/proc/sys/fs/file-max")
|
||||
}
|
||||
|
||||
// SupportedFilesystem returns true if the supplied filesystem type is supported for MQ data
|
||||
func SupportedFilesystem(fsType string) bool {
|
||||
switch fsType {
|
||||
case "aufs", "overlayfs", "tmpfs":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ValidMultiInstanceFilesystem returns true if the supplied filesystem type is valid for a multi-instance queue manager
|
||||
func ValidMultiInstanceFilesystem(fsType string) bool {
|
||||
if !SupportedFilesystem(fsType) {
|
||||
return false
|
||||
}
|
||||
// TODO : check for non-shared filesystems & shared filesystems which are known not to work
|
||||
return true
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -15,11 +15,9 @@ 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.
|
||||
*/
|
||||
package main
|
||||
package containerruntime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@@ -101,24 +99,17 @@ var fsTypes = map[int64]string{
|
||||
0x58295829: "zsmalloc",
|
||||
}
|
||||
|
||||
func checkFS(path string) error {
|
||||
// GetFilesystem returns the filesystem type for the specified path
|
||||
func GetFilesystem(path string) (string, error) {
|
||||
statfs := &unix.Statfs_t{}
|
||||
err := unix.Statfs(path, statfs)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
return "", err
|
||||
}
|
||||
// Use a type conversion to make type an int64. On s390x it's a uint32.
|
||||
t, ok := fsTypes[int64(statfs.Type)]
|
||||
if !ok {
|
||||
log.Printf("WARNING: detected %v has unknown filesystem type %x", path, statfs.Type)
|
||||
return nil
|
||||
}
|
||||
switch t {
|
||||
case "aufs", "overlayfs", "tmpfs":
|
||||
return fmt.Errorf("%v uses unsupported filesystem type: %v", path, t)
|
||||
default:
|
||||
log.Printf("Detected %v has filesystem type '%v'", path, t)
|
||||
return nil
|
||||
return "unknown", nil
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,7 @@ 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.
|
||||
*/
|
||||
package main
|
||||
package containerruntime
|
||||
|
||||
// Dummy version of this function, only for non-Linux systems.
|
||||
// Having this allows unit tests to be run on other platforms (e.g. macOS)
|
||||
103
internal/containerruntimelogger/logruntime.go
Normal file
103
internal/containerruntimelogger/logruntime.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package containerruntimelogger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/containerruntime"
|
||||
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||
"github.com/ibm-messaging/mq-container/internal/user"
|
||||
)
|
||||
|
||||
// LogContainerDetails logs details about the container runtime
|
||||
func LogContainerDetails(log *logger.Logger) error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return fmt.Errorf("Unsupported platform: %v", runtime.GOOS)
|
||||
}
|
||||
log.Printf("CPU architecture: %v", runtime.GOARCH)
|
||||
kv, err := containerruntime.GetKernelVersion()
|
||||
if err == nil {
|
||||
log.Printf("Linux kernel version: %v", kv)
|
||||
}
|
||||
cr, err := containerruntime.GetContainerRuntime()
|
||||
if err == nil {
|
||||
log.Printf("Container runtime: %v", cr)
|
||||
}
|
||||
bi, err := containerruntime.GetBaseImage()
|
||||
if err == nil {
|
||||
log.Printf("Base image: %v", bi)
|
||||
}
|
||||
u, err := user.GetUser()
|
||||
if err == nil {
|
||||
if len(u.SupplementalGID) == 0 {
|
||||
log.Printf("Running as user ID %v (%v) with primary group %v", u.UID, u.Name, u.PrimaryGID)
|
||||
} else {
|
||||
log.Printf("Running as user ID %v (%v) with primary group %v, and supplementary groups %v", u.UID, u.Name, u.PrimaryGID, strings.Join(u.SupplementalGID, ","))
|
||||
}
|
||||
}
|
||||
caps, err := containerruntime.GetCapabilities()
|
||||
capLogged := false
|
||||
if err == nil {
|
||||
for k, v := range caps {
|
||||
if len(v) > 0 {
|
||||
log.Printf("Capabilities (%s set): %v", strings.ToLower(k), strings.Join(v, ","))
|
||||
capLogged = true
|
||||
}
|
||||
}
|
||||
if !capLogged {
|
||||
log.Print("Capabilities: none")
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Error getting capabilities: %v", err)
|
||||
}
|
||||
sc, err := containerruntime.GetSeccomp()
|
||||
if err == nil {
|
||||
log.Printf("seccomp enforcing mode: %v", sc)
|
||||
}
|
||||
log.Printf("Process security attributes: %v", containerruntime.GetSecurityAttributes())
|
||||
m, err := containerruntime.GetMounts()
|
||||
if err == nil {
|
||||
if len(m) == 0 {
|
||||
log.Print("No volume detected. Persistent messages may be lost")
|
||||
} else {
|
||||
for mountPoint, fsType := range m {
|
||||
log.Printf("Detected '%v' volume mounted to %v", fsType, mountPoint)
|
||||
if !containerruntime.SupportedFilesystem(fsType) {
|
||||
return fmt.Errorf("%v uses unsupported filesystem type: %v", mountPoint, fsType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// For a multi-instance queue manager - check all required mounts exist & validate filesystem type
|
||||
if os.Getenv("MQ_MULTI_INSTANCE") == "true" {
|
||||
log.Println("Multi-instance queue manager: enabled")
|
||||
reqMounts := []string{"/mnt/mqm", "/mnt/mqm-log", "/mnt/mqm-data"}
|
||||
for _, mountPoint := range reqMounts {
|
||||
if fsType, ok := m[mountPoint]; ok {
|
||||
if !containerruntime.ValidMultiInstanceFilesystem(fsType) {
|
||||
return fmt.Errorf("%v uses filesystem type '%v' which is invalid for a multi-instance queue manager", mountPoint, fsType)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Missing required mount '%v' for a multi-instance queue manager", mountPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
58
internal/copy/copy.go
Normal file
58
internal/copy/copy.go
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package copy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/filecheck"
|
||||
)
|
||||
|
||||
func CopyFileMode(src, dest string, perm os.FileMode) error {
|
||||
|
||||
err := filecheck.CheckFileSource(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %s for copy: %v", src, err)
|
||||
}
|
||||
|
||||
// #nosec G304 - filename variable 'src' is checked above to ensure it is valid
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %s for copy: %v", src, err)
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, perm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %s for copy: %v", dest, err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = out.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// CopyFile copies the specified file
|
||||
func CopyFile(src, dest string) error {
|
||||
return CopyFileMode(src, dest, 0770)
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// +build !mqdev
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -15,8 +13,25 @@ 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
func postInit(name string) error {
|
||||
package filecheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckFileSource checks the filename is valid
|
||||
func CheckFileSource(fileName string) error {
|
||||
|
||||
absFile, _ := filepath.Abs(fileName)
|
||||
|
||||
prefixes := []string{"bin", "boot", "dev", "lib", "lib64", "proc", "sbin", "sys"}
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(absFile, filepath.Join("/", prefix)) {
|
||||
return fmt.Errorf("Filename resolves to invalid path '%v'", absFile)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
40
internal/filecheck/filecheck_test.go
Normal file
40
internal/filecheck/filecheck_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package filecheck
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckFileSource(t *testing.T) {
|
||||
|
||||
invalidFilenames := []string{"/bin", "/boot", "/dev", "/lib", "/lib64", "/proc", "/sbin", "/sys", "/bin/myfile", "/boot/mydir/myfile", "/var/../dev", "/var/../lib/myfile"}
|
||||
for _, invalidFilename := range invalidFilenames {
|
||||
err := CheckFileSource(invalidFilename)
|
||||
if err == nil {
|
||||
t.Errorf("Expected to receive an error for filename '%v'", invalidFilename)
|
||||
}
|
||||
}
|
||||
|
||||
validFilenames := []string{"/var", "/mydir/dev", "/mydir/dev/myfile"}
|
||||
for _, validFilename := range validFilenames {
|
||||
err := CheckFileSource(validFilename)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error received: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -13,12 +13,15 @@ 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
// Package keystore contains code to create and update keystores
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -53,23 +56,44 @@ func NewCMSKeyStore(filename, password string) *KeyStore {
|
||||
}
|
||||
}
|
||||
|
||||
// NewPKCS12KeyStore creates a new PKCS12 Key Store, managed by the runmqakm command
|
||||
func NewPKCS12KeyStore(filename, password string) *KeyStore {
|
||||
return &KeyStore{
|
||||
Filename: filename,
|
||||
Password: password,
|
||||
keyStoreType: "p12",
|
||||
command: "/opt/mqm/bin/runmqakm",
|
||||
}
|
||||
}
|
||||
|
||||
// Create a key store, if it doesn't already exist
|
||||
func (ks *KeyStore) Create() error {
|
||||
_, err := os.Stat(ks.Filename)
|
||||
if err == nil {
|
||||
// Keystore already exists so we should refresh it by deleting it.
|
||||
extension := filepath.Ext(ks.Filename)
|
||||
log.Debugf("Refreshing keystore: %v", ks.Filename)
|
||||
if ks.keyStoreType == "cms" {
|
||||
// Only delete these when we are refreshing the kdb keystore
|
||||
stashFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".sth"
|
||||
rdbFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".rdb"
|
||||
crlFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".crl"
|
||||
os.Remove(stashFile)
|
||||
os.Remove(rdbFile)
|
||||
os.Remove(crlFile)
|
||||
err = os.Remove(stashFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Remove(rdbFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Remove(crlFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = os.Remove(ks.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Remove(ks.Filename)
|
||||
} else if !os.IsNotExist(err) {
|
||||
// If the keystore exists but cannot be accessed then return the error
|
||||
return err
|
||||
@@ -83,12 +107,10 @@ func (ks *KeyStore) Create() error {
|
||||
|
||||
mqmUID, mqmGID, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
err = os.Chown(ks.Filename, mqmUID, mqmGID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -98,7 +120,6 @@ func (ks *KeyStore) Create() error {
|
||||
func (ks *KeyStore) CreateStash() error {
|
||||
extension := filepath.Ext(ks.Filename)
|
||||
stashFile := ks.Filename[0:len(ks.Filename)-len(extension)] + ".sth"
|
||||
log.Debugf("TLS stash file: %v", stashFile)
|
||||
_, err := os.Stat(stashFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -111,12 +132,10 @@ func (ks *KeyStore) CreateStash() error {
|
||||
}
|
||||
mqmUID, mqmGID, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
err = os.Chown(stashFile, mqmUID, mqmGID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -131,6 +150,33 @@ func (ks *KeyStore) Import(inputFile, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSelfSignedCertificate creates a self-signed certificate in the keystore
|
||||
func (ks *KeyStore) CreateSelfSignedCertificate(label, dn, hostname string) error {
|
||||
out, _, err := command.Run(ks.command, "-cert", "-create", "-db", ks.Filename, "-pw", ks.Password, "-label", label, "-dn", dn, "-san_dnsname", hostname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running \"%v -cert -create\": %v %s", ks.command, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add adds a CA certificate to the keystore
|
||||
func (ks *KeyStore) Add(inputFile, label string) error {
|
||||
out, _, err := command.Run(ks.command, "-cert", "-add", "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password, "-file", inputFile, "-label", label)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running \"%v -cert -add\": %v %s", ks.command, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add adds a CA certificate to the keystore
|
||||
func (ks *KeyStore) AddNoLabel(inputFile string) error {
|
||||
out, _, err := command.Run(ks.command, "-cert", "-add", "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password, "-file", inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running \"%v -cert -add\": %v %s", ks.command, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertificateLabels returns the labels of all certificates in the key store
|
||||
func (ks *KeyStore) GetCertificateLabels() ([]string, error) {
|
||||
out, _, err := command.Run(ks.command, "-cert", "-list", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
|
||||
@@ -155,9 +201,44 @@ func (ks *KeyStore) GetCertificateLabels() ([]string, error) {
|
||||
|
||||
// RenameCertificate renames the specified certificate
|
||||
func (ks *KeyStore) RenameCertificate(from, to string) error {
|
||||
if ks.command == "/opt/mqm/bin/runmqakm" {
|
||||
// runmqakm can't handle certs with ' in them so just use capicmd
|
||||
// Overriding gosec here as this function is in an internal package and only callable by our internal functions.
|
||||
// #nosec G204
|
||||
cmd := exec.Command("/opt/mqm/gskit8/bin/gsk8capicmd_64", "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to)
|
||||
cmd.Env = append(os.Environ(), "LD_LIBRARY_PATH=/opt/mqm/gskit8/lib64/:/opt/mqm/gskit8/lib")
|
||||
out, _, err := command.RunCmd(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running \"%v -cert -rename\": %v %s", "/opt/mqm/gskit8/bin/gsk8capicmd_64", err, out)
|
||||
}
|
||||
} else {
|
||||
out, _, err := command.Run(ks.command, "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running \"%v -cert -rename\": %v %s", ks.command, err, out)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAllCertificates Lists all certificates in the keystore
|
||||
func (ks *KeyStore) ListAllCertificates() ([]string, error) {
|
||||
out, _, err := command.Run(ks.command, "-cert", "-list", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error running \"%v -cert -list\": %v %s", ks.command, err, out)
|
||||
}
|
||||
scanner := bufio.NewScanner(strings.NewReader(out))
|
||||
var labels []string
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if strings.HasPrefix(s, "-") || strings.HasPrefix(s, "*-") || strings.HasPrefix(s, "!") {
|
||||
s := strings.TrimLeft(s, "-*!")
|
||||
labels = append(labels, strings.TrimSpace(s))
|
||||
}
|
||||
}
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -44,7 +44,7 @@ type Logger struct {
|
||||
pid string
|
||||
serverName string
|
||||
host string
|
||||
user *user.User
|
||||
userName string
|
||||
}
|
||||
|
||||
// NewLogger creates a new logger
|
||||
@@ -53,9 +53,13 @@ func NewLogger(writer io.Writer, debug bool, json bool, serverName string) (*Log
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This can fail because the container's running as a random UID which
|
||||
// is not known by the OS. We don't want this to break the logging
|
||||
// entirely, so just use a blank user name.
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
userName := ""
|
||||
if err == nil {
|
||||
userName = user.Username
|
||||
}
|
||||
return &Logger{
|
||||
mutex: sync.Mutex{},
|
||||
@@ -66,7 +70,7 @@ func NewLogger(writer io.Writer, debug bool, json bool, serverName string) (*Log
|
||||
pid: strconv.Itoa(os.Getpid()),
|
||||
serverName: serverName,
|
||||
host: hostname,
|
||||
user: user,
|
||||
userName: userName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -93,7 +97,7 @@ func (l *Logger) log(level string, msg string) {
|
||||
"ibm_serverName": l.serverName,
|
||||
"ibm_processName": l.processName,
|
||||
"ibm_processId": l.pid,
|
||||
"ibm_userName": l.user.Username,
|
||||
"ibm_userName": l.userName,
|
||||
"type": "mq_containerlog",
|
||||
}
|
||||
s, err := l.format(entry)
|
||||
@@ -110,11 +114,6 @@ func (l *Logger) log(level string, msg string) {
|
||||
l.mutex.Unlock()
|
||||
}
|
||||
|
||||
// LogDirect logs a message directly to stdout
|
||||
func (l *Logger) LogDirect(msg string) {
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
// Debug logs a line as debug
|
||||
func (l *Logger) Debug(args ...interface{}) {
|
||||
if l.debug {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||
"github.com/ibm-messaging/mq-container/internal/ready"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
@@ -39,12 +40,21 @@ var (
|
||||
// GatherMetrics gathers metrics for the queue manager
|
||||
func GatherMetrics(qmName string, log *logger.Logger) {
|
||||
|
||||
// If running in standby mode - wait until the queue manager becomes active
|
||||
for {
|
||||
active, _ := ready.IsRunningAsActiveQM(qmName)
|
||||
if active {
|
||||
break
|
||||
}
|
||||
time.Sleep(requestTimeout * time.Second)
|
||||
}
|
||||
|
||||
metricsEnabled = true
|
||||
|
||||
err := startMetricsGathering(qmName, log)
|
||||
if err != nil {
|
||||
log.Errorf("Metrics Error: %s", err.Error())
|
||||
StopMetricsGathering()
|
||||
StopMetricsGathering(log)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +86,7 @@ func startMetricsGathering(qmName string, log *logger.Logger) error {
|
||||
http.Handle("/metrics", prometheus.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
// #nosec G104
|
||||
w.Write([]byte("Status: METRICS ACTIVE"))
|
||||
})
|
||||
|
||||
@@ -83,7 +94,7 @@ func startMetricsGathering(qmName string, log *logger.Logger) error {
|
||||
err = metricsServer.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
log.Errorf("Metrics Error: Failed to handle metrics request: %v", err)
|
||||
StopMetricsGathering()
|
||||
StopMetricsGathering(log)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -91,7 +102,7 @@ func startMetricsGathering(qmName string, log *logger.Logger) error {
|
||||
}
|
||||
|
||||
// StopMetricsGathering stops gathering metrics for the queue manager
|
||||
func StopMetricsGathering() {
|
||||
func StopMetricsGathering(log *logger.Logger) {
|
||||
|
||||
if metricsEnabled {
|
||||
|
||||
@@ -101,6 +112,9 @@ func StopMetricsGathering() {
|
||||
// Shutdown HTTP server
|
||||
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
metricsServer.Shutdown(timeout)
|
||||
err := metricsServer.Shutdown(timeout)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to shutdown metrics server: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ func processMetrics(log *logger.Logger, qmName string) {
|
||||
firstConnect = false
|
||||
startChannel <- true
|
||||
}
|
||||
// #nosec G104
|
||||
metrics, _ = initialiseMetrics(log)
|
||||
}
|
||||
|
||||
|
||||
20
internal/mqini/mqat.ini
Normal file
20
internal/mqini/mqat.ini
Normal file
@@ -0,0 +1,20 @@
|
||||
#*******************************************************************#
|
||||
#* Module Name: mqat.ini *#
|
||||
#* Type : IBM MQ queue manager configuration file *#
|
||||
# Function : Define the configuration of application activity *#
|
||||
#* trace for a single queue manager. *#
|
||||
#*******************************************************************#
|
||||
|
||||
# Global settings stanza, default values
|
||||
AllActivityTrace:
|
||||
ActivityInterval=1
|
||||
ActivityCount=100
|
||||
TraceLevel=MEDIUM
|
||||
TraceMessageData=0
|
||||
StopOnGetTraceMsg=ON
|
||||
SubscriptionDelivery=BATCHED
|
||||
|
||||
# Prevent the sample activity trace program from generating data
|
||||
ApplicationTrace:
|
||||
ApplName=amqsact*
|
||||
Trace=OFF
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,12 @@ package mqini
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
@@ -34,6 +39,12 @@ type QueueManager struct {
|
||||
InstallationName string
|
||||
}
|
||||
|
||||
var qmgrDir string
|
||||
|
||||
var stanzasQMINI []string
|
||||
|
||||
var stanzasMQATINI []string
|
||||
|
||||
// getQueueManagerFromStanza parses a queue manager stanza
|
||||
func getQueueManagerFromStanza(stanza string) (*QueueManager, error) {
|
||||
scanner := bufio.NewScanner(strings.NewReader(stanza))
|
||||
@@ -71,5 +82,247 @@ func GetQueueManager(name string) (*QueueManager, error) {
|
||||
// GetErrorLogDirectory returns the directory holding the error logs for the
|
||||
// specified queue manager
|
||||
func GetErrorLogDirectory(qm *QueueManager) string {
|
||||
if qm.DataPath != "" {
|
||||
return filepath.Join(qm.DataPath, "errors")
|
||||
}
|
||||
return filepath.Join(qm.Prefix, "qmgrs", qm.Directory, "errors")
|
||||
}
|
||||
|
||||
//AddStanzas Reads supplied mq ini configuration files and updates the stanzas
|
||||
//into queue manager's ini configuration files.
|
||||
func AddStanzas(qmname string) error {
|
||||
|
||||
//find the qmgr directory.
|
||||
qm, err := GetQueueManager(qmname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qmgrDir = filepath.Join(qm.Prefix, "qmgrs", qm.Directory)
|
||||
|
||||
//Find the users ini configuration file
|
||||
files := getIniFileList()
|
||||
if len(files) > 1 {
|
||||
msg := fmt.Sprintf("[ %v ]", files)
|
||||
return errors.New("Only a single ini file can be provided. Following ini files are found:" + msg)
|
||||
}
|
||||
if len(files) == 0 {
|
||||
//no ini file update required.
|
||||
return nil
|
||||
}
|
||||
|
||||
iniFileBytes, err := ioutil.ReadFile(files[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userconfig := string(iniFileBytes)
|
||||
if len(userconfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
//Prepare a list of all supported stanzas
|
||||
PopulateAllAvailableStanzas()
|
||||
|
||||
//Update the qmgr ini file with user config.
|
||||
qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeConfigStanzas(qmConfig, atConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopulateAllAvailableStanzas initializes the ini stanzas prescribed by mq specification.
|
||||
func PopulateAllAvailableStanzas() {
|
||||
stanzasQMINI = []string{"ExitPath",
|
||||
"Log",
|
||||
"Service",
|
||||
"ServiceComponent",
|
||||
"Channels",
|
||||
"TCP",
|
||||
"ApiExitLocal",
|
||||
"AccessMode",
|
||||
"RestrictedMode",
|
||||
"XAResourceManager",
|
||||
"DefaultBindType",
|
||||
"SSL",
|
||||
"DiagnosticMessages",
|
||||
"Filesystem",
|
||||
"Security",
|
||||
"TuningParameters",
|
||||
"ExitPropertiesLocal",
|
||||
"LU62",
|
||||
"NETBIOS"}
|
||||
|
||||
stanzasMQATINI = []string{"AllActivityTrace", "ApplicationTrace"}
|
||||
}
|
||||
|
||||
// getIniFileList Checks for the user supplied ini file in /etc/mqm directory.
|
||||
func getIniFileList() []string {
|
||||
|
||||
fileList := []string{}
|
||||
filepath.Walk("/etc/mqm", func(path string, f os.FileInfo, err error) error {
|
||||
if strings.HasSuffix(path, ".ini") {
|
||||
fileList = append(fileList, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return fileList
|
||||
}
|
||||
|
||||
//PrepareConfigStanzasToWrite Reads through the user supplied ini config file and prepares list of
|
||||
//updates to be written into corresponding mq ini files (qm.ini and/or mqat.ini files.)
|
||||
func PrepareConfigStanzasToWrite(userconfig string) (string, string, error) {
|
||||
|
||||
var qminiConfigStr string
|
||||
var mqatiniConfigStr string
|
||||
|
||||
//read the initial version.
|
||||
iniFileBytes, err := ioutil.ReadFile(filepath.Join(qmgrDir, "qm.ini"))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
qminiConfigStr = string(iniFileBytes)
|
||||
|
||||
iniFileBytes, err = ioutil.ReadFile(filepath.Join(qmgrDir, "mqat.ini"))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
mqatiniConfigStr = string(iniFileBytes)
|
||||
|
||||
stanzaListMerge := make(map[string]strings.Builder)
|
||||
stanzaListAppend := make(map[string]strings.Builder)
|
||||
var sbAppend strings.Builder
|
||||
var sbMerger strings.Builder
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(userconfig))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
consumetoAppend := false
|
||||
consumeToMerge := false
|
||||
var stanza string
|
||||
|
||||
//read through the user file and prepare what we want.
|
||||
for scanner.Scan() {
|
||||
if strings.Contains(scanner.Text(), ":") {
|
||||
consumetoAppend = false
|
||||
consumeToMerge = false
|
||||
stanza = scanner.Text()
|
||||
//check if this stanza exists in the qm.ini/mqat.ini files
|
||||
if strings.Contains(qminiConfigStr, stanza) ||
|
||||
(strings.Contains(mqatiniConfigStr, stanza) && !(strings.Contains(stanza, "ApplicationTrace"))) {
|
||||
consumeToMerge = true
|
||||
sbMerger = strings.Builder{}
|
||||
|
||||
stanzaListMerge[stanza] = sbMerger
|
||||
} else {
|
||||
consumetoAppend = true
|
||||
sbAppend = strings.Builder{}
|
||||
stanzaListAppend[stanza] = sbAppend
|
||||
}
|
||||
} else {
|
||||
if consumetoAppend {
|
||||
sb := stanzaListAppend[stanza]
|
||||
sb.WriteString(scanner.Text() + "\n")
|
||||
stanzaListAppend[stanza] = sb
|
||||
}
|
||||
if consumeToMerge {
|
||||
sb := stanzaListMerge[stanza]
|
||||
sb.WriteString(scanner.Text() + "\n")
|
||||
stanzaListMerge[stanza] = sb
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//merge if stanza exits.
|
||||
if len(stanzaListMerge) > 0 {
|
||||
for key := range stanzaListMerge {
|
||||
toWrite, filename := ValidateStanzaToWrite(key)
|
||||
if toWrite {
|
||||
attrList := stanzaListMerge[key]
|
||||
switch filename {
|
||||
case "qm.ini":
|
||||
qminiConfigStr = prepareStanzasToMerge(key, attrList, qminiConfigStr)
|
||||
case "mqat.ini":
|
||||
mqatiniConfigStr = prepareStanzasToMerge(key, attrList, mqatiniConfigStr)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//append new stanzas.
|
||||
if len(stanzaListAppend) > 0 {
|
||||
for key := range stanzaListAppend {
|
||||
attrList := stanzaListAppend[key]
|
||||
if strings.Contains(strings.Join(stanzasMQATINI, ", "), strings.TrimSuffix(strings.TrimSpace(key), ":")) {
|
||||
mqatiniConfigStr = prepareStanzasToAppend(key, attrList, mqatiniConfigStr)
|
||||
} else {
|
||||
qminiConfigStr = prepareStanzasToAppend(key, attrList, qminiConfigStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return qminiConfigStr, mqatiniConfigStr, nil
|
||||
}
|
||||
|
||||
//ValidateStanzaToWrite Validates stanza to be written and the file it belongs to.
|
||||
func ValidateStanzaToWrite(stanza string) (bool, string) {
|
||||
stanza = strings.TrimSpace(stanza)
|
||||
if strings.Contains(stanza, ":") {
|
||||
stanza = stanza[:len(stanza)-1]
|
||||
}
|
||||
|
||||
if strings.Contains(strings.Join(stanzasQMINI, ", "), stanza) {
|
||||
return true, "qm.ini"
|
||||
} else if strings.Contains(strings.Join(stanzasMQATINI, ", "), stanza) {
|
||||
return true, "mqat.ini"
|
||||
} else {
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
//prepareStanzasToAppend Prepares list of stanzas that are to be appended into qm ini files(qm.ini/mqat.ini)
|
||||
func prepareStanzasToAppend(key string, attrList strings.Builder, iniConfig string) string {
|
||||
newVal := key + "\n" + attrList.String()
|
||||
iniConfig = iniConfig + newVal
|
||||
return iniConfig
|
||||
}
|
||||
|
||||
//prepareStanzasToMerge Prepares list of stanzas that are to be updated into qm ini files(qm.ini/mqat.ini)
|
||||
//These stanzas are already present in mq ini files and their values have to be updated with user supplied ini.
|
||||
func prepareStanzasToMerge(key string, attrList strings.Builder, iniConfig string) string {
|
||||
lineScanner := bufio.NewScanner(strings.NewReader(attrList.String()))
|
||||
lineScanner.Split(bufio.ScanLines)
|
||||
for lineScanner.Scan() {
|
||||
attrLine := lineScanner.Text()
|
||||
keyvalue := strings.Split(attrLine, "=")
|
||||
//this line present in qm.ini, update value.
|
||||
if strings.Contains(iniConfig, keyvalue[0]) {
|
||||
re := regexp.MustCompile(keyvalue[0] + "=.*")
|
||||
iniConfig = re.ReplaceAllString(iniConfig, attrLine)
|
||||
} else { //this line not present in qm.ini file, add it.
|
||||
re := regexp.MustCompile(key)
|
||||
newVal := key + "\n" + attrLine
|
||||
iniConfig = re.ReplaceAllString(iniConfig, newVal)
|
||||
}
|
||||
}
|
||||
return iniConfig
|
||||
}
|
||||
|
||||
//writeConfigStanzas Writes the ini file updates into corresponding mq ini files.
|
||||
func writeConfigStanzas(qmConfig string, atConfig string) error {
|
||||
|
||||
err := ioutil.WriteFile(filepath.Join(qmgrDir, "qm.ini"), []byte(qmConfig), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(qmgrDir, "mqat.ini"), []byte(atConfig), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,9 @@ limitations under the License.
|
||||
package mqini
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -62,3 +64,139 @@ func TestGetQueueManager(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIniFileStanzas(t *testing.T) {
|
||||
PopulateAllAvailableStanzas()
|
||||
|
||||
checkReturns("ApiExitLocal", true, true, t)
|
||||
checkReturns("Channels", true, true, t)
|
||||
checkReturns("TCP", true, true, t)
|
||||
checkReturns("ServiceComponent", true, true, t)
|
||||
checkReturns("Service", true, true, t)
|
||||
checkReturns("AccessMode", true, true, t)
|
||||
checkReturns("RestrictedMode", true, true, t)
|
||||
checkReturns("XAResourceManager", true, true, t)
|
||||
checkReturns("SSL", true, true, t)
|
||||
checkReturns("Security", true, true, t)
|
||||
checkReturns("TuningParameters", true, true, t)
|
||||
checkReturns("ABC", false, false, t)
|
||||
checkReturns("#1234ABD", true, false, t)
|
||||
checkReturns("AllActivityTrace", false, true, t)
|
||||
checkReturns("ApplicationTrace", false, true, t)
|
||||
checkReturns("xyz123abvc", false, false, t)
|
||||
}
|
||||
|
||||
func TestIniFile1Update(t *testing.T) {
|
||||
|
||||
iniFileBytes, err := ioutil.ReadFile("test1qm.ini")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: [%s]\n", err.Error())
|
||||
}
|
||||
userconfig := string(iniFileBytes)
|
||||
qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: [%s]\n", err.Error())
|
||||
}
|
||||
if len(atConfig) == 0 {
|
||||
t.Errorf("Unexpected stanza file update: mqat.ini[%s]\n", atConfig)
|
||||
}
|
||||
if len(qmConfig) == 0 {
|
||||
t.Errorf("Expected stanza file not found: qm.ini\n")
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(userconfig))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.Contains(qmConfig, line) {
|
||||
t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIniFile2Update(t *testing.T) {
|
||||
|
||||
iniFileBytes, err := ioutil.ReadFile("test2qm.ini")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: [%s]\n", err.Error())
|
||||
}
|
||||
userconfig := string(iniFileBytes)
|
||||
qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: [%s]\n", err.Error())
|
||||
}
|
||||
if len(atConfig) == 0 {
|
||||
t.Errorf("Expected stanza file not found: mqat.ini\n")
|
||||
}
|
||||
if len(qmConfig) == 0 {
|
||||
t.Errorf("Expected stanza file not found: qm.ini\n")
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(userconfig))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.Contains(atConfig, line) {
|
||||
t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIniFile3Update(t *testing.T) {
|
||||
|
||||
i := 0
|
||||
iniFileBytes, err := ioutil.ReadFile("test3qm.ini")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: [%s]\n", err.Error())
|
||||
}
|
||||
userconfig := string(iniFileBytes)
|
||||
qmConfig, atConfig, err := PrepareConfigStanzasToWrite(userconfig)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: [%s]\n", err.Error())
|
||||
}
|
||||
if len(qmConfig) == 0 {
|
||||
t.Errorf("Unexpected stanza file update: qm.ini[%s]\n", atConfig)
|
||||
}
|
||||
if len(atConfig) == 0 {
|
||||
t.Errorf("Expected stanza file not found: mqat.ini\n")
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(userconfig))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
i++
|
||||
//first 20 lines of test3qm.ini shall go into qm.ini file and rest into mqat.ini file.
|
||||
if i < 20 {
|
||||
if !strings.Contains(qmConfig, line) {
|
||||
t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig)
|
||||
}
|
||||
} else if i > 20 {
|
||||
if !strings.Contains(atConfig, line) {
|
||||
t.Errorf("Expected stanza line not found in updated string. line=%s\n, Stanza:%s\n", line, qmConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkReturns(stanza string, isqmini bool, shouldexist bool, t *testing.T) {
|
||||
|
||||
exists, filename := ValidateStanzaToWrite(stanza)
|
||||
if exists != shouldexist {
|
||||
t.Errorf("Stanza should exist %t but found was %t", shouldexist, exists)
|
||||
}
|
||||
|
||||
if shouldexist {
|
||||
if isqmini {
|
||||
if filename != "qm.ini" {
|
||||
t.Errorf("Expected filename:qm.ini for stanza:%s. But got %s", stanza, filename)
|
||||
}
|
||||
} else {
|
||||
if filename != "mqat.ini" {
|
||||
t.Errorf("Expected filename:mqat.ini for stanza:%s. But got %s", stanza, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
internal/mqini/qm.ini
Normal file
45
internal/mqini/qm.ini
Normal file
@@ -0,0 +1,45 @@
|
||||
#*******************************************************************#
|
||||
#* Module Name: qm.ini *#
|
||||
#* Type : IBM MQ queue manager configuration file *#
|
||||
# Function : Define the configuration of a single queue manager *#
|
||||
#* *#
|
||||
#*******************************************************************#
|
||||
#* Notes : *#
|
||||
#* 1) This file defines the configuration of the queue manager *#
|
||||
#* *#
|
||||
#*******************************************************************#
|
||||
ExitPath:
|
||||
ExitsDefaultPath=C:\ProgramData\IBM\MQ\exits
|
||||
ExitsDefaultPath64=C:\ProgramData\IBM\MQ\exits64
|
||||
InstanceData:
|
||||
InstanceID=1562831591
|
||||
Startup=ServiceManual
|
||||
#* *#
|
||||
#* *#
|
||||
Log:
|
||||
LogPrimaryFiles=3
|
||||
LogSecondaryFiles=2
|
||||
LogFilePages=4096
|
||||
LogType=CIRCULAR
|
||||
LogBufferPages=0
|
||||
LogPath=C:\ProgramData\IBM\MQ\log\INI1\
|
||||
LogWriteIntegrity=TripleWrite
|
||||
Service:
|
||||
Name=AuthorizationService
|
||||
EntryPoints=14
|
||||
ServiceComponent:
|
||||
Service=AuthorizationService
|
||||
Name=MQSeries.WindowsNT.auth.service
|
||||
Module=amqzfu.dll
|
||||
ComponentDataSize=0
|
||||
Channels:
|
||||
ChlauthEarlyAdopt=Y
|
||||
TCP:
|
||||
SndBuffSize=0
|
||||
RcvBuffSize=0
|
||||
RcvSndBuffSize=0
|
||||
RcvRcvBuffSize=0
|
||||
ClntSndBuffSize=0
|
||||
ClntRcvBuffSize=0
|
||||
SvrSndBuffSize=0
|
||||
SvrRcvBuffSize=0
|
||||
5
internal/mqini/test1qm.ini
Normal file
5
internal/mqini/test1qm.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
ApiExitLocal:
|
||||
Sequence=1
|
||||
Function=EntryPoint
|
||||
Module=/opt/mylibs/mylib.so
|
||||
Name=mylib
|
||||
7
internal/mqini/test2qm.ini
Normal file
7
internal/mqini/test2qm.ini
Normal file
@@ -0,0 +1,7 @@
|
||||
AllActivityTrace:
|
||||
ActivityInterval=11
|
||||
ActivityCount=1
|
||||
TraceLevel=INFO
|
||||
ApplicationTrace:
|
||||
ApplName=amqsget
|
||||
Trace=ON
|
||||
23
internal/mqini/test3qm.ini
Normal file
23
internal/mqini/test3qm.ini
Normal file
@@ -0,0 +1,23 @@
|
||||
ApiExitLocal:
|
||||
Sequence=1
|
||||
Function=EntryPoint
|
||||
Module=/opt/MQOpenTracing/MQOpenTracingExit.so
|
||||
Name=MQOpenTracingExit
|
||||
Channels:
|
||||
MQIBindType=FASTPATH
|
||||
Log:
|
||||
LogPrimaryFiles=30
|
||||
LogType=CIRCULAR
|
||||
LogPath=/ProgramfILES/IBM/MQ/log/INI1/
|
||||
TCP:
|
||||
SndBuffSize=4095
|
||||
RcvBuffSize=4095
|
||||
RcvSndBuffSize=4095
|
||||
RcvRcvBuffSize=4095
|
||||
ClntSndBuffSize=2049
|
||||
ClntRcvBuffSize=2049
|
||||
SvrSndBuffSize=2049
|
||||
SvrRcvBuffSize=2049
|
||||
ApplicationTrace:
|
||||
ApplName=amqsput
|
||||
Trace=ON
|
||||
264
internal/mqscredact/mqscredact.go
Normal file
264
internal/mqscredact/mqscredact.go
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
|
||||
package mqscredact
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/* List of sensitive MQ Parameters */
|
||||
var sensitiveParameters = []string{"LDAPPWD", "PASSWORD", "SSLCRYP"}
|
||||
|
||||
// redactionString is what sensitive paramters will be replaced with
|
||||
const redactionString = "(*********)"
|
||||
|
||||
func findEndOfParamterString(stringDenoter rune, r *bufio.Reader) string {
|
||||
parameter := ""
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return parameter
|
||||
}
|
||||
parameter = parameter + string(char)
|
||||
if char == stringDenoter {
|
||||
break
|
||||
} else if char == '\n' {
|
||||
// Check if we're on a comment line
|
||||
NewLineLoop:
|
||||
for {
|
||||
// Look at next character without moving buffer forwards
|
||||
chars, err := r.Peek(1)
|
||||
if err != nil {
|
||||
return parameter
|
||||
}
|
||||
// Check if we're at the beginning of some data.
|
||||
startOutput, _ := regexp.MatchString(`[^:0-9\s]`, string(chars[0]))
|
||||
if startOutput {
|
||||
// We are at the start, check if we're on a comment line
|
||||
if chars[0] == '*' {
|
||||
// found a comment line. go to the next newline chraracter
|
||||
CommentLoop:
|
||||
for {
|
||||
char, _, err = r.ReadRune()
|
||||
if err != nil {
|
||||
return parameter
|
||||
}
|
||||
parameter = parameter + string(char)
|
||||
if char == '\n' {
|
||||
break CommentLoop
|
||||
}
|
||||
}
|
||||
// Go round again as we're now on a new line
|
||||
continue NewLineLoop
|
||||
}
|
||||
// We've checked for comment and it isn't a comment line so break without moving buffer forwards
|
||||
break NewLineLoop
|
||||
}
|
||||
// Move the buffer forward and try again
|
||||
char, _, _ = r.ReadRune()
|
||||
parameter = parameter + string(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parameter
|
||||
}
|
||||
|
||||
// getParameterString reads from r in order to find the end of the MQSC Parameter value. This is enclosed in ( ).
|
||||
// This function will return what it finds and will increment the reader pointer along as it goes.
|
||||
func getParameterString(r *bufio.Reader) string {
|
||||
// Add the ( in as it will have been dropped before.
|
||||
parameter := "("
|
||||
Loop:
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return parameter
|
||||
}
|
||||
|
||||
parameter = parameter + string(char)
|
||||
|
||||
switch char {
|
||||
case ')':
|
||||
break Loop
|
||||
// TODO: Duplicate code..
|
||||
case '\'', '"':
|
||||
parameter = parameter + findEndOfParamterString(char, r)
|
||||
}
|
||||
}
|
||||
|
||||
return parameter
|
||||
}
|
||||
|
||||
func resetAllParameters(currentVerb, originalString *string, lineContinuation, foundGap, parameterNext, redacting, checkComment *bool) {
|
||||
*currentVerb = ""
|
||||
*originalString = ""
|
||||
*lineContinuation = false
|
||||
*foundGap = false
|
||||
*parameterNext = false
|
||||
*redacting = false
|
||||
*checkComment = true
|
||||
}
|
||||
|
||||
// Redact is the main function for redacting sensitive parameters in MQSC strings
|
||||
// It accepts a string and redacts sensitive paramters such as LDAPPWD or PASSWORD
|
||||
func Redact(out string) (string, error) {
|
||||
out = strings.TrimSpace(out)
|
||||
var returnStr, currentVerb, originalString string
|
||||
var lineContinuation, foundGap, parameterNext, redacting, checkComment bool
|
||||
newline := true
|
||||
resetAllParameters(¤tVerb, &originalString, &lineContinuation, &foundGap, ¶meterNext, &redacting, &checkComment)
|
||||
r := bufio.NewReader(strings.NewReader(out))
|
||||
|
||||
MainLoop:
|
||||
for {
|
||||
// We have found a opening ( so use special parameter parsing
|
||||
if parameterNext {
|
||||
parameterStr := getParameterString(r)
|
||||
if !redacting {
|
||||
returnStr = returnStr + parameterStr
|
||||
} else {
|
||||
returnStr = returnStr + redactionString
|
||||
}
|
||||
|
||||
resetAllParameters(¤tVerb, &originalString, &lineContinuation, &foundGap, ¶meterNext, &redacting, &checkComment)
|
||||
}
|
||||
|
||||
// Loop round getting hte next parameter
|
||||
char, _, err := r.ReadRune()
|
||||
if err == io.EOF {
|
||||
if originalString != "" {
|
||||
returnStr = returnStr + originalString
|
||||
}
|
||||
break
|
||||
} else if err != nil {
|
||||
return returnStr, err
|
||||
}
|
||||
|
||||
/* We need to push forward until we find a non-whitespace, digit or colon character */
|
||||
if newline {
|
||||
startOutput, _ := regexp.MatchString(`[^:0-9\s]`, string(char))
|
||||
if !startOutput {
|
||||
originalString = originalString + string(char)
|
||||
continue MainLoop
|
||||
}
|
||||
newline = false
|
||||
}
|
||||
|
||||
switch char {
|
||||
// Found a line continuation character
|
||||
case '+', '-':
|
||||
lineContinuation = true
|
||||
foundGap = false
|
||||
originalString = originalString + string(char)
|
||||
continue MainLoop
|
||||
|
||||
// Found whitespace/new line
|
||||
case '\n':
|
||||
checkComment = true
|
||||
newline = true
|
||||
fallthrough
|
||||
case '\t', '\r', ' ':
|
||||
if !lineContinuation {
|
||||
foundGap = true
|
||||
}
|
||||
originalString = originalString + string(char)
|
||||
continue MainLoop
|
||||
|
||||
// Found a paramter value
|
||||
case '(':
|
||||
parameterNext = true
|
||||
/* Do not continue as we need to do some checks */
|
||||
|
||||
// Found a comment, parse in a special manner
|
||||
case '*':
|
||||
if checkComment {
|
||||
originalString = originalString + string(char)
|
||||
// Loop round until we find the new line character that marks the end of the comment
|
||||
CommentLoop:
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err == io.EOF {
|
||||
if originalString != "" {
|
||||
returnStr = returnStr + originalString
|
||||
}
|
||||
break MainLoop
|
||||
} else if err != nil {
|
||||
return returnStr, err
|
||||
}
|
||||
originalString = originalString + string(char)
|
||||
|
||||
if char == '\n' {
|
||||
break CommentLoop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Comment has been read and added to original string, go back to start
|
||||
checkComment = true
|
||||
newline = true
|
||||
continue MainLoop
|
||||
}
|
||||
/* Do not continue as we need to do some checks */
|
||||
|
||||
} //end of switch
|
||||
|
||||
checkComment = false
|
||||
|
||||
if lineContinuation {
|
||||
lineContinuation = false
|
||||
}
|
||||
if foundGap || parameterNext {
|
||||
// we've completed an parameter so check whether it is sensitive
|
||||
currentVerb = strings.ToUpper(currentVerb)
|
||||
|
||||
if isSensitiveCommand(currentVerb) {
|
||||
redacting = true
|
||||
}
|
||||
|
||||
// Add the unedited string to the return string
|
||||
returnStr = returnStr + originalString
|
||||
|
||||
//reset some of the parameters
|
||||
originalString = ""
|
||||
currentVerb = ""
|
||||
foundGap = false
|
||||
lineContinuation = false
|
||||
}
|
||||
|
||||
originalString = originalString + string(char)
|
||||
currentVerb = currentVerb + string(char)
|
||||
}
|
||||
|
||||
return returnStr, nil
|
||||
}
|
||||
|
||||
// isSensitiveCommand checks whether the given string contains a sensitive parameter.
|
||||
// We use contains here because we can't determine whether a line continuation seperates
|
||||
// parts of a parameter or two different parameters.
|
||||
func isSensitiveCommand(command string) bool {
|
||||
for _, v := range sensitiveParameters {
|
||||
if strings.Contains(command, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
171
internal/mqscredact/mqscredact_test.go
Normal file
171
internal/mqscredact/mqscredact_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package mqscredact
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const passwordString = passwordHalf1 + passwordHalf2
|
||||
const passwordHalf1 = "hippo"
|
||||
const passwordHalf2 = "123456"
|
||||
|
||||
var testStrings = [...]string{
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordString + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordString + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD ('" + passwordString + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD\t\t('" + passwordString + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) ldappwd('" + passwordString + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LdApPwD('" + passwordString + "')",
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD('" + passwordString + "')",
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD(\"" + passwordString + "\")",
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD ('" + passwordString + "')",
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD\t\t('" + passwordString + "')",
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) password('" + passwordString + "')",
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) pAsSwOrD('" + passwordString + "')",
|
||||
"ALTER QMGR SSLCRYP('" + passwordString + "')",
|
||||
"ALTER QMGR SSLCRYP(\"" + passwordString + "\")",
|
||||
"ALTER QMGR SSLCRYP ('" + passwordString + "')",
|
||||
"ALTER QMGR SSLCRYP\t\t('" + passwordString + "')",
|
||||
"ALTER QMGR sslcryp('" + passwordString + "')",
|
||||
"ALTER QMGR sslCRYP('" + passwordString + "')",
|
||||
|
||||
// Line continuation ones
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\n " + passwordHalf2 + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\n\t" + passwordHalf2 + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\n\t " + passwordHalf2 + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "+\n " + passwordHalf2 + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "+\n\t" + passwordHalf2 + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "+\n\t " + passwordHalf2 + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "-\n" + passwordHalf2 + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('" + passwordHalf1 + "-\n" + passwordHalf2 + "')",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+ \n " + passwordHalf2 + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "+\t\n " + passwordHalf2 + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "- \n" + passwordHalf2 + "\")",
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(\"" + passwordHalf1 + "-\t\n" + passwordHalf2 + "\")",
|
||||
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD(\"" + passwordHalf1 + "+\n " + passwordHalf2 + "\")",
|
||||
|
||||
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "+\n " + passwordHalf2 + "\")",
|
||||
|
||||
//edge cases
|
||||
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "+\n 123+\n 456\")",
|
||||
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "-\n123-\n456\")",
|
||||
|
||||
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\")",
|
||||
"ALTER QMGR SSLCRYP(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\")",
|
||||
|
||||
"ALTER QMGR SSLCRYP + \n (\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\")",
|
||||
"ALTER QMGR SSLCRYP - \n(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\")",
|
||||
|
||||
"ALTER QMGR SSL + \n CRYP(\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\")",
|
||||
"ALTER QMGR SSL - \nCRYP(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\")",
|
||||
|
||||
"ALTER QMGR + \n SSL +\n CRYP(\"" + passwordHalf1 + "+\n 1+\n 2+\n 3+\n 4+\n 5+\n 6\") +\n TEST(1234)",
|
||||
"ALTER QMGR -\nSSL -\nCRYP(\"" + passwordHalf1 + "-\n1-\n2-\n3-\n4-\n5-\n6\") -\nTEST(1234)",
|
||||
|
||||
"ALTER QMGR +\n * COMMENT\n SSL +\n * COMMENT IN MIDDLE\n CRYP('" + passwordString + "')",
|
||||
|
||||
" 1: ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASS+\n : *test comment\n : WORD('" + passwordString + "')",
|
||||
" 2: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD('" + passwordHalf1 + "-\n*commentinmiddle with ' \n" + passwordHalf2 + "')",
|
||||
" 3: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD('" + passwordHalf1 + "-\n*commentinmiddle with ') \n" + passwordHalf2 + "')",
|
||||
}
|
||||
|
||||
var expected = [...]string{
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD " + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD\t\t" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) ldappwd" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LdApPwD" + redactionString,
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD" + redactionString,
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD" + redactionString,
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD " + redactionString,
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD\t\t" + redactionString,
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) password" + redactionString,
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) pAsSwOrD" + redactionString,
|
||||
"ALTER QMGR SSLCRYP" + redactionString,
|
||||
"ALTER QMGR SSLCRYP" + redactionString,
|
||||
"ALTER QMGR SSLCRYP " + redactionString,
|
||||
"ALTER QMGR SSLCRYP\t\t" + redactionString,
|
||||
"ALTER QMGR sslcryp" + redactionString,
|
||||
"ALTER QMGR sslCRYP" + redactionString,
|
||||
|
||||
// Line continuation ones
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
"DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD" + redactionString,
|
||||
|
||||
"DEFINE CHANNEL(CHL) CHLTYPE(SOMETHING) PASSWORD" + redactionString,
|
||||
|
||||
"ALTER QMGR SSLCRYP" + redactionString,
|
||||
|
||||
//edge cases
|
||||
"ALTER QMGR SSLCRYP" + redactionString,
|
||||
"ALTER QMGR SSLCRYP" + redactionString,
|
||||
|
||||
"ALTER QMGR SSLCRYP" + redactionString,
|
||||
"ALTER QMGR SSLCRYP" + redactionString,
|
||||
|
||||
"ALTER QMGR SSLCRYP + \n \t " + redactionString,
|
||||
"ALTER QMGR SSLCRYP - \n " + redactionString,
|
||||
|
||||
"ALTER QMGR SSL + \n CRYP" + redactionString,
|
||||
"ALTER QMGR SSL - \nCRYP" + redactionString,
|
||||
|
||||
"ALTER QMGR + \n SSL +\n CRYP" + redactionString + " +\n TEST(1234)",
|
||||
"ALTER QMGR -\nSSL -\nCRYP" + redactionString + " -\nTEST(1234)",
|
||||
|
||||
"ALTER QMGR +\n * COMMENT\n SSL +\n * COMMENT IN MIDDLE\n CRYP" + redactionString,
|
||||
|
||||
"1: ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASS+\n : *test comment\n : WORD" + redactionString,
|
||||
"2: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD" + redactionString,
|
||||
"3: ALTER CHANNEL(TEST3) CHLTYPE(SDR) PASSWORD" + redactionString,
|
||||
}
|
||||
|
||||
// Returns true if the 2 strings are equal ignoring whitespace characters
|
||||
func compareIgnoreWhiteSpace(str1, str2 string) bool {
|
||||
whiteSpaces := [...]string{" ", "\t", "\n", "\r"}
|
||||
for _, w := range whiteSpaces {
|
||||
str1 = strings.Replace(str1, w, "", -1)
|
||||
str2 = strings.Replace(str2, w, "", -1)
|
||||
}
|
||||
|
||||
return str1 == str2
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
for i, v := range testStrings {
|
||||
back, _ := Redact(v)
|
||||
if strings.Contains(back, passwordHalf1) || strings.Contains(back, passwordHalf2) || strings.Contains(back, passwordString) {
|
||||
t.Errorf("MAJOR FAIL[%d]: Found an instance of the password. ", i)
|
||||
}
|
||||
|
||||
if !compareIgnoreWhiteSpace(back, expected[i]) {
|
||||
t.Errorf("FAIL[%d]:\nGave :%s\nexpected:%s\ngot :%s", i, v, expected[i], back)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -13,7 +13,9 @@ 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
// Package mqtemplate contains code to process template files
|
||||
package mqtemplate
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -21,11 +23,12 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
"github.com/ibm-messaging/mq-container/internal/logger"
|
||||
)
|
||||
|
||||
// processTemplateFile takes a Go templateFile, and processes it with the
|
||||
// ProcessTemplateFile takes a Go templateFile, and processes it with the
|
||||
// supplied data, writing to destFile
|
||||
func processTemplateFile(templateFile, destFile string, data interface{}) error {
|
||||
func ProcessTemplateFile(templateFile, destFile string, data interface{}, log *logger.Logger) error {
|
||||
// Re-configure channel if app password not set
|
||||
t, err := template.ParseFiles(templateFile)
|
||||
if err != nil {
|
||||
@@ -36,7 +39,12 @@ func processTemplateFile(templateFile, destFile string, data interface{}) error
|
||||
_, err = os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
os.MkdirAll(dir, 0660)
|
||||
// #nosec G301
|
||||
err = os.MkdirAll(dir, 0770)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
mqmUID, mqmGID, err := command.LookupMQM()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -51,6 +59,7 @@ func processTemplateFile(templateFile, destFile string, data interface{}) error
|
||||
return err
|
||||
}
|
||||
}
|
||||
// #nosec G302
|
||||
f, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, 0660)
|
||||
defer f.Close()
|
||||
err = t.Execute(f, data)
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,9 @@ package ready
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
)
|
||||
|
||||
const fileName string = "/run/runmqserver/ready"
|
||||
@@ -62,3 +65,24 @@ func Check() (bool, error) {
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// IsRunningAsActiveQM returns true if the queue manager is running in active mode
|
||||
func IsRunningAsActiveQM(name string) (bool, error) {
|
||||
return isRunningQM(name, "(RUNNING)")
|
||||
}
|
||||
|
||||
// IsRunningAsStandbyQM returns true if the queue manager is running in standby mode
|
||||
func IsRunningAsStandbyQM(name string) (bool, error) {
|
||||
return isRunningQM(name, "(RUNNING AS STANDBY)")
|
||||
}
|
||||
|
||||
func isRunningQM(name string, status string) (bool, error) {
|
||||
out, _, err := command.Run("dspmq", "-n", "-m", name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if strings.Contains(string(out), status) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
610
internal/tls/tls.go
Normal file
610
internal/tls/tls.go
Normal file
@@ -0,0 +1,610 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
pwr "math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/filecheck"
|
||||
"github.com/ibm-messaging/mq-container/internal/keystore"
|
||||
pkcs "software.sslmate.com/src/go-pkcs12"
|
||||
)
|
||||
|
||||
// IntegrationDefaultLabel is the default certificate label used by Cloud Integration Platform
|
||||
const IntegrationDefaultLabel = "default"
|
||||
|
||||
// P12TrustStoreName is the name of the PKCS#12 truststore used by the webconsole
|
||||
const P12TrustStoreName = "trust.p12"
|
||||
|
||||
// CMSKeyStoreName is the name of the CMS Keystore used by the queue manager
|
||||
const CMSKeyStoreName = "key.kdb"
|
||||
|
||||
type KeyStoreData struct {
|
||||
Keystore *keystore.KeyStore
|
||||
Password string
|
||||
TrustedCerts []*pem.Block
|
||||
KnownFingerPrints []string
|
||||
KeyLabels []string
|
||||
}
|
||||
|
||||
type P12KeyFiles struct {
|
||||
Keystores []string
|
||||
Password string
|
||||
}
|
||||
|
||||
func getCertFingerPrint(block *pem.Block) (string, error) {
|
||||
// Add to future truststore and known certs (if not already there)
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse x509 certificate: %v", err)
|
||||
}
|
||||
sha512Sum := sha512.Sum512(cert.Raw)
|
||||
sha512str := string(sha512Sum[:])
|
||||
|
||||
return sha512str, nil
|
||||
}
|
||||
|
||||
// Add to Keystores known certs (if not already there) and add to the
|
||||
// Keystore if "addToKeystore" is true.
|
||||
func addCertToKeyData(block *pem.Block, keyData *KeyStoreData, addToKeystore bool) error {
|
||||
sha512str, err := getCertFingerPrint(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
known := false
|
||||
for _, fingerprint := range keyData.KnownFingerPrints {
|
||||
if fingerprint == sha512str {
|
||||
known = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !known {
|
||||
// Sometimes we don't want to add to the keystore trust here.
|
||||
// For example if it will be imported with the key later.
|
||||
if addToKeystore {
|
||||
keyData.TrustedCerts = append(keyData.TrustedCerts, block)
|
||||
}
|
||||
keyData.KnownFingerPrints = append(keyData.KnownFingerPrints, sha512str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generates a random 12 character password from the characters a-z, A-Z, 0-9.
|
||||
func generateRandomPassword() string {
|
||||
pwr.Seed(time.Now().Unix())
|
||||
validChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
validcharArray := []byte(validChars)
|
||||
password := ""
|
||||
for i := 0; i < 12; i++ {
|
||||
password = password + string(validcharArray[pwr.Intn(len(validcharArray))])
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
// Creates the PKCS#12 Truststore and the CMS Keystore.
|
||||
func generateAllStores(dir string) (KeyStoreData, KeyStoreData, error) {
|
||||
var cmsKeystore, p12TrustStore KeyStoreData
|
||||
pw := generateRandomPassword()
|
||||
|
||||
cmsKeystore.Password = pw
|
||||
p12TrustStore.Password = pw
|
||||
|
||||
// Create the keystore Directory (if it doesn't already exist)
|
||||
// #nosec G301 - write group permissions are required
|
||||
err := os.MkdirAll(dir, 0770)
|
||||
if err != nil {
|
||||
return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create keystore directory: %v", err)
|
||||
}
|
||||
|
||||
p12TrustStore.Keystore = keystore.NewPKCS12KeyStore(filepath.Join(dir, P12TrustStoreName), p12TrustStore.Password)
|
||||
err = p12TrustStore.Keystore.Create()
|
||||
if err != nil {
|
||||
return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create PKCS#12 TrustStore: %v", err)
|
||||
}
|
||||
|
||||
cmsKeystore.Keystore = keystore.NewCMSKeyStore(filepath.Join(dir, CMSKeyStoreName), cmsKeystore.Password)
|
||||
err = cmsKeystore.Keystore.Create()
|
||||
if err != nil {
|
||||
return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create CMS KeyStore: %v", err)
|
||||
}
|
||||
|
||||
return cmsKeystore, p12TrustStore, nil
|
||||
}
|
||||
|
||||
// processKeys walks through the keyDir directory and imports any keys it finds to individual PKCS#12 keystores
|
||||
// and the CMS KeyStore. The label it uses is the name of the directory if finds the keys in.
|
||||
func processKeys(keyDir, outputDir string, cmsKeyDB, p12TrustDB *KeyStoreData) (string, P12KeyFiles, error) {
|
||||
var p12s P12KeyFiles
|
||||
var firstLabel string
|
||||
|
||||
pwToUse := cmsKeyDB.Password
|
||||
p12s.Password = pwToUse
|
||||
trustStoreReserveredName := P12TrustStoreName[0 : len(P12TrustStoreName)-len(filepath.Ext(P12TrustStoreName))]
|
||||
keyList, err := ioutil.ReadDir(keyDir)
|
||||
|
||||
if err == nil && len(keyList) > 0 {
|
||||
// Found some keys, verify the contents
|
||||
for _, key := range keyList {
|
||||
keys, _ := ioutil.ReadDir(filepath.Join(keyDir, key.Name()))
|
||||
keyLabel := key.Name()
|
||||
if keyLabel == trustStoreReserveredName {
|
||||
return firstLabel, p12s, fmt.Errorf("Found key with same label set as same name as truststore(%s). This is not allowed", trustStoreReserveredName)
|
||||
}
|
||||
|
||||
keyfilename := ""
|
||||
var keyfile interface{}
|
||||
var certFile *x509.Certificate
|
||||
var caFile []*x509.Certificate
|
||||
|
||||
// find the keyfile name
|
||||
for _, a := range keys {
|
||||
if strings.HasSuffix(a.Name(), ".key") {
|
||||
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
|
||||
keyFile, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name()))
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not read keyfile %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
|
||||
}
|
||||
block, _ := pem.Decode(keyFile)
|
||||
if block == nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not decode keyfile %s: pem.Decode returned nil", filepath.Join(keyDir, key.Name(), a.Name()))
|
||||
}
|
||||
|
||||
//Test whether it is PKCS1
|
||||
keyfile, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
// Before we fail check whether it is PKCS8
|
||||
keyfile, err = x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
fmt.Printf("key %s ParsePKCS1/8PrivateKey ERR: %v\n", filepath.Join(keyDir, key.Name(), a.Name()), err)
|
||||
return firstLabel, p12s, err
|
||||
}
|
||||
//It was PKCS8 afterall
|
||||
}
|
||||
keyfilename = a.Name()
|
||||
}
|
||||
}
|
||||
if keyfile == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find out what the keyfile was called without the extension
|
||||
prefix := keyfilename[0 : len(keyfilename)-len(filepath.Ext(keyfilename))]
|
||||
|
||||
for _, a := range keys {
|
||||
if strings.HasSuffix(a.Name(), ".key") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(a.Name(), prefix) && strings.HasSuffix(a.Name(), ".crt") {
|
||||
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
|
||||
cert, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name()))
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not read file %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
|
||||
}
|
||||
block, _ := pem.Decode(cert)
|
||||
if block == nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not decode certificate %s: pem.Decode returned nil", filepath.Join(keyDir, key.Name(), a.Name()))
|
||||
}
|
||||
certFile, err = x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not parse certificate %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
|
||||
}
|
||||
// Add to the dup list for the CMS keystore but not the PKCS#12 Truststore
|
||||
err = addCertToKeyData(block, cmsKeyDB, false)
|
||||
|
||||
} else if strings.HasSuffix(a.Name(), ".crt") {
|
||||
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
|
||||
remainder, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name()))
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not read file %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
|
||||
}
|
||||
|
||||
for string(remainder) != "" {
|
||||
var block *pem.Block
|
||||
block, remainder = pem.Decode(remainder)
|
||||
// If we can't decode the CA certificate then just exit.
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Add to the dup list for the CMS keystore
|
||||
err = addCertToKeyData(block, cmsKeyDB, false)
|
||||
|
||||
// Add to the p12 truststore
|
||||
err = addCertToKeyData(block, p12TrustDB, true)
|
||||
|
||||
caCert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not parse CA certificate %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err)
|
||||
}
|
||||
|
||||
caFile = append(caFile, caCert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create p12 keystore
|
||||
file, err := pkcs.Encode(rand.Reader, keyfile, certFile, caFile, pwToUse)
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not encode PKCS#12 Keystore %s: %v", keyLabel+".p12", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(outputDir, keyLabel+".p12"), file, 0644)
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not write PKCS#12 Keystore %s: %v", filepath.Join(outputDir, keyLabel+".p12"), err)
|
||||
}
|
||||
|
||||
p12s.Keystores = append(p12s.Keystores, keyLabel+".p12")
|
||||
|
||||
// Add to the CMS keystore
|
||||
err = cmsKeyDB.Keystore.Import(filepath.Join(outputDir, keyLabel+".p12"), pwToUse)
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not import keys from %s into CMS Keystore: %v", filepath.Join(outputDir, keyLabel+".p12"), err)
|
||||
}
|
||||
|
||||
// Relabel it
|
||||
allLabels, err := cmsKeyDB.Keystore.GetCertificateLabels()
|
||||
if err != nil {
|
||||
return firstLabel, p12s, fmt.Errorf("Could not list keys in CMS Keystore: %v", err)
|
||||
}
|
||||
relabelled := false
|
||||
for _, cl := range allLabels {
|
||||
found := false
|
||||
for _, kl := range cmsKeyDB.KeyLabels {
|
||||
if strings.Trim(cl, "\"") == kl {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// This is the one to rename
|
||||
err = cmsKeyDB.Keystore.RenameCertificate(strings.Trim(cl, "\""), keyLabel)
|
||||
if err != nil {
|
||||
return firstLabel, p12s, err
|
||||
}
|
||||
relabelled = true
|
||||
cmsKeyDB.KeyLabels = append(cmsKeyDB.KeyLabels, keyLabel)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !relabelled {
|
||||
return firstLabel, p12s, fmt.Errorf("Unable to find the added key for %s in CMS keystore", keyLabel)
|
||||
}
|
||||
|
||||
// First key found so mark it as the one to use with the queue manager.
|
||||
if firstLabel == "" {
|
||||
firstLabel = keyLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
return firstLabel, p12s, nil
|
||||
}
|
||||
|
||||
// processTrustCertificates walks through the trustDir directory and adds any certificates it finds
|
||||
// to the PKCS#12 Truststore and the CMS KeyStore as long as has not already been added.
|
||||
func processTrustCertificates(trustDir string, cmsKeyDB, p12TrustDB *KeyStoreData) error {
|
||||
certList, err := ioutil.ReadDir(trustDir)
|
||||
if err == nil && len(certList) > 0 {
|
||||
// Found some keys, verify the contents
|
||||
for _, cert := range certList {
|
||||
certs, _ := ioutil.ReadDir(filepath.Join(trustDir, cert.Name()))
|
||||
for _, a := range certs {
|
||||
if strings.HasSuffix(a.Name(), ".crt") {
|
||||
// #nosec G304 - filename variable is derived from contents of 'trustDir' which is a defined constant
|
||||
remainder, err := ioutil.ReadFile(filepath.Join(trustDir, cert.Name(), a.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not read file %s: %v", filepath.Join(trustDir, cert.Name(), a.Name()), err)
|
||||
}
|
||||
|
||||
for string(remainder) != "" {
|
||||
var block *pem.Block
|
||||
block, remainder = pem.Decode(remainder)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Add to the CMS keystore
|
||||
err = addCertToKeyData(block, cmsKeyDB, true)
|
||||
|
||||
// Add to the p12 truststore
|
||||
err = addCertToKeyData(block, p12TrustDB, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// We've potentially created two lists of certificates to import. Add them both to relevant Truststores
|
||||
if len(p12TrustDB.TrustedCerts) > 0 {
|
||||
// Do P12 TrustStore first
|
||||
temporaryPemFile := filepath.Join("/tmp", "trust.pem")
|
||||
_, err := os.Stat(temporaryPemFile)
|
||||
if err == nil {
|
||||
err = os.Remove(temporaryPemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not remove file %v: %v", temporaryPemFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = writeCertsToFile(temporaryPemFile, p12TrustDB.TrustedCerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p12TrustDB.Keystore.AddNoLabel(temporaryPemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not add certificates to PKCS#12 Truststore: %v", err)
|
||||
}
|
||||
|
||||
// We need to relabel everything because liberty doesn't play nicely with autolabelled certs
|
||||
allCerts, err := p12TrustDB.Keystore.ListAllCertificates()
|
||||
if err != nil || len(allCerts) <= 0 {
|
||||
return fmt.Errorf("Could not get any certificates from PKCS#12 Truststore: %v", err)
|
||||
}
|
||||
|
||||
for i, cert := range allCerts {
|
||||
cert = strings.Trim(cert, "\"")
|
||||
cert = strings.TrimSpace(cert)
|
||||
newLabel := fmt.Sprintf("Trust%d", i)
|
||||
|
||||
err = p12TrustDB.Keystore.RenameCertificate(cert, newLabel)
|
||||
if err != nil || len(allCerts) <= 0 {
|
||||
return fmt.Errorf("Could not rename certificate %s to %s in PKCS#12 Truststore: %v", cert, newLabel, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmsKeyDB.TrustedCerts) > 0 {
|
||||
// Now the CMS Keystore
|
||||
temporaryPemFile := filepath.Join("/tmp", "cmsTrust.pem")
|
||||
_, err := os.Stat(temporaryPemFile)
|
||||
if err == nil {
|
||||
err = os.Remove(temporaryPemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not remove file %v: %v", temporaryPemFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = writeCertsToFile(temporaryPemFile, cmsKeyDB.TrustedCerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmsKeyDB.Keystore.AddNoLabel(temporaryPemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not add certificates to CMS keystore: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Writes a given list of certificates to a file.
|
||||
func writeCertsToFile(file string, certs []*pem.Block) error {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writeCertsToFile: Could not create file %s: %v", file, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
w := bufio.NewWriter(f)
|
||||
|
||||
for i, c := range certs {
|
||||
err := pem.Encode(w, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writeCertsToFile: Could not encode certificate number %d: %v", i, err)
|
||||
}
|
||||
err = w.Flush()
|
||||
if err != nil {
|
||||
return fmt.Errorf("writeCertsToFile: Failed to write to file %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureTLSKeystores sets up the TLS Trust and Keystores for use
|
||||
func ConfigureTLSKeystores(keyDir, certDir, outputDir string) (string, KeyStoreData, KeyStoreData, P12KeyFiles, error) {
|
||||
var returnLabel, label string
|
||||
var cmsKeyDB, p12TrustDB KeyStoreData
|
||||
var keyFiles P12KeyFiles
|
||||
var err error
|
||||
|
||||
cmsKeyDB, p12TrustDB, err = generateAllStores(outputDir)
|
||||
if err != nil {
|
||||
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
|
||||
}
|
||||
|
||||
returnLabel, err = expandOldTLSVariable(keyDir, outputDir, &cmsKeyDB, &p12TrustDB)
|
||||
if err != nil {
|
||||
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
|
||||
}
|
||||
|
||||
label, keyFiles, err = processKeys(keyDir, outputDir, &cmsKeyDB, &p12TrustDB)
|
||||
if err != nil {
|
||||
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
|
||||
}
|
||||
if returnLabel == "" {
|
||||
returnLabel = label
|
||||
}
|
||||
|
||||
err = processTrustCertificates(certDir, &cmsKeyDB, &p12TrustDB)
|
||||
if err != nil {
|
||||
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
|
||||
}
|
||||
|
||||
return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err
|
||||
}
|
||||
|
||||
// This function supports the old mechanism of importing certificates supplied by the MQ_TLS_KEYSTORE envvar
|
||||
func expandOldTLSVariable(keyDir, outputDir string, cmsKeyDB, p12TrustDB *KeyStoreData) (string, error) {
|
||||
// TODO: Change this or find a way to set it
|
||||
outputDirName := "acopiedcertificate"
|
||||
|
||||
// Check whether the old variable is set. If not exit quietly
|
||||
keyfile := os.Getenv("MQ_TLS_KEYSTORE")
|
||||
if keyfile == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// There is a file to read and process
|
||||
keyfilepw := os.Getenv("MQ_TLS_PASSPHRASE")
|
||||
|
||||
if !strings.HasSuffix(keyfile, ".p12") {
|
||||
return "", fmt.Errorf("MQ_TLS_KEYSTORE (%s) does not point to a PKCS#12 file ending with the suffix .p12", keyfile)
|
||||
}
|
||||
|
||||
_, err := os.Stat(keyfile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("File %s referenced by MQ_TLS_KEYSTORE does not exist", keyfile)
|
||||
}
|
||||
|
||||
err = filecheck.CheckFileSource(keyfile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("File %s referenced by MQ_TLS_KEYSTORE is invalid: %v", keyfile, err)
|
||||
}
|
||||
|
||||
// #nosec G304 - filename variable 'keyfile' is checked above to ensure it is valid
|
||||
readkey, err := ioutil.ReadFile(keyfile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read %s: %v", keyfile, err)
|
||||
}
|
||||
|
||||
// File has been checked and read, decode it.
|
||||
pk, cert, cas, err := pkcs.DecodeChain(readkey, keyfilepw)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to decode %s: %v", keyfile, err)
|
||||
}
|
||||
|
||||
// Find a directory name that doesn't exist
|
||||
for {
|
||||
_, err := os.Stat(filepath.Join(keyDir, outputDirName))
|
||||
if err == nil {
|
||||
outputDirName = outputDirName + "0"
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//Bceause they supplied this certificate using the old method we should use this for qm & webconsole
|
||||
overrideLabel := outputDirName
|
||||
|
||||
// Write out the certificate for the private key
|
||||
if cert != nil {
|
||||
block := pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Headers: nil,
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
err = addCertToKeyData(&block, cmsKeyDB, false)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("expandOldTLSVariable: Failed to add cert to CMS Keystore duplicate list: %v", err)
|
||||
}
|
||||
err = addCertToKeyData(&block, p12TrustDB, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("expandOldTLSVariable: Failed to add cert to P12 Truststore duplicate list: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// now write out all the ca certificates
|
||||
if cas != nil || len(cas) > 0 {
|
||||
for i, c := range cas {
|
||||
block := pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Headers: nil,
|
||||
Bytes: c.Raw,
|
||||
}
|
||||
|
||||
// Add to the dup list for the CMS keystore
|
||||
err = addCertToKeyData(&block, cmsKeyDB, false)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("expandOldTLSVariable: Failed to add CA cert %d to CMS Keystore duplicate list: %v", i, err)
|
||||
}
|
||||
|
||||
// Add to the p12 truststore
|
||||
err = addCertToKeyData(&block, p12TrustDB, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("expandOldTLSVariable: Failed to add CA cert %d to P12 Truststore duplicate list: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we've handled the certificates copy the keystore into place
|
||||
destination := filepath.Join(outputDir, outputDirName+".p12")
|
||||
|
||||
// Create p12 keystore
|
||||
file, err := pkcs.Encode(rand.Reader, pk, cert, cas, p12TrustDB.Password)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to re-encode p12 keystore: %v", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(destination, file, 0644)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to write p12 keystore: %v", err)
|
||||
}
|
||||
|
||||
// Add to the CMS keystore
|
||||
err = cmsKeyDB.Keystore.Import(destination, p12TrustDB.Password)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to import p12 keystore %s: %v", destination, err)
|
||||
}
|
||||
|
||||
if pk != nil {
|
||||
// Relabel the key
|
||||
allLabels, err := cmsKeyDB.Keystore.GetCertificateLabels()
|
||||
if err != nil {
|
||||
fmt.Printf("cms GetCertificateLabels: %v\n", err)
|
||||
return "", err
|
||||
}
|
||||
relabelled := false
|
||||
for _, cl := range allLabels {
|
||||
found := false
|
||||
for _, kl := range cmsKeyDB.KeyLabels {
|
||||
if strings.Trim(cl, "\"") == kl {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// This is the one to rename
|
||||
err = cmsKeyDB.Keystore.RenameCertificate(strings.Trim(cl, "\""), outputDirName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
relabelled = true
|
||||
cmsKeyDB.KeyLabels = append(cmsKeyDB.KeyLabels, outputDirName)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !relabelled {
|
||||
return "", fmt.Errorf("Unable to find the added key in CMS keystore")
|
||||
}
|
||||
}
|
||||
|
||||
return overrideLabel, nil
|
||||
}
|
||||
81
internal/user/user.go
Normal file
81
internal/user/user.go
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/command"
|
||||
)
|
||||
|
||||
// User holds information on primary and supplemental OS groups
|
||||
type User struct {
|
||||
UID string
|
||||
Name string
|
||||
PrimaryGID string
|
||||
SupplementalGID []string
|
||||
}
|
||||
|
||||
// GetUser returns the current user and group information
|
||||
func GetUser() (User, error) {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
g, err := getCurrentUserGroups()
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
if err != nil && len(g) == 0 {
|
||||
return User{
|
||||
UID: u.Uid,
|
||||
Name: u.Name,
|
||||
PrimaryGID: u.Gid,
|
||||
SupplementalGID: []string{},
|
||||
}, nil
|
||||
}
|
||||
// Look for the primary group in the list of group IDs
|
||||
for i, v := range g {
|
||||
if v == u.Gid {
|
||||
// Remove the element from the slice
|
||||
g = append(g[:i], g[i+1:]...)
|
||||
}
|
||||
}
|
||||
return User{
|
||||
UID: u.Uid,
|
||||
Name: u.Name,
|
||||
PrimaryGID: u.Gid,
|
||||
SupplementalGID: g,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getCurrentUserGroups() ([]string, error) {
|
||||
var nilArray []string
|
||||
out, _, err := command.Run("id", "--groups")
|
||||
if err != nil {
|
||||
return nilArray, err
|
||||
}
|
||||
|
||||
out = strings.TrimSpace(out)
|
||||
if out == "" {
|
||||
return nilArray, fmt.Errorf("Unable to determine groups for current user")
|
||||
}
|
||||
|
||||
groups := strings.Split(out, " ")
|
||||
return groups, nil
|
||||
}
|
||||
@@ -12,17 +12,17 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
image: ibmcom/mq:9
|
||||
image: ibmcom/mq:9.1.1.0
|
||||
manifests:
|
||||
- image: ibmcom/mq:9.1.0.0-x86_64
|
||||
- image: ibmcom/mq:9.1.1.0-x86_64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.0.0-ppc64le
|
||||
- image: ibmcom/mq:9.1.1.0-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.0.0-s390x
|
||||
- image: ibmcom/mq:9.1.1.0-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
28
manifests/dockerhub/manifest-9.1.2-UBI.yaml
Normal file
28
manifests/dockerhub/manifest-9.1.2-UBI.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# © Copyright IBM Corporation 2019
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
image: ibmcom/mq:9.1.2.0-UBI
|
||||
manifests:
|
||||
- image: ibmcom/mq:9.1.2.0-UBI-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.2.0-UBI-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.2.0-UBI-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2015, 2017
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -12,7 +12,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM mqadvanced
|
||||
RUN useradd alice -G mqm && \
|
||||
echo alice:passw0rd | chpasswd
|
||||
COPY *.mqsc /etc/mqm/
|
||||
image: ibmcom/mq:9.1.2.0
|
||||
manifests:
|
||||
- image: ibmcom/mq:9.1.2.0-x86_64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.2.0-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.2.0-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
|
||||
28
manifests/dockerhub/manifest-9.1.3.yaml
Normal file
28
manifests/dockerhub/manifest-9.1.3.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# © Copyright IBM Corporation 2019
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
image: ibmcom/mq:9.1.3.0-r2
|
||||
manifests:
|
||||
- image: ibmcom/mq:9.1.3.0-r2-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.3.0-r2-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.3.0-r2-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2018
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -14,16 +14,15 @@
|
||||
|
||||
image: ibmcom/mq:latest
|
||||
manifests:
|
||||
- image: ibmcom/mq:9.1.0.0-x86_64
|
||||
- image: ibmcom/mq:9.1.3.0-r2-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.0.0-ppc64le
|
||||
- image: ibmcom/mq:9.1.3.0-r2-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcom/mq:9.1.0.0-s390x
|
||||
- image: ibmcom/mq:9.1.3.0-r2-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
|
||||
|
||||
@@ -12,22 +12,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG BASE_IMAGE=mq-sdk:9.0.5.0-x86_64-ubuntu-16.04
|
||||
image: ibmcorp/mqadvanced-server-dev:9.1.1.0
|
||||
manifests:
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.1.0-x86_64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.1.0-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.1.0-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
COPY incubating/mq-golang-sdk/install-golang.sh /usr/local/bin
|
||||
|
||||
ENV GO_VERSION=1.10
|
||||
|
||||
ENV PATH="${PATH}:/usr/lib/go-${GO_VERSION}/bin:/go/bin:/usr/local/go/bin" \
|
||||
CGO_CFLAGS="-I/opt/mqm/inc/" \
|
||||
CGO_LDFLAGS_ALLOW="-Wl,-rpath.*" \
|
||||
GOPATH="/go"
|
||||
|
||||
# Install the Go compiler and Git
|
||||
RUN chmod +x /usr/local/bin/install-golang.sh \
|
||||
&& sleep 1 \
|
||||
&& install-golang.sh
|
||||
|
||||
WORKDIR $GOPATH
|
||||
28
manifests/dockerstore/manifest-9.1.2-UBI.yaml
Normal file
28
manifests/dockerstore/manifest-9.1.2-UBI.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# © Copyright IBM Corporation 2019
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI
|
||||
manifests:
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-UBI-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
29
manifests/dockerstore/manifest-9.1.2.yaml
Normal file
29
manifests/dockerstore/manifest-9.1.2.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
image: ibmcorp/mqadvanced-server-dev:9.1.2.0
|
||||
manifests:
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-x86_64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.2.0-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
|
||||
28
manifests/dockerstore/manifest-9.1.3.yaml
Normal file
28
manifests/dockerstore/manifest-9.1.3.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# © Copyright IBM Corporation 2019
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
image: ibmcorp/mqadvanced-server-dev:9.1.3.0-r2
|
||||
manifests:
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.3.0-r2-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.3.0-r2-ppc64le
|
||||
platform:
|
||||
architecture: ppc64le
|
||||
os: linux
|
||||
- image: ibmcorp/mqadvanced-server-dev:9.1.3.0-r2-s390x
|
||||
platform:
|
||||
architecture: s390x
|
||||
os: linux
|
||||
@@ -1,19 +0,0 @@
|
||||
* © Copyright IBM Corporation 2017
|
||||
*
|
||||
* Licensed 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.
|
||||
|
||||
DEFINE CHANNEL(PASSWORD.SVRCONN) CHLTYPE(SVRCONN) REPLACE
|
||||
SET CHLAUTH(PASSWORD.SVRCONN) TYPE(BLOCKUSER) USERLIST('nobody') DESCR('Allow privileged users on this channel')
|
||||
SET CHLAUTH('*') TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(NOACCESS) DESCR('BackStop rule')
|
||||
SET CHLAUTH(PASSWORD.SVRCONN) TYPE(ADDRESSMAP) ADDRESS('*') USERSRC(CHANNEL) CHCKCLNT(REQUIRED)
|
||||
ALTER AUTHINFO(SYSTEM.DEFAULT.AUTHINFO.IDPWOS) AUTHTYPE(IDPWOS) ADOPTCTX(YES)
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/docker/go-connections"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// +build mqdev
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -33,14 +33,16 @@ import (
|
||||
// Note: This test requires a separate container image to be available for the JMS tests.
|
||||
func TestDevGoldenPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
qm := "qm1"
|
||||
containerConfig := container.Config{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=qm1",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
},
|
||||
}
|
||||
id := runContainerWithPorts(t, cli, &containerConfig, []int{9443})
|
||||
@@ -49,7 +51,13 @@ func TestDevGoldenPath(t *testing.T) {
|
||||
waitForWebReady(t, cli, id, insecureTLSConfig)
|
||||
t.Run("JMS", func(t *testing.T) {
|
||||
// Run the JMS tests, with no password specified
|
||||
runJMSTests(t, cli, id, false, "app", "")
|
||||
runJMSTests(t, cli, id, false, "app", defaultAppPasswordOS)
|
||||
})
|
||||
t.Run("REST admin", func(t *testing.T) {
|
||||
testRESTAdmin(t, cli, id, insecureTLSConfig)
|
||||
})
|
||||
t.Run("REST messaging", func(t *testing.T) {
|
||||
testRESTMessaging(t, cli, id, insecureTLSConfig, qm, "app", defaultAppPasswordWeb)
|
||||
})
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
@@ -59,16 +67,20 @@ func TestDevGoldenPath(t *testing.T) {
|
||||
// Note: This test requires a separate container image to be available for the JMS tests
|
||||
func TestDevSecure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const tlsPassPhrase string = "passw0rd"
|
||||
qm := "qm1"
|
||||
appPassword := "differentPassw0rd"
|
||||
containerConfig := container.Config{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=qm1",
|
||||
"MQ_APP_PASSWORD=" + devAppPassword,
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"MQ_APP_PASSWORD=" + appPassword,
|
||||
"MQ_TLS_KEYSTORE=/var/tls/server.p12",
|
||||
"MQ_TLS_PASSPHRASE=" + tlsPassPhrase,
|
||||
"DEBUG=1",
|
||||
@@ -100,13 +112,24 @@ func TestDevSecure(t *testing.T) {
|
||||
waitForReady(t, cli, ctr.ID)
|
||||
cert := filepath.Join(tlsDir(t, true), "server.crt")
|
||||
waitForWebReady(t, cli, ctr.ID, createTLSConfig(t, cert, tlsPassPhrase))
|
||||
runJMSTests(t, cli, ctr.ID, true, "app", devAppPassword)
|
||||
|
||||
t.Run("JMS", func(t *testing.T) {
|
||||
runJMSTests(t, cli, ctr.ID, true, "app", appPassword)
|
||||
})
|
||||
t.Run("REST admin", func(t *testing.T) {
|
||||
testRESTAdmin(t, cli, ctr.ID, insecureTLSConfig)
|
||||
})
|
||||
t.Run("REST messaging", func(t *testing.T) {
|
||||
testRESTMessaging(t, cli, ctr.ID, insecureTLSConfig, qm, "app", appPassword)
|
||||
})
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ctr.ID)
|
||||
}
|
||||
|
||||
func TestDevWebDisabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -115,7 +138,7 @@ func TestDevWebDisabled(t *testing.T) {
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=qm1",
|
||||
"MQ_DISABLE_WEB_CONSOLE=true",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
},
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
@@ -129,7 +152,7 @@ func TestDevWebDisabled(t *testing.T) {
|
||||
})
|
||||
t.Run("JMS", func(t *testing.T) {
|
||||
// Run the JMS tests, with no password specified
|
||||
runJMSTests(t, cli, id, false, "app", "")
|
||||
runJMSTests(t, cli, id, false, "app", defaultAppPasswordOS)
|
||||
})
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
@@ -137,6 +160,7 @@ func TestDevWebDisabled(t *testing.T) {
|
||||
|
||||
func TestDevConfigDisabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// +build mqdev
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,12 +18,15 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -34,8 +37,9 @@ import (
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
const devAdminPassword string = "passw0rd"
|
||||
const devAppPassword string = "passw0rd"
|
||||
const defaultAdminPassword string = "passw0rd"
|
||||
const defaultAppPasswordOS string = ""
|
||||
const defaultAppPasswordWeb string = "passw0rd"
|
||||
|
||||
// Disable TLS verification (server uses a self-signed certificate by default,
|
||||
// so verification isn't useful anyway)
|
||||
@@ -58,7 +62,7 @@ func waitForWebReady(t *testing.T, cli *client.Client, ID string, tlsConfig *tls
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req.SetBasicAuth("admin", devAdminPassword)
|
||||
req.SetBasicAuth("admin", defaultAdminPassword)
|
||||
resp, err := httpClient.Do(req.WithContext(ctx))
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
t.Log("MQ web server is ready")
|
||||
@@ -83,6 +87,7 @@ func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, pa
|
||||
"MQ_PORT_1414_TCP_ADDR=" + getIPAddress(t, cli, ID),
|
||||
"MQ_USERNAME=" + user,
|
||||
"MQ_CHANNEL=DEV.APP.SVRCONN",
|
||||
"IBMJRE=" + os.Getenv("IBMJRE"),
|
||||
},
|
||||
Image: imageNameDevJMS(),
|
||||
}
|
||||
@@ -109,7 +114,7 @@ func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, pa
|
||||
t.Fatal(err)
|
||||
}
|
||||
startContainer(t, cli, ctr.ID)
|
||||
rc := waitForContainer(t, cli, ctr.ID, 10)
|
||||
rc := waitForContainer(t, cli, ctr.ID, 2*time.Minute)
|
||||
if rc != 0 {
|
||||
t.Errorf("JUnit container failed with rc=%v", rc)
|
||||
}
|
||||
@@ -140,17 +145,16 @@ func createTLSConfig(t *testing.T, certFile, password string) *tls.Config {
|
||||
}
|
||||
}
|
||||
|
||||
func testREST(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config) {
|
||||
func testRESTAdmin(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config) {
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Duration(30 * time.Second),
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", getPort(t, cli, ID, 9443))
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req.SetBasicAuth("admin", devAdminPassword)
|
||||
req.SetBasicAuth("admin", defaultAdminPassword)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -159,3 +163,70 @@ func testREST(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config
|
||||
t.Errorf("Expected HTTP status code %v from 'GET installation'; got %v", http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// curl -i -k https://localhost:1234/ibmmq/rest/v1/messaging/qmgr/qm1/queue/DEV.QUEUE.1/message -X POST -u app -H “ibm-mq-rest-csrf-token: N/A” -H “Content-Type: text/plain;charset=utf-8" -d “Hello World”
|
||||
|
||||
func logHTTPRequest(t *testing.T, req *http.Request) {
|
||||
d, err := httputil.DumpRequestOut(req, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("HTTP request: %v", string(d))
|
||||
}
|
||||
|
||||
func logHTTPResponse(t *testing.T, resp *http.Response) {
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("HTTP response: %v", string(d))
|
||||
}
|
||||
|
||||
func testRESTMessaging(t *testing.T, cli *client.Client, ID string, tlsConfig *tls.Config, qmName string, user string, password string) {
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Duration(30 * time.Second),
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
q := "DEV.QUEUE.1"
|
||||
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/messaging/qmgr/%s/queue/%s/message", getPort(t, cli, ID, 9443), qmName, q)
|
||||
putMessage := []byte("Hello")
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(putMessage))
|
||||
req.SetBasicAuth(user, password)
|
||||
req.Header.Add("ibm-mq-rest-csrf-token", "n/a")
|
||||
req.Header.Add("Content-Type", "text/plain;charset=utf-8")
|
||||
logHTTPRequest(t, req)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logHTTPResponse(t, resp)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Errorf("Expected HTTP status code %v from 'POST to queue'; got %v", http.StatusOK, resp.StatusCode)
|
||||
t.Logf("HTTP response: %+v", resp)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("DELETE", url, nil)
|
||||
req.Header.Add("ibm-mq-rest-csrf-token", "n/a")
|
||||
req.SetBasicAuth(user, password)
|
||||
logHTTPRequest(t, req)
|
||||
resp, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logHTTPResponse(t, resp)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected HTTP status code %v from 'DELETE from queue'; got %v", http.StatusOK, resp.StatusCode)
|
||||
t.Logf("HTTP response: %+v", resp)
|
||||
t.Fail()
|
||||
}
|
||||
gotMessage, err := ioutil.ReadAll(resp.Body)
|
||||
//gotMessage := string(b)
|
||||
if string(gotMessage) != string(putMessage) {
|
||||
t.Errorf("Expected payload to be \"%s\"; got \"%s\"", putMessage, gotMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -38,6 +37,7 @@ import (
|
||||
|
||||
func TestLicenseNotSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -45,15 +45,16 @@ func TestLicenseNotSet(t *testing.T) {
|
||||
containerConfig := container.Config{}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
rc := waitForContainer(t, cli, id, 5)
|
||||
rc := waitForContainer(t, cli, id, 20*time.Second)
|
||||
if rc != 1 {
|
||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||
}
|
||||
expectTerminationMessage(t)
|
||||
expectTerminationMessage(t, cli, id)
|
||||
}
|
||||
|
||||
func TestLicenseView(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -63,7 +64,7 @@ func TestLicenseView(t *testing.T) {
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
rc := waitForContainer(t, cli, id, 5)
|
||||
rc := waitForContainer(t, cli, id, 20*time.Second)
|
||||
if rc != 1 {
|
||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||
}
|
||||
@@ -77,12 +78,14 @@ func TestLicenseView(t *testing.T) {
|
||||
// TestGoldenPath starts a queue manager successfully when metrics are enabled
|
||||
func TestGoldenPathWithMetrics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
goldenPath(t, true)
|
||||
}
|
||||
|
||||
// TestGoldenPath starts a queue manager successfully when metrics are disabled
|
||||
func TestGoldenPathNoMetrics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
goldenPath(t, false)
|
||||
}
|
||||
|
||||
@@ -107,32 +110,40 @@ func goldenPath(t *testing.T, metric bool) {
|
||||
}
|
||||
|
||||
// TestSecurityVulnerabilities checks for any vulnerabilities in the image, as reported
|
||||
// by Ubuntu
|
||||
// by Red Hat
|
||||
func TestSecurityVulnerabilities(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rc, _ := runContainerOneShot(t, cli, "bash", "-c", "test -d /etc/apt")
|
||||
rc, _ := runContainerOneShot(t, cli, "bash", "-c", "command -v microdnf && test -e /etc/yum.repos.d/ubi.repo")
|
||||
if rc != 0 {
|
||||
t.Skip("Skipping test because container is not Ubuntu-based")
|
||||
}
|
||||
// Override the entrypoint to make "apt" only receive security updates, then check for updates
|
||||
var url string
|
||||
if runtime.GOARCH == "amd64" {
|
||||
url = "http://security.ubuntu.com/ubuntu/"
|
||||
} else {
|
||||
url = "http://ports.ubuntu.com/ubuntu-ports/"
|
||||
}
|
||||
rc, log := runContainerOneShot(t, cli, "bash", "-c", "source /etc/os-release && echo \"deb "+url+" ${VERSION_CODENAME}-security main restricted\" > /etc/apt/sources.list && apt-get update 2>&1 >/dev/null && apt-get --simulate -qq upgrade")
|
||||
if rc != 0 {
|
||||
t.Fatalf("Expected success, got %v", rc)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(log), "\n")
|
||||
if len(lines) > 0 && lines[0] != "" {
|
||||
t.Errorf("Expected no vulnerabilities, found the following:\n%v", log)
|
||||
t.Skip("Skipping test because container is based on ubi-minimal, which doesn't include yum")
|
||||
}
|
||||
// id, _, err := command.Run("sudo", "buildah", "from", imageName())
|
||||
// if err != nil {
|
||||
// t.Log(id)
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// id = strings.TrimSpace(id)
|
||||
// defer command.Run("buildah", "rm", id)
|
||||
// mnt, _, err := command.Run("sudo", "buildah", "mount", id)
|
||||
// if err != nil {
|
||||
// t.Log(mnt)
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// mnt = strings.TrimSpace(mnt)
|
||||
// out, _, err := command.Run("bash", "-c", "sudo cp /etc/yum.repos.d/* "+filepath.Join(mnt, "/etc/yum.repos.d/"))
|
||||
// if err != nil {
|
||||
// t.Log(out)
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// out, ret, _ := command.Run("bash", "-c", "yum --installroot="+mnt+" updateinfo list sec | grep /Sec")
|
||||
// if ret != 1 {
|
||||
// t.Errorf("Expected no vulnerabilities, found the following:\n%v", out)
|
||||
// }
|
||||
}
|
||||
|
||||
func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName string) {
|
||||
@@ -155,11 +166,13 @@ func utilTestNoQueueManagerName(t *testing.T, hostName string, expectedName stri
|
||||
}
|
||||
func TestNoQueueManagerName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
utilTestNoQueueManagerName(t, "test", "test")
|
||||
}
|
||||
|
||||
func TestNoQueueManagerNameInvalidHostname(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
utilTestNoQueueManagerName(t, "test-1", "test1")
|
||||
}
|
||||
|
||||
@@ -167,6 +180,7 @@ func TestNoQueueManagerNameInvalidHostname(t *testing.T) {
|
||||
// container and starts a new one with same volume. With metrics enabled
|
||||
func TestWithVolumeAndMetrics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
withVolume(t, true)
|
||||
}
|
||||
|
||||
@@ -174,6 +188,7 @@ func TestWithVolumeAndMetrics(t *testing.T) {
|
||||
// container and starts a new one with same volume. With metrics disabled
|
||||
func TestWithVolumeNoMetrics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
withVolume(t, false)
|
||||
}
|
||||
|
||||
@@ -183,7 +198,7 @@ func withVolume(t *testing.T, metric bool) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
vol := createVolume(t, cli)
|
||||
vol := createVolume(t, cli, t.Name())
|
||||
defer removeVolume(t, cli, vol.Name)
|
||||
containerConfig := container.Config{
|
||||
Image: imageName(),
|
||||
@@ -221,10 +236,67 @@ func withVolume(t *testing.T, metric bool) {
|
||||
waitForReady(t, cli, ctr2.ID)
|
||||
}
|
||||
|
||||
// TestWithSplitVolumesLogsData starts a queue manager with separate log/data mounts
|
||||
func TestWithSplitVolumesLogsData(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
defer removeVolume(t, cli, qmsharedlogs.Name)
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
defer removeVolume(t, cli, qmshareddata.Name)
|
||||
|
||||
err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"})
|
||||
|
||||
defer removeVolume(t, cli, qmVol)
|
||||
defer cleanContainer(t, cli, qmID)
|
||||
|
||||
waitForReady(t, cli, qmID)
|
||||
}
|
||||
|
||||
// TestWithSplitVolumesLogsOnly starts a queue manager with a separate log mount
|
||||
func TestWithSplitVolumesLogsOnly(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
defer removeVolume(t, cli, qmsharedlogs.Name)
|
||||
|
||||
err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, "", []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"})
|
||||
|
||||
defer removeVolume(t, cli, qmVol)
|
||||
defer cleanContainer(t, cli, qmID)
|
||||
|
||||
waitForReady(t, cli, qmID)
|
||||
}
|
||||
|
||||
// TestWithSplitVolumesDataOnly starts a queue manager with a separate data mount
|
||||
func TestWithSplitVolumesDataOnly(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
defer removeVolume(t, cli, qmshareddata.Name)
|
||||
|
||||
err, qmID, qmVol := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata.Name, []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"})
|
||||
|
||||
defer removeVolume(t, cli, qmVol)
|
||||
defer cleanContainer(t, cli, qmID)
|
||||
|
||||
waitForReady(t, cli, qmID)
|
||||
}
|
||||
|
||||
// TestNoVolumeWithRestart ensures a queue manager container can be stopped
|
||||
// and restarted cleanly
|
||||
func TestNoVolumeWithRestart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -240,58 +312,138 @@ func TestNoVolumeWithRestart(t *testing.T) {
|
||||
waitForReady(t, cli, id)
|
||||
}
|
||||
|
||||
// TestCreateQueueManagerFail causes a failure of `crtmqm`
|
||||
func TestCreateQueueManagerFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
// TestVolumeRequiresRoot tests the case where only the root user can write
|
||||
// to the persistent volume. In this case, an "init container" is needed,
|
||||
// where `runmqserver -i` is run to initialize the storage. Then the
|
||||
// container can be run as normal.
|
||||
func TestVolumeRequiresRoot(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName())
|
||||
vol := createVolume(t, cli, t.Name())
|
||||
defer removeVolume(t, cli, vol.Name)
|
||||
|
||||
// Set permissions on the volume to only allow root to write it
|
||||
// It's important that read and execute permissions are given to other users
|
||||
rc, _ := runContainerOneShotWithVolume(t, cli, vol.Name+":/mnt/mqm:nocopy", "bash", "-c", "chown 65534:4294967294 /mnt/mqm/ && chmod 0755 /mnt/mqm/ && ls -lan /mnt/mqm/")
|
||||
if rc != 0 {
|
||||
t.Errorf("Expected one shot container to return rc=0, got rc=%v", rc)
|
||||
}
|
||||
|
||||
containerConfig := container.Config{
|
||||
Image: imageName(),
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||
}
|
||||
hostConfig := container.HostConfig{
|
||||
Binds: []string{
|
||||
coverageBind(t),
|
||||
vol.Name + ":/mnt/mqm:nocopy",
|
||||
},
|
||||
}
|
||||
networkingConfig := network.NetworkingConfig{}
|
||||
|
||||
// Run an "init container" as root, with the "-i" option, to initialize the volume
|
||||
containerConfig = container.Config{
|
||||
Image: imageName(),
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"},
|
||||
User: "0",
|
||||
Entrypoint: []string{"runmqserver", "-i"},
|
||||
}
|
||||
initCtr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"Init")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ")
|
||||
defer cleanContainer(t, cli, initCtr.ID)
|
||||
t.Logf("Init container ID=%v", initCtr.ID)
|
||||
startContainer(t, cli, initCtr.ID)
|
||||
rc = waitForContainer(t, cli, initCtr.ID, 20*time.Second)
|
||||
if rc != 0 {
|
||||
t.Errorf("Expected init container to exit with rc=0, got rc=%v", rc)
|
||||
}
|
||||
|
||||
containerConfig = container.Config{
|
||||
Image: imageName(),
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=true"},
|
||||
}
|
||||
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"Main")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ctr.ID)
|
||||
t.Logf("Main container ID=%v", ctr.ID)
|
||||
startContainer(t, cli, ctr.ID)
|
||||
waitForReady(t, cli, ctr.ID)
|
||||
}
|
||||
|
||||
// TestCreateQueueManagerFail causes a failure of `crtmqm`
|
||||
func TestCreateQueueManagerFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN echo '#!/bin/bash\nexit 999' > /opt/mqm/bin/crtmqm
|
||||
RUN chown mqm:mqm /opt/mqm/bin/crtmqm
|
||||
RUN chmod 6550 /opt/mqm/bin/crtmqm
|
||||
USER mqm`, imageName())},
|
||||
}
|
||||
tag := createImage(t, cli, files)
|
||||
defer deleteImage(t, cli, tag)
|
||||
|
||||
containerConfig := container.Config{
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||
// Override the entrypoint to create the queue manager directory, but leave it empty.
|
||||
// This will cause `crtmqm` to return with an exit code of 2.
|
||||
Entrypoint: []string{"bash", "-c", "mkdir -p /mnt/mqm/data && mkdir -p /var/mqm/qmgrs/qm1 && exec " + oldEntrypoint},
|
||||
Image: tag,
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
rc := waitForContainer(t, cli, id, 10)
|
||||
rc := waitForContainer(t, cli, id, 10*time.Second)
|
||||
if rc != 1 {
|
||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||
}
|
||||
expectTerminationMessage(t)
|
||||
expectTerminationMessage(t, cli, id)
|
||||
}
|
||||
|
||||
// TestStartQueueManagerFail causes a failure of `strmqm`
|
||||
func TestStartQueueManagerFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm
|
||||
RUN chown mqm:mqm /opt/mqm/bin/strmqm
|
||||
RUN chmod 6550 /opt/mqm/bin/strmqm
|
||||
USER mqm`, imageName())},
|
||||
}
|
||||
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ")
|
||||
tag := createImage(t, cli, files)
|
||||
defer deleteImage(t, cli, tag)
|
||||
|
||||
containerConfig := container.Config{
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=1"},
|
||||
// Override the entrypoint to replace `strmqm` with a script which deletes the queue manager.
|
||||
// This will cause `strmqm` to return with an exit code of 72.
|
||||
Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm && exec " + oldEntrypoint},
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||
Image: tag,
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
rc := waitForContainer(t, cli, id, 10)
|
||||
rc := waitForContainer(t, cli, id, 20*time.Second)
|
||||
if rc != 1 {
|
||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||
}
|
||||
expectTerminationMessage(t)
|
||||
expectTerminationMessage(t, cli, id)
|
||||
}
|
||||
|
||||
// TestVolumeUnmount runs a queue manager with a volume, and then forces an
|
||||
@@ -300,11 +452,12 @@ func TestStartQueueManagerFail(t *testing.T) {
|
||||
// attached storage gets unmounted.
|
||||
func TestVolumeUnmount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
vol := createVolume(t, cli)
|
||||
vol := createVolume(t, cli, t.Name())
|
||||
defer removeVolume(t, cli, vol.Name)
|
||||
containerConfig := container.Config{
|
||||
Image: imageName(),
|
||||
@@ -348,6 +501,7 @@ func TestVolumeUnmount(t *testing.T) {
|
||||
// created, then checks that no zombies exist (runmqserver should reap them)
|
||||
func TestZombies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -384,6 +538,7 @@ func TestZombies(t *testing.T) {
|
||||
// on that image, and checks that the MQSC has been applied correctly.
|
||||
func TestMQSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -391,7 +546,13 @@ func TestMQSC(t *testing.T) {
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf("FROM %v\nRUN rm -f /etc/mqm/*.mqsc\nADD test.mqsc /etc/mqm/", imageName())},
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN rm -f /etc/mqm/*.mqsc
|
||||
ADD test.mqsc /etc/mqm/
|
||||
RUN chmod 0660 /etc/mqm/test.mqsc
|
||||
USER mqm`, imageName())},
|
||||
{"test.mqsc", "DEFINE QLOCAL(test)"},
|
||||
}
|
||||
tag := createImage(t, cli, files)
|
||||
@@ -411,11 +572,244 @@ func TestMQSC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLargeMQSC creates a new image with a large MQSC file in, starts a container based
|
||||
// on that image, and checks that the MQSC has been applied correctly.
|
||||
func TestLargeMQSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const numQueues = 1000
|
||||
var buf bytes.Buffer
|
||||
for i := 1; i <= numQueues; i++ {
|
||||
fmt.Fprintf(&buf, "* Test processing of a large MQSC file, defining queue test%v\nDEFINE QLOCAL(test%v)\n", i, i)
|
||||
}
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN rm -f /etc/mqm/*.mqsc
|
||||
ADD test.mqsc /etc/mqm/
|
||||
RUN chmod 0660 /etc/mqm/test.mqsc
|
||||
USER mqm`, imageName())},
|
||||
{"test.mqsc", buf.String()},
|
||||
}
|
||||
tag := createImage(t, cli, files)
|
||||
defer deleteImage(t, cli, tag)
|
||||
|
||||
containerConfig := container.Config{
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||
Image: tag,
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
|
||||
rc, mqscOutput := execContainer(t, cli, id, "mqm", []string{"bash", "-c", "echo 'DISPLAY QLOCAL(test" + strconv.Itoa(numQueues) + ")' | runmqsc"})
|
||||
if rc != 0 {
|
||||
r := regexp.MustCompile("AMQ[0-9][0-9][0-9][0-9]E")
|
||||
t.Fatalf("Expected runmqsc to exit with rc=0, got %v with error %v", rc, r.FindString(mqscOutput))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedactValidMQSC creates a new image with a Valid MQSC file that contains sensitive information, starts a container based
|
||||
// on that image, and checks that the MQSC has been redacted in the logs.
|
||||
func TestRedactValidMQSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
passwords := "hippoman4567"
|
||||
sslcryp := fmt.Sprintf("GSK_PKCS11=/usr/lib/pkcs11/PKCS11_API.so;token-label;%s;SYMMETRIC_CIPHER_ON;", passwords)
|
||||
|
||||
/* LDAPPWD*/
|
||||
fmt.Fprintf(&buf, "DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) CONNAME('test(24)') SHORTUSR('sn') LDAPUSER('user') LDAPPWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) ldappwd('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) lDaPpWd('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD \t('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) +\n LDAP+\n PWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) -\nLDAPP-\nWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) +\n*test comment\n LDAPP-\n*test comment2\nWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD(%v)\n", passwords)
|
||||
|
||||
/* PASSWORD */
|
||||
fmt.Fprintf(&buf, "DEFINE CHANNEL(TEST2) CHLTYPE(SDR) CONNAME('test(24)') XMITQ('fake') PASSWORD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) password('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) pAsSwOrD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASSWORD \t('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) +\n CHLTYPE(SDR) PASS+\n WORD+\n ('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) -\nCHLTYPE(SDR) PASS-\nWORD-\n('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) +\n CHLTYPE(SDR) PASS-\n*comemnt 2\nWORD+\n*test comment\n ('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER CHANNEL(TEST2) CHLTYPE(SDR) PASSWORD(%s)\n", passwords)
|
||||
|
||||
/* SSLCRYP */
|
||||
fmt.Fprintf(&buf, "ALTER QMGR SSLCRYP('%v')\n", sslcryp)
|
||||
fmt.Fprintf(&buf, "ALTER QMGR sslcryp('%v')\n", sslcryp)
|
||||
fmt.Fprintf(&buf, "ALTER QMGR SsLcRyP('%v')\n", sslcryp)
|
||||
fmt.Fprintf(&buf, "ALTER QMGR SSLCRYP \t('%v')\n", sslcryp)
|
||||
fmt.Fprintf(&buf, "ALTER QMGR +\n SSL+\n CRYP+\n ('%v')\n", sslcryp)
|
||||
fmt.Fprintf(&buf, "ALTER QMGR -\nSSLC-\nRYP-\n('%v')\n", sslcryp)
|
||||
fmt.Fprintf(&buf, "ALTER QMGR +\n*commenttime\n SSL-\n*commentagain\nCRYP+\n*last comment\n ('%v')\n", sslcryp)
|
||||
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN rm -f /etc/mqm/*.mqsc
|
||||
ADD test.mqsc /etc/mqm/
|
||||
RUN chmod 0660 /etc/mqm/test.mqsc
|
||||
USER mqm`, imageName())},
|
||||
{"test.mqsc", buf.String()},
|
||||
}
|
||||
tag := createImage(t, cli, files)
|
||||
defer deleteImage(t, cli, tag)
|
||||
|
||||
containerConfig := container.Config{
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||
Image: tag,
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
stopContainer(t, cli, id)
|
||||
scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, id)))
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if strings.Contains(s, sslcryp) || strings.Contains(s, passwords) {
|
||||
t.Fatalf("Expected redacted MQSC output, got: %v", s)
|
||||
}
|
||||
}
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRedactValidMQSC creates a new image with a Invalid MQSC file that contains sensitive information, starts a container based
|
||||
// on that image, and checks that the MQSC has been redacted in the logs.
|
||||
func TestRedactInvalidMQSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
passwords := "hippoman4567"
|
||||
sslcryp := fmt.Sprintf("GSK_PKCS11=/usr/lib/pkcs11/PKCS11_API.so;token-label;%s;SYMMETRIC_CIPHER_ON;", passwords)
|
||||
|
||||
/* LDAPPWD*/
|
||||
fmt.Fprintf(&buf, "DEFINE AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) CONNAME('test(24)') SHORTUSR('sn') LDAPUSER('user') LDAPPWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPPPPPP('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD['%v']\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(ARGHHH) LDAPPWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) ARGHAHA(IDPWLDAP) LDAPPWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD '%v'\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('%v') badvalues\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) badvales LDAPPWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD{'%v'}\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD<'%v'>\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('%v'+\n p['il6])\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) AUTHTYPE(IDPWLDAP) LDAPPWD('%v'/653***)\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v'\n DISPLAY QMGR", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v💩')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD💩('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAP+\n 💩PWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) 💩 LDAPPWD('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD 💩 ('%v')\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER AUTHINFO(TEST) LDAPPWD('%v') 💩\n", passwords)
|
||||
fmt.Fprintf(&buf, "ALTER 💩 AUTHINFO(TEST) LDAPPWD('%v')\n", passwords)
|
||||
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN rm -f /etc/mqm/*.mqsc
|
||||
ADD test.mqsc /etc/mqm/
|
||||
RUN chmod 0660 /etc/mqm/test.mqsc
|
||||
USER mqm`, imageName())},
|
||||
{"test.mqsc", buf.String()},
|
||||
}
|
||||
tag := createImage(t, cli, files)
|
||||
defer deleteImage(t, cli, tag)
|
||||
|
||||
containerConfig := container.Config{
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||
Image: tag,
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
rc := waitForContainer(t, cli, id, 20*time.Second)
|
||||
if rc != 1 {
|
||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||
}
|
||||
scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, id)))
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if strings.Contains(s, sslcryp) || strings.Contains(s, passwords) {
|
||||
t.Fatalf("Expected redacted MQSC output, got: %v", s)
|
||||
}
|
||||
}
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInvalidMQSC creates a new image with an MQSC file containing invalid MQSC,
|
||||
// tries to start a container based on that image, and checks that container terminates
|
||||
func TestInvalidMQSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN rm -f /etc/mqm/*.mqsc
|
||||
ADD mqscTest.mqsc /etc/mqm/
|
||||
RUN chmod 0660 /etc/mqm/mqscTest.mqsc
|
||||
USER mqm`, imageName())},
|
||||
{"mqscTest.mqsc", "DEFINE INVALIDLISTENER('TEST.LISTENER.TCP') TRPTYPE(TCP) PORT(1414) CONTROL(QMGR) REPLACE"},
|
||||
}
|
||||
tag := createImage(t, cli, files)
|
||||
defer deleteImage(t, cli, tag)
|
||||
|
||||
containerConfig := container.Config{
|
||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
||||
Image: tag,
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
rc := waitForContainer(t, cli, id, 60*time.Second)
|
||||
if rc != 1 {
|
||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||
}
|
||||
expectTerminationMessage(t, cli, id)
|
||||
}
|
||||
|
||||
// TestReadiness creates a new image with large amounts of MQSC in, to
|
||||
// ensure that the readiness check doesn't pass until configuration has finished.
|
||||
// WARNING: This test is sensitive to the speed of the machine it's running on.
|
||||
func TestReadiness(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -428,7 +822,13 @@ func TestReadiness(t *testing.T) {
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"Dockerfile", fmt.Sprintf("FROM %v\nRUN rm -f /etc/mqm/*.mqsc\nADD test.mqsc /etc/mqm/", imageName())},
|
||||
{"Dockerfile", fmt.Sprintf(`
|
||||
FROM %v
|
||||
USER root
|
||||
RUN rm -f /etc/mqm/*.mqsc
|
||||
ADD test.mqsc /etc/mqm/
|
||||
RUN chmod 0660 /etc/mqm/test.mqsc
|
||||
USER mqm`, imageName())},
|
||||
{"test.mqsc", buf.String()},
|
||||
}
|
||||
tag := createImage(t, cli, files)
|
||||
@@ -464,22 +864,34 @@ func TestReadiness(t *testing.T) {
|
||||
|
||||
func TestErrorLogRotation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
logsize := 65536
|
||||
|
||||
rc, _ := runContainerOneShot(t, cli, "bash", "-c", "test -d /etc/apt")
|
||||
if rc != 0 {
|
||||
// RHEL
|
||||
logsize = 32768
|
||||
}
|
||||
|
||||
qmName := "qm1"
|
||||
containerConfig := container.Config{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qmName,
|
||||
"MQMAXERRORLOGSIZE=65536",
|
||||
fmt.Sprintf("MQMAXERRORLOGSIZE=%d", logsize),
|
||||
"LOG_FORMAT=json",
|
||||
fmt.Sprintf("AMQ_EXTRA_QM_STANZAS=QMErrorLog:ErrorLogSize=%d", logsize),
|
||||
},
|
||||
ExposedPorts: nat.PortSet{
|
||||
"1414/tcp": struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
@@ -487,6 +899,7 @@ func TestErrorLogRotation(t *testing.T) {
|
||||
// Generate some content for the error logs, by trying to put messages under an unauthorized user
|
||||
// execContainer(t, cli, id, "fred", []string{"bash", "-c", "for i in {1..30} ; do /opt/mqm/samp/bin/amqsput FAKE; done"})
|
||||
execContainer(t, cli, id, "root", []string{"useradd", "fred"})
|
||||
|
||||
for {
|
||||
execContainer(t, cli, id, "fred", []string{"bash", "-c", "/opt/mqm/samp/bin/amqsput FAKE"})
|
||||
|
||||
@@ -529,12 +942,14 @@ func TestErrorLogRotation(t *testing.T) {
|
||||
// Tests the log comes out in JSON format when JSON format is enabled. With metrics enabled
|
||||
func TestJSONLogFormatWithMetrics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
jsonLogFormat(t, true)
|
||||
}
|
||||
|
||||
// Tests the log comes out in JSON format when JSON format is enabled. With metrics disabled
|
||||
func TestJSONLogFormatNoMetrics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
jsonLogFormat(t, false)
|
||||
}
|
||||
|
||||
@@ -575,6 +990,7 @@ func jsonLogFormat(t *testing.T, metric bool) {
|
||||
|
||||
func TestBadLogFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -587,11 +1003,11 @@ func TestBadLogFormat(t *testing.T) {
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
rc := waitForContainer(t, cli, id, 5)
|
||||
rc := waitForContainer(t, cli, id, 20*time.Second)
|
||||
if rc != 1 {
|
||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||
}
|
||||
expectTerminationMessage(t)
|
||||
expectTerminationMessage(t, cli, id)
|
||||
}
|
||||
|
||||
// TestMQJSONDisabled tests the case where MQ's JSON logging feature is
|
||||
@@ -670,8 +1086,8 @@ func TestVersioning(t *testing.T) {
|
||||
|
||||
total := 6
|
||||
foundCreated := false
|
||||
foundRevision := false
|
||||
foundSource := false
|
||||
// foundRevision := false
|
||||
// foundSource := false
|
||||
foundMQVersion := false
|
||||
foundMQLevel := false
|
||||
foundMQLicense := false
|
||||
@@ -684,37 +1100,38 @@ func TestVersioning(t *testing.T) {
|
||||
dataAr := strings.Split(line, " ")
|
||||
data := dataAr[len(dataAr)-1]
|
||||
|
||||
// Verify created
|
||||
// Verify created is in a known timestamp format
|
||||
_, err := time.Parse(time.RFC3339, data)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to validate Image created (%v) - %v", data, err)
|
||||
_, err2 := time.Parse("2006-01-02T15:04:05-0700", data)
|
||||
if err != nil && err2 != nil {
|
||||
t.Errorf("Failed to validate Image created stamp (%v) - %v or %v", data, time.RFC3339, "2006-01-02T15:04:05-0700")
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(line, "Image revision:") && !foundRevision {
|
||||
total--
|
||||
foundRevision = true
|
||||
dataAr := strings.Split(line, " ")
|
||||
data := dataAr[len(dataAr)-1]
|
||||
// if strings.Contains(line, "Image revision:") && !foundRevision {
|
||||
// total--
|
||||
// foundRevision = true
|
||||
// dataAr := strings.Split(line, " ")
|
||||
// data := dataAr[len(dataAr)-1]
|
||||
|
||||
// Verify revision
|
||||
pattern := regexp.MustCompile("^[a-fA-F0-9]{40}$")
|
||||
if !pattern.MatchString(data) {
|
||||
t.Errorf("Failed to validate revision (%v)", data)
|
||||
}
|
||||
}
|
||||
// // Verify revision
|
||||
// pattern := regexp.MustCompile("^[a-fA-F0-9]{40}$")
|
||||
// if !pattern.MatchString(data) {
|
||||
// t.Errorf("Failed to validate revision (%v)", data)
|
||||
// }
|
||||
// }
|
||||
|
||||
if strings.Contains(line, "Image source:") && !foundSource {
|
||||
total--
|
||||
foundSource = true
|
||||
dataAr := strings.Split(line, " ")
|
||||
data := dataAr[len(dataAr)-1]
|
||||
// if strings.Contains(line, "Image source:") && !foundSource {
|
||||
// total--
|
||||
// foundSource = true
|
||||
// dataAr := strings.Split(line, " ")
|
||||
// data := dataAr[len(dataAr)-1]
|
||||
|
||||
// Verify source
|
||||
if !strings.Contains(data, "github") {
|
||||
t.Errorf("Failed to validate source (%v)", data)
|
||||
}
|
||||
}
|
||||
// // Verify source
|
||||
// if !strings.Contains(data, "github") {
|
||||
// t.Errorf("Failed to validate source (%v)", data)
|
||||
// }
|
||||
// }
|
||||
|
||||
if strings.Contains(line, "MQ version:") && !foundMQVersion {
|
||||
total--
|
||||
@@ -760,7 +1177,11 @@ func TestVersioning(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if !foundCreated || !foundRevision || !foundSource || !foundMQVersion || !foundMQLevel || !foundMQLicense {
|
||||
t.Errorf("Failed to find one or more version strings: created(%v) revision(%v) source(%v) mqversion(%v) mqlevel(%v) mqlicense(%v)", foundCreated, foundRevision, foundSource, foundMQVersion, foundMQLevel, foundMQLicense)
|
||||
// if !foundCreated || !foundRevision || !foundSource || !foundMQVersion || !foundMQLevel || !foundMQLicense {
|
||||
if !foundCreated || !foundMQVersion || !foundMQLevel || !foundMQLicense {
|
||||
|
||||
// t.Errorf("Failed to find one or more version strings: created(%v) revision(%v) source(%v) mqversion(%v) mqlevel(%v) mqlicense(%v)", foundCreated, foundRevision, foundSource, foundMQVersion, foundMQLevel, foundMQLicense)
|
||||
t.Errorf("Failed to find one or more version strings: created(%v) mqversion(%v) mqlevel(%v) mqlicense(%v)", foundCreated, foundMQVersion, foundMQLevel, foundMQLicense)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2018
|
||||
© Copyright IBM Corporation 2017, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -43,6 +43,18 @@ import (
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
type containerDetails struct {
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Path string
|
||||
Args []string
|
||||
CapAdd []string
|
||||
CapDrop []string
|
||||
User string
|
||||
Env []string
|
||||
}
|
||||
|
||||
func imageName() string {
|
||||
image, ok := os.LookupEnv("TEST_IMAGE")
|
||||
if !ok {
|
||||
@@ -59,6 +71,29 @@ func imageNameDevJMS() string {
|
||||
return image
|
||||
}
|
||||
|
||||
// baseImage returns the ID of the underlying base image (e.g. "ubuntu" or "rhel")
|
||||
func baseImage(t *testing.T, cli *client.Client) string {
|
||||
rc, out := runContainerOneShot(t, cli, "grep", "^ID=", "/etc/os-release")
|
||||
if rc != 0 {
|
||||
t.Fatal("Couldn't determine base image")
|
||||
}
|
||||
s := strings.Split(out, "=")
|
||||
if len(s) < 2 {
|
||||
t.Fatal("Couldn't determine base image string")
|
||||
}
|
||||
return s[1]
|
||||
}
|
||||
|
||||
// devImage returns true if the image under test is a developer image,
|
||||
// determined by use of the MQ_ADMIN_PASSWORD environment variable
|
||||
func devImage(t *testing.T, cli *client.Client) bool {
|
||||
rc, _ := runContainerOneShot(t, cli, "printenv", "MQ_ADMIN_PASSWORD")
|
||||
if rc == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isWSL return whether we are running in the Windows Subsystem for Linux
|
||||
func isWSL(t *testing.T) bool {
|
||||
if runtime.GOOS == "linux" {
|
||||
@@ -124,66 +159,79 @@ func getTempDir(t *testing.T, unixStylePath bool) string {
|
||||
return "/tmp/"
|
||||
}
|
||||
|
||||
// terminationLogUnixPath returns the name of the file to use for the termination log message, with a UNIX path
|
||||
func terminationLogUnixPath(t *testing.T) string {
|
||||
// Warning: this directory must be accessible to the Docker daemon,
|
||||
// in order to enable the bind mount
|
||||
return getTempDir(t, true) + t.Name() + "-termination-log"
|
||||
}
|
||||
|
||||
// terminationLogOSPath returns the name of the file to use for the termination log message, with an OS specific path
|
||||
func terminationLogOSPath(t *testing.T) string {
|
||||
// Warning: this directory must be accessible to the Docker daemon,
|
||||
// in order to enable the bind mount
|
||||
return getTempDir(t, false) + t.Name() + "-termination-log"
|
||||
}
|
||||
|
||||
// terminationBind returns a string to use to bind-mount a termination log file.
|
||||
// This is done using a bind, because you can't copy files from /dev out of the container.
|
||||
func terminationBind(t *testing.T) string {
|
||||
n := terminationLogUnixPath(t)
|
||||
// Remove it if it already exists
|
||||
os.Remove(n)
|
||||
// Create the empty file
|
||||
f, err := os.OpenFile(n, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
return terminationLogOSPath(t) + ":/dev/termination-log"
|
||||
}
|
||||
|
||||
// terminationMessage return the termination message, or an empty string if not set
|
||||
func terminationMessage(t *testing.T) string {
|
||||
b, err := ioutil.ReadFile(terminationLogUnixPath(t))
|
||||
func terminationMessage(t *testing.T, cli *client.Client, ID string) string {
|
||||
r, _, err := cli.CopyFromContainer(context.Background(), ID, "/run/termination-log")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
b, err := ioutil.ReadAll(r)
|
||||
tr := tar.NewReader(bytes.NewReader(b))
|
||||
_, err = tr.Next()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return ""
|
||||
}
|
||||
// read the complete content of the file h.Name into the bs []byte
|
||||
content, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return ""
|
||||
}
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func expectTerminationMessage(t *testing.T) {
|
||||
m := terminationMessage(t)
|
||||
func expectTerminationMessage(t *testing.T, cli *client.Client, ID string) {
|
||||
m := terminationMessage(t, cli, ID)
|
||||
if m == "" {
|
||||
t.Error("Expected termination message to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
||||
// logContainerDetails logs selected details about the container
|
||||
func logContainerDetails(t *testing.T, cli *client.Client, ID string) {
|
||||
i, err := cli.ContainerInspect(context.Background(), ID)
|
||||
if err == nil {
|
||||
// Log the results and continue
|
||||
t.Logf("Inspected container %v: %#v", ID, i)
|
||||
s, err := json.MarshalIndent(i, "", " ")
|
||||
d := containerDetails{
|
||||
ID: ID,
|
||||
Name: i.Name,
|
||||
Image: i.Image,
|
||||
Path: i.Path,
|
||||
Args: i.Args,
|
||||
CapAdd: i.HostConfig.CapAdd,
|
||||
CapDrop: i.HostConfig.CapDrop,
|
||||
User: i.Config.User,
|
||||
Env: i.Config.Env,
|
||||
}
|
||||
// If you need more details, you can always just run `json.MarshalIndent(i, "", " ")` to see everything.
|
||||
t.Logf("Container details: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanContainerQuiet(t *testing.T, cli *client.Client, ID string) {
|
||||
timeout := 10 * time.Second
|
||||
err := cli.ContainerStop(context.Background(), ID, &timeout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
// Just log the error and continue
|
||||
t.Log(err)
|
||||
}
|
||||
t.Logf("Inspected container %v: %v", ID, string(s))
|
||||
opts := types.ContainerRemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: true,
|
||||
}
|
||||
err = cli.ContainerRemove(context.Background(), ID, opts)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
||||
logContainerDetails(t, cli, ID)
|
||||
t.Logf("Stopping container: %v", ID)
|
||||
timeout := 10 * time.Second
|
||||
// Stop the container. This allows the coverage output to be generated.
|
||||
err = cli.ContainerStop(context.Background(), ID, &timeout)
|
||||
err := cli.ContainerStop(context.Background(), ID, &timeout)
|
||||
if err != nil {
|
||||
// Just log the error and continue
|
||||
t.Log(err)
|
||||
@@ -195,11 +243,10 @@ func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
||||
// Log the container output for any container we're about to delete
|
||||
t.Logf("Console log from container %v:\n%v", ID, inspectTextLogs(t, cli, ID))
|
||||
|
||||
m := terminationMessage(t)
|
||||
m := terminationMessage(t, cli, ID)
|
||||
if m != "" {
|
||||
t.Logf("Termination message: %v", m)
|
||||
}
|
||||
os.Remove(terminationLogUnixPath(t))
|
||||
|
||||
t.Logf("Removing container: %s", ID)
|
||||
opts := types.ContainerRemoveOptions{
|
||||
@@ -219,15 +266,36 @@ func runContainerWithPorts(t *testing.T, cli *client.Client, containerConfig *co
|
||||
if containerConfig.Image == "" {
|
||||
containerConfig.Image = imageName()
|
||||
}
|
||||
// Always run as the "mqm" user, unless the test has specified otherwise
|
||||
if containerConfig.User == "" {
|
||||
containerConfig.User = "mqm"
|
||||
}
|
||||
// if coverage
|
||||
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
|
||||
containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t))
|
||||
hostConfig := container.HostConfig{
|
||||
Binds: []string{
|
||||
coverageBind(t),
|
||||
terminationBind(t),
|
||||
},
|
||||
PortBindings: nat.PortMap{},
|
||||
CapDrop: []string{
|
||||
"ALL",
|
||||
},
|
||||
}
|
||||
if devImage(t, cli) {
|
||||
t.Logf("Detected MQ Advanced for Developers image — adding extra Linux capabilities to container")
|
||||
hostConfig.CapAdd = []string{
|
||||
"CHOWN",
|
||||
"SETUID",
|
||||
"SETGID",
|
||||
"AUDIT_WRITE",
|
||||
}
|
||||
// Only needed for a RHEL-based image
|
||||
if baseImage(t, cli) != "ubuntu" {
|
||||
hostConfig.CapAdd = append(hostConfig.CapAdd, "DAC_OVERRIDE")
|
||||
}
|
||||
} else {
|
||||
t.Logf("Detected MQ Advanced image - dropping all capabilities")
|
||||
}
|
||||
for _, p := range ports {
|
||||
port := nat.Port(fmt.Sprintf("%v/tcp", p))
|
||||
@@ -254,13 +322,62 @@ func runContainer(t *testing.T, cli *client.Client, containerConfig *container.C
|
||||
return runContainerWithPorts(t, cli, containerConfig, nil)
|
||||
}
|
||||
|
||||
// runContainerOneShot runs a container with a custom entrypoint, as the root
|
||||
// user and with default capabilities
|
||||
func runContainerOneShot(t *testing.T, cli *client.Client, command ...string) (int64, string) {
|
||||
containerConfig := container.Config{
|
||||
Entrypoint: command,
|
||||
User: "root",
|
||||
Image: imageName(),
|
||||
}
|
||||
id := runContainer(t, cli, &containerConfig)
|
||||
defer cleanContainer(t, cli, id)
|
||||
return waitForContainer(t, cli, id, 10), inspectLogs(t, cli, id)
|
||||
hostConfig := container.HostConfig{}
|
||||
networkingConfig := network.NetworkingConfig{}
|
||||
t.Logf("Running one shot container (%s): %v", containerConfig.Image, command)
|
||||
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShot")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startOptions := types.ContainerStartOptions{}
|
||||
err = cli.ContainerStart(context.Background(), ctr.ID, startOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainerQuiet(t, cli, ctr.ID)
|
||||
rc := waitForContainer(t, cli, ctr.ID, 20*time.Second)
|
||||
out := inspectLogs(t, cli, ctr.ID)
|
||||
t.Logf("One shot container finished with rc=%v, output=%v", rc, out)
|
||||
return rc, out
|
||||
}
|
||||
|
||||
// runContainerOneShot runs a container with a custom entrypoint, as the root
|
||||
// user, with default capabilities, and a volume mounted
|
||||
func runContainerOneShotWithVolume(t *testing.T, cli *client.Client, bind string, command ...string) (int64, string) {
|
||||
containerConfig := container.Config{
|
||||
Entrypoint: command,
|
||||
User: "root",
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := container.HostConfig{
|
||||
Binds: []string{
|
||||
bind,
|
||||
},
|
||||
}
|
||||
networkingConfig := network.NetworkingConfig{}
|
||||
t.Logf("Running one shot container with volume (%s): %v", containerConfig.Image, command)
|
||||
ctr, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShotVolume")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startOptions := types.ContainerStartOptions{}
|
||||
err = cli.ContainerStart(context.Background(), ctr.ID, startOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainerQuiet(t, cli, ctr.ID)
|
||||
rc := waitForContainer(t, cli, ctr.ID, 20*time.Second)
|
||||
out := inspectLogs(t, cli, ctr.ID)
|
||||
t.Logf("One shot container finished with rc=%v, output=%v", rc, out)
|
||||
return rc, out
|
||||
}
|
||||
|
||||
func startContainer(t *testing.T, cli *client.Client, ID string) {
|
||||
@@ -281,6 +398,14 @@ func stopContainer(t *testing.T, cli *client.Client, ID string) {
|
||||
}
|
||||
}
|
||||
|
||||
func killContainer(t *testing.T, cli *client.Client, ID string, signal string) {
|
||||
t.Logf("Killing container: %v", ID)
|
||||
err := cli.ContainerKill(context.Background(), ID, signal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getExitCodeFilename(t *testing.T) string {
|
||||
return t.Name() + "ExitCode"
|
||||
}
|
||||
@@ -309,19 +434,19 @@ func getCoverageExitCode(t *testing.T, orig int64) int64 {
|
||||
}
|
||||
|
||||
// waitForContainer waits until a container has exited
|
||||
func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout int64) int64 {
|
||||
rc, err := cli.ContainerWait(context.Background(), ID)
|
||||
|
||||
func waitForContainer(t *testing.T, cli *client.Client, ID string, timeout time.Duration) int64 {
|
||||
c, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
rc, err := cli.ContainerWait(c, ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if coverage() {
|
||||
// COVERAGE: When running coverage, the exit code is written to a file,
|
||||
// to allow the coverage to be generated (which doesn't happen for non-zero
|
||||
// exit codes)
|
||||
rc = getCoverageExitCode(t, rc)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
@@ -395,7 +520,7 @@ func execContainer(t *testing.T, cli *client.Client, ID string, user string, cmd
|
||||
}
|
||||
|
||||
func waitForReady(t *testing.T, cli *client.Client, ID string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
@@ -405,6 +530,9 @@ func waitForReady(t *testing.T, cli *client.Client, ID string) {
|
||||
if rc == 0 {
|
||||
t.Log("MQ is ready")
|
||||
return
|
||||
} else if rc == 10 {
|
||||
t.Log("MQ Readiness: Queue Manager Running as Standby")
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("Timed out waiting for container to become ready")
|
||||
@@ -440,17 +568,17 @@ func removeNetwork(t *testing.T, cli *client.Client, ID string) {
|
||||
}
|
||||
}
|
||||
|
||||
func createVolume(t *testing.T, cli *client.Client) types.Volume {
|
||||
func createVolume(t *testing.T, cli *client.Client, name string) types.Volume {
|
||||
v, err := cli.VolumeCreate(context.Background(), volume.VolumesCreateBody{
|
||||
Driver: "local",
|
||||
DriverOpts: map[string]string{},
|
||||
Labels: map[string]string{},
|
||||
Name: t.Name(),
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Created volume %v", t.Name())
|
||||
t.Logf("Created volume %v", v.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
|
||||
234
test/docker/mq_multi_instance_test.go
Normal file
234
test/docker/mq_multi_instance_test.go
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
var miEnv = []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_MULTI_INSTANCE=true",
|
||||
}
|
||||
|
||||
// TestMultiInstanceStartStop creates 2 containers in a multi instance queue manager configuration
|
||||
// and starts/stop them checking we always have an active and standby
|
||||
func TestMultiInstanceStartStop(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
defer removeVolume(t, cli, volume)
|
||||
}
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
defer cleanContainer(t, cli, qm1bId)
|
||||
|
||||
waitForReady(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1bId)
|
||||
|
||||
err, active, standby := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
killContainer(t, cli, active, "SIGTERM")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if status := getQueueManagerStatus(t, cli, standby, "QM1"); strings.Compare(status, "Running") != 0 {
|
||||
t.Fatalf("Expected QM1 to be running as active queue manager, dspmq returned status of %v", status)
|
||||
}
|
||||
|
||||
startContainer(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1aId)
|
||||
|
||||
err, _, _ = getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestMultiInstanceContainerStop starts 2 containers in a multi instance queue manager configuration,
|
||||
// stops the active queue manager, then checks to ensure the backup queue manager becomes active
|
||||
func TestMultiInstanceContainerStop(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
defer removeVolume(t, cli, volume)
|
||||
}
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
defer cleanContainer(t, cli, qm1bId)
|
||||
|
||||
waitForReady(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1bId)
|
||||
|
||||
err, active, standby := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stopContainer(t, cli, active)
|
||||
|
||||
if status := getQueueManagerStatus(t, cli, standby, "QM1"); strings.Compare(status, "Running") != 0 {
|
||||
t.Fatalf("Expected QM1 to be running as active queue manager, dspmq returned status of %v", status)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiInstanceRace starts 2 containers in separate goroutines in a multi instance queue manager
|
||||
// configuration, then checks to ensure that both an active and standby queue manager have been started
|
||||
func TestMultiInstanceRace(t *testing.T) {
|
||||
t.Skipf("Skipping %v until file lock is implemented", t.Name())
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
defer removeVolume(t, cli, qmsharedlogs.Name)
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
defer removeVolume(t, cli, qmshareddata.Name)
|
||||
|
||||
qmsChannel := make(chan QMChan)
|
||||
|
||||
go singleMultiInstanceQueueManager(t, cli, qmsharedlogs.Name, qmshareddata.Name, qmsChannel)
|
||||
go singleMultiInstanceQueueManager(t, cli, qmsharedlogs.Name, qmshareddata.Name, qmsChannel)
|
||||
|
||||
qm1a := <-qmsChannel
|
||||
if qm1a.Error != nil {
|
||||
t.Fatal(qm1a.Error)
|
||||
}
|
||||
|
||||
qm1b := <-qmsChannel
|
||||
if qm1b.Error != nil {
|
||||
t.Fatal(qm1b.Error)
|
||||
}
|
||||
|
||||
qm1aId, qm1aData := qm1a.QMId, qm1a.QMData
|
||||
qm1bId, qm1bData := qm1b.QMId, qm1b.QMData
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer removeVolume(t, cli, qm1bData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
defer cleanContainer(t, cli, qm1bId)
|
||||
|
||||
waitForReady(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1bId)
|
||||
|
||||
err, _, _ = getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoSharedMounts starts 2 multi instance queue managers without providing shared log/data
|
||||
// mounts, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoSharedMounts(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", "", miEnv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-log'", 30*time.Second)
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoSharedLogs starts 2 multi instance queue managers without providing a shared log
|
||||
// mount, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoSharedLogs(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
defer removeVolume(t, cli, qmshareddata.Name)
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata.Name, miEnv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-log'", 30*time.Second)
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoSharedData starts 2 multi instance queue managers without providing a shared data
|
||||
// mount, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoSharedData(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
defer removeVolume(t, cli, qmsharedlogs.Name)
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, "", miEnv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-data'", 30*time.Second)
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoMounts starts 2 multi instance queue managers without providing a shared data
|
||||
// mount, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoMounts(t *testing.T) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, false, "", "", miEnv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm'", 30*time.Second)
|
||||
}
|
||||
178
test/docker/mq_multi_instance_test_util.go
Normal file
178
test/docker/mq_multi_instance_test_util.go
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019
|
||||
|
||||
Licensed 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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type QMChan struct {
|
||||
QMId string
|
||||
QMData string
|
||||
Error error
|
||||
}
|
||||
|
||||
// configureMultiInstance creates the volumes and containers required for basic testing
|
||||
// of multi instance queue managers. Returns error, qm1a ID, qm1b ID, slice of volume names
|
||||
func configureMultiInstance(t *testing.T, cli *client.Client) (error, string, string, []string) {
|
||||
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, miEnv)
|
||||
if err != nil {
|
||||
return err, "", "", []string{}
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
err, qm1bId, qm1bData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs.Name, qmshareddata.Name, miEnv)
|
||||
if err != nil {
|
||||
return err, "", "", []string{}
|
||||
}
|
||||
|
||||
volumes := []string{qmsharedlogs.Name, qmshareddata.Name, qm1aData, qm1bData}
|
||||
|
||||
return nil, qm1aId, qm1bId, volumes
|
||||
}
|
||||
|
||||
func singleMultiInstanceQueueManager(t *testing.T, cli *client.Client, qmsharedlogs string, qmshareddata string, qmsChannel chan QMChan) {
|
||||
err, qmId, qmData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv)
|
||||
if err != nil {
|
||||
qmsChannel <- QMChan{Error: err}
|
||||
}
|
||||
qmsChannel <- QMChan{QMId: qmId, QMData: qmData}
|
||||
}
|
||||
|
||||
func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata string, qmdata string) container.HostConfig {
|
||||
|
||||
var hostConfig container.HostConfig
|
||||
|
||||
switch mounts {
|
||||
case 1:
|
||||
hostConfig = container.HostConfig{
|
||||
Binds: []string{
|
||||
coverageBind(t),
|
||||
qmdata + ":/mnt/mqm",
|
||||
},
|
||||
}
|
||||
case 2:
|
||||
hostConfig = container.HostConfig{
|
||||
Binds: []string{
|
||||
coverageBind(t),
|
||||
qmdata + ":/mnt/mqm",
|
||||
qmshareddata + ":/mnt/mqm-data",
|
||||
},
|
||||
}
|
||||
case 3:
|
||||
hostConfig = container.HostConfig{
|
||||
Binds: []string{
|
||||
coverageBind(t),
|
||||
qmdata + ":/mnt/mqm",
|
||||
qmsharedlogs + ":/mnt/mqm-log",
|
||||
},
|
||||
}
|
||||
case 4:
|
||||
hostConfig = container.HostConfig{
|
||||
Binds: []string{
|
||||
coverageBind(t),
|
||||
qmdata + ":/mnt/mqm",
|
||||
qmsharedlogs + ":/mnt/mqm-log",
|
||||
qmshareddata + ":/mnt/mqm-data",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return hostConfig
|
||||
}
|
||||
|
||||
func startMultiVolumeQueueManager(t *testing.T, cli *client.Client, dataVol bool, qmsharedlogs string, qmshareddata string, env []string) (error, string, string) {
|
||||
id := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
qmdata := createVolume(t, cli, id)
|
||||
containerConfig := container.Config{
|
||||
Image: imageName(),
|
||||
Env: env,
|
||||
}
|
||||
var hostConfig container.HostConfig
|
||||
|
||||
if !dataVol {
|
||||
hostConfig = container.HostConfig{}
|
||||
} else if qmsharedlogs == "" && qmshareddata == "" {
|
||||
hostConfig = getHostConfig(t, 1, "", "", qmdata.Name)
|
||||
} else if qmsharedlogs == "" {
|
||||
hostConfig = getHostConfig(t, 2, "", qmshareddata, qmdata.Name)
|
||||
} else if qmshareddata == "" {
|
||||
hostConfig = getHostConfig(t, 3, qmsharedlogs, "", qmdata.Name)
|
||||
} else {
|
||||
hostConfig = getHostConfig(t, 4, qmsharedlogs, qmshareddata, qmdata.Name)
|
||||
}
|
||||
networkingConfig := network.NetworkingConfig{}
|
||||
qm, err := cli.ContainerCreate(context.Background(), &containerConfig, &hostConfig, &networkingConfig, t.Name()+id)
|
||||
if err != nil {
|
||||
return err, "", ""
|
||||
}
|
||||
startContainer(t, cli, qm.ID)
|
||||
|
||||
return nil, qm.ID, qmdata.Name
|
||||
}
|
||||
|
||||
func getActiveStandbyQueueManager(t *testing.T, cli *client.Client, qm1aId string, qm1bId string) (error, string, string) {
|
||||
qm1aStatus := getQueueManagerStatus(t, cli, qm1aId, "QM1")
|
||||
qm1bStatus := getQueueManagerStatus(t, cli, qm1bId, "QM1")
|
||||
|
||||
if qm1aStatus == "Running" && qm1bStatus == "Running as standby" {
|
||||
return nil, qm1aId, qm1bId
|
||||
} else if qm1bStatus == "Running" && qm1aStatus == "Running as standby" {
|
||||
return nil, qm1bId, qm1aId
|
||||
}
|
||||
err := fmt.Errorf("Expected to be running in multi instance configuration, got status 1) %v status 2) %v", qm1aStatus, qm1bStatus)
|
||||
return err, "", ""
|
||||
}
|
||||
|
||||
func getQueueManagerStatus(t *testing.T, cli *client.Client, containerID string, queueManagerName string) string {
|
||||
_, dspmqOut := execContainer(t, cli, containerID, "mqm", []string{"bash", "-c", "dspmq", "-m", queueManagerName})
|
||||
regex := regexp.MustCompile(`STATUS\(.*\)`)
|
||||
status := regex.FindString(dspmqOut)
|
||||
status = strings.TrimSuffix(strings.TrimPrefix(status, "STATUS("), ")")
|
||||
return status
|
||||
}
|
||||
|
||||
func waitForTerminationMessage(t *testing.T, cli *client.Client, qmId string, terminationString string, timeout time.Duration) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
m := terminationMessage(t, cli, qmId)
|
||||
if m != "" {
|
||||
if !strings.Contains(m, terminationString) {
|
||||
t.Fatalf("Expected container to fail on missing required mount. Got termination message: %v", m)
|
||||
}
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("Timed out waiting for container to terminate")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018
|
||||
© Copyright IBM Corporation 2018, 2019
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
func TestGoldenPathMetric(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -53,6 +54,7 @@ func TestGoldenPathMetric(t *testing.T) {
|
||||
|
||||
func TestMetricNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -96,6 +98,7 @@ func TestMetricNames(t *testing.T) {
|
||||
|
||||
func TestMetricLabels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
requiredLabels := []string{"qmgr"}
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
@@ -144,6 +147,7 @@ func TestMetricLabels(t *testing.T) {
|
||||
|
||||
func TestRapidFirePrometheus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -177,6 +181,7 @@ func TestRapidFirePrometheus(t *testing.T) {
|
||||
|
||||
func TestSlowPrometheus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -207,6 +212,7 @@ func TestSlowPrometheus(t *testing.T) {
|
||||
|
||||
func TestContainerRestart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -254,6 +260,7 @@ func TestContainerRestart(t *testing.T) {
|
||||
|
||||
func TestQMRestart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -279,7 +286,7 @@ func TestQMRestart(t *testing.T) {
|
||||
|
||||
// Restart just the QM (to simulate a lost connection)
|
||||
t.Log("Stopping queue manager\n")
|
||||
rc, out := execContainer(t, cli, id, "mqm", []string{"endmqm", "-w", defaultMetricQMName})
|
||||
rc, out := execContainer(t, cli, id, "mqm", []string{"endmqm", "-w", "-r", defaultMetricQMName})
|
||||
if rc != 0 {
|
||||
t.Fatalf("Failed to stop the queue manager. rc=%d, err=%s", rc, out)
|
||||
}
|
||||
@@ -311,6 +318,7 @@ func TestQMRestart(t *testing.T) {
|
||||
|
||||
func TestValidValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -346,6 +354,7 @@ func TestValidValues(t *testing.T) {
|
||||
|
||||
func TestChangingValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# © Copyright IBM Corporation 2018
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,7 @@
|
||||
###############################################################################
|
||||
# Application build environment (Maven)
|
||||
###############################################################################
|
||||
FROM maven:3-ibmjava as builder
|
||||
FROM docker.io/maven:3-ibmjava as builder
|
||||
COPY pom.xml /usr/src/mymaven/
|
||||
WORKDIR /usr/src/mymaven
|
||||
# Download dependencies separately, so Docker caches them
|
||||
@@ -30,7 +30,7 @@ RUN find /usr/src/mymaven
|
||||
###############################################################################
|
||||
# Application runtime (JRE only, no build environment)
|
||||
###############################################################################
|
||||
FROM ibmjava:sfj
|
||||
FROM docker.io/ibmjava:8-jre
|
||||
COPY --from=builder /usr/src/mymaven/target/*.jar /opt/app/
|
||||
COPY --from=builder /usr/src/mymaven/target/lib/*.jar /opt/app/
|
||||
ENTRYPOINT ["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"]
|
||||
|
||||
84
test/messaging/buildah.sh
Executable file
84
test/messaging/buildah.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
###############################################################################
|
||||
# Setup MQ JMS Test container
|
||||
###############################################################################
|
||||
|
||||
# Use a "scratch" container, so the resulting image has minimal files
|
||||
# Resulting image won't have yum, for example
|
||||
readonly ctr_mq=$(buildah from rhel7-minimal)
|
||||
readonly mnt_mq=$(buildah mount $ctr_mq)
|
||||
readonly imagename=$1
|
||||
|
||||
microdnf_opts="--nodocs"
|
||||
# Check whether the host is registered with Red Hat
|
||||
if subscription-manager status ; then
|
||||
# Host is subscribed, but the minimal image has no enabled repos
|
||||
# Note that the "bc" package is the only one in "extras"
|
||||
microdnf_opts="${microdnf_opts} --enablerepo=rhel-7-server-rpms --enablerepo=rhel-7-server-extras-rpms"
|
||||
else
|
||||
# Use the Yum repositories configured on the host
|
||||
cp -R /etc/yum.repos.d/* ${mnt_mq}/etc/yum.repos.d/
|
||||
fi
|
||||
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} install \
|
||||
java-1.8.0-openjdk-devel \
|
||||
java \
|
||||
which \
|
||||
wget
|
||||
|
||||
buildah run $ctr_mq -- sh -c "cd /tmp && wget https://www-eu.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.tar.gz"
|
||||
tar xvf $mnt_mq/tmp/apache-maven-3.6.0-bin.tar.gz -C $mnt_mq/tmp/
|
||||
|
||||
mkdir -p $mnt_mq/usr/src/mymaven
|
||||
cp pom.xml $mnt_mq/usr/src/mymaven/
|
||||
cp -R src $mnt_mq/usr/src/mymaven/src
|
||||
|
||||
buildah run $ctr_mq -- sh -c "cd /usr/src/mymaven && export M2_HOME=/tmp/apache-maven-3.6.0 && export M2=\$M2_HOME/bin && export PATH=\$M2:\$PATH && mvn --version && mvn dependency:go-offline install && mvn --offline install"
|
||||
|
||||
mkdir -p $mnt_mq/opt/app
|
||||
|
||||
cp $mnt_mq/usr/src/mymaven/target/*.jar $mnt_mq/opt/app/
|
||||
cp $mnt_mq/usr/src/mymaven/target/lib/*.jar $mnt_mq/opt/app/
|
||||
|
||||
###############################################################################
|
||||
# Post install tidy up
|
||||
###############################################################################
|
||||
|
||||
rm -rf $mnt_mq/tmp/*
|
||||
rm -rf $mnt_mq/usr/src/mymaven
|
||||
|
||||
# Clean up cached files
|
||||
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} clean all
|
||||
rm -rf ${mnt_mq}/etc/yum.repos.d/*
|
||||
|
||||
###############################################################################
|
||||
# Contain image finalization
|
||||
###############################################################################
|
||||
|
||||
buildah config \
|
||||
--os linux \
|
||||
--label architecture=amd64 \
|
||||
--label name="${imagename%:*}" \
|
||||
--cmd "" \
|
||||
--entrypoint '["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"]' \
|
||||
$ctr_mq
|
||||
buildah unmount $ctr_mq
|
||||
buildah commit $ctr_mq $imagename
|
||||
|
||||
buildah rm $ctr_mq
|
||||
@@ -32,19 +32,19 @@ limitations under the License.
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<version>5.3.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.2.0</version>
|
||||
<version>5.3.2</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-console-standalone</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<version>1.3.2</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -73,6 +73,10 @@ class JMSTests {
|
||||
factory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
|
||||
factory.setChannel(channel);
|
||||
factory.setConnectionNameList(String.format("%s(1414)", addr));
|
||||
// If a password is set, make sure it gets sent to the queue manager for authentication
|
||||
if (password != null) {
|
||||
factory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true);
|
||||
}
|
||||
// factory.setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
|
||||
if (TRUSTSTORE == null) {
|
||||
LOGGER.info("Not using TLS");
|
||||
@@ -81,8 +85,14 @@ class JMSTests {
|
||||
LOGGER.info(String.format("Using TLS. Trust store=%s", TRUSTSTORE));
|
||||
SSLSocketFactory ssl = createSSLSocketFactory();
|
||||
factory.setSSLSocketFactory(ssl);
|
||||
boolean ibmjre = System.getenv("IBMJRE").equals("true");
|
||||
if (ibmjre){
|
||||
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "true");
|
||||
factory.setSSLCipherSuite("SSL_RSA_WITH_AES_128_CBC_SHA256");
|
||||
// LOGGER.info(Arrays.toString(ssl.getSupportedCipherSuites()));
|
||||
} else {
|
||||
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false");
|
||||
factory.setSSLCipherSuite("TLS_RSA_WITH_AES_128_CBC_SHA256");
|
||||
}
|
||||
}
|
||||
// Give up if unable to reconnect for 10 minutes
|
||||
// factory.setClientReconnectTimeout(600);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user