From 812d6c4738c0a44e8c5508c226fd42439bc6ddf1 Mon Sep 17 00:00:00 2001 From: convery Date: Fri, 1 Apr 2022 13:14:08 +0100 Subject: [PATCH] New Simplified ACE image - 12.0.4.0-r1 --- ApplyIFixes.sh | 38 - CHANGELOG.md | 81 - Dockerfile | 53 + LICENSE | 909 ------- PULL_REQUEST_TEMPLATE.md | 13 - README.md | 303 +-- ace_compile_bars.sh | 19 - ace_config_agent.sh | 40 - ace_config_bar_overrides.sh | 23 - ace_config_bars.sh | 19 - ace_config_extensions.sh | 20 - ace_config_keystore.sh | 64 - ace_config_odbcini.sh | 24 - ace_config_policy.sh | 29 - ace_config_serverconf.sh | 19 - ace_config_ssl.sh | 25 - ace_config_truststore.sh | 44 - ace_config_workdir_overrides.sh | 19 - ace_env.sh | 12 - ace_forceflowhttps.sh | 64 - ace_integration_server.sh | 39 - ace_license_check.sh | 39 - ace_mqsicommand.sh | 14 - appconnect_enterprise_logo.svg | 20 - build-rhel.sh | 49 - cmd/chkacehealthy/.gitignore | 1 - cmd/chkacehealthy/main.go | 177 -- cmd/chkacehealthy/main_test.go | 134 - cmd/chkaceready/main.go | 35 - cmd/runaceserver/integrationserver.go | 1230 --------- .../integrationserver_internal_test.go | 1240 --------- cmd/runaceserver/integrationserver_test.go | 438 ---- cmd/runaceserver/license.go | 92 - cmd/runaceserver/license_internal_test.go | 282 -- cmd/runaceserver/logging.go | 95 - cmd/runaceserver/main.go | 274 -- cmd/runaceserver/main_internal_test.go | 61 - cmd/runaceserver/signals.go | 81 - cmd/runaceserver/version.go | 100 - cmd/runaceserver/version_internal_test.go | 90 - common/contentserver/bar.go | 78 - common/contentserver/bar_test.go | 104 - common/contentserver/mock_client.go | 48 - common/designer/flow_validation.go | 250 -- common/designer/flow_validation_test.go | 679 ----- common/designer/license_toggles.go | 73 - common/designer/license_toggles_test.go | 102 - common/logger/logger.go | 219 -- common/logger/logger_test.go | 80 - deps/.gitignore | 6 - deps/CSAPI/META-INF/bar-refresh.links | 13 - deps/CSAPI/META-INF/broker.xml | 55 - deps/CSAPI/META-INF/manifest.mf | 1 - deps/CSAPI/META-INF/service.log | 36 - deps/CSAPI/META-INF/user.log | 37 - deps/CSAPI/Proxy.cmf | 75 - deps/CSAPI/application.descriptor | 1 - deps/OpenTracing/config/README | 1 - deps/OpenTracing/library/README | 1 - deps/package-connectors.json | 10 - experimental/ace-basic/Dockerfile.ubuntu | 6 +- git.commit | 1 - go.mod | 55 - go.sum | 778 ------ internal/command/command.go | 252 -- internal/command/command_test.go | 49 - internal/configuration/configuration.go | 659 ----- internal/configuration/configuration_test.go | 1037 -------- internal/configuration/ioutil.go | 41 - .../techConnectorsConfiguration.go | 951 ------- .../techConnectorsConfiguration_test.go | 2018 --------------- internal/isCommandsApi/apiServer_test.go | 250 -- internal/isCommandsApi/apiserver.go | 193 -- internal/isCommandsApi/dbparamsHandler.go | 83 - .../isCommandsApi/dbparamsHandler_test.go | 198 -- internal/metrics/exporter.go | 185 -- internal/metrics/exporter_test.go | 314 --- internal/metrics/mapping.go | 280 -- internal/metrics/mapping_test.go | 147 -- internal/metrics/metrics.go | 109 - internal/metrics/update.go | 593 ----- internal/metrics/update_test.go | 550 ---- internal/name/name.go | 45 - internal/name/name_internal_test.go | 40 - internal/name/name_test.go | 38 - internal/trace/trace_handler.go | 433 ---- internal/trace/trace_handler_test.go | 859 ------ .../initial-config/webusers/admin-users.txt | 1 - .../initial-config/webusers/audit-users.txt | 1 - .../initial-config/webusers/editor-users.txt | 1 - .../webusers/operator-users.txt | 1 - .../initial-config/webusers/server.conf.yaml | 4 - .../initial-config/webusers/viewer-users.txt | 1 - internal/webadmin/webadmin.go | 222 -- internal/webadmin/webadmin_test.go | 490 ---- licenses/LICENSE | 202 ++ licenses/non_ibm_license | Bin 57010 -> 0 bytes licenses/notices | Bin 685218 -> 0 bytes sample/Dockerfile.aceonly | 16 - sample/PIs/CallHTTPSEcho.zip | Bin 1770 -> 0 bytes sample/PIs/CallMQEcho.zip | Bin 2721 -> 0 bytes sample/PIs/CallMQMultiEcho.zip | Bin 4471 -> 0 bytes sample/PIs/HTTPSEcho.zip | Bin 2136 -> 0 bytes sample/PIs/MQEcho.zip | Bin 2115 -> 0 bytes sample/PIs/MQMultiEcho.zip | Bin 2195 -> 0 bytes sample/README.md | 48 - sample/bars_aceonly/CallHTTPSEcho.bar | Bin 4197 -> 0 bytes sample/bars_aceonly/HTTPSEcho.bar | Bin 4407 -> 0 bytes .../dashboards/ibm-ace-grafana-dashboard.json | 2305 ----------------- .../dashboards/ibm-ace-kibana5-dashboard.json | 132 - sample/initial-config/keystore/extra-key.crt | 22 - sample/initial-config/keystore/extra-key.key | 30 - sample/initial-config/keystore/extra-key.pass | 1 - sample/initial-config/keystore/mykey.crt | 22 - sample/initial-config/keystore/mykey.key | 30 - sample/initial-config/keystore/mykey.pass | 1 - sample/initial-config/mqsc/config.mqsc | 1 - sample/initial-config/odbcini/odbc.ini | 40 - .../initial-config/policy/default.policyxml | 29 - .../initial-config/policy/policy.descriptor | 4 - .../serverconf/server.conf.yaml | 17 - .../initial-config/setdbparms/setdbparms.txt | 3 - sample/initial-config/truststore/cacert.crt | 22 - .../initial-config/truststore/extra-cert.crt | 22 - .../initial-config/webusers/admin-users.txt | 2 - .../initial-config/webusers/viewer-users.txt | 2 - samples/README.md | 9 + samples/applyIfix/Dockerfile | 14 + samples/applyIfix/README.md | 17 + samples/applyIfix/ifix/.gitignore | 2 + samples/bars/CustomerDatabaseV1.bar | Bin 0 -> 92474 bytes samples/bars/Dockerfile | 12 + samples/bars/README.md | 34 + samples/bars/test.sh | 24 + samples/scripts/Dockerfile | 16 + samples/scripts/README.md | 26 + .../scripts/ace_config_logging.sh | 17 +- .../scripts/ace_config_setdbparms.sh | 35 +- samples/scripts/server.conf.yaml | 3 + samples/scripts/setdbparms/setdbparms.txt | 6 + samples/updateBase/Dockerfile | 8 + samples/updateBase/README.md | 14 + test-and-coverage.sh | 38 - test.sh | 24 + ubi/Dockerfile-legacy.aceonly | 113 - ubi/Dockerfile.aceonly | 119 - ubi/Dockerfile.connectors | 17 - ubi/Dockerfile.ifix | 20 - ubi/Dockerfile.mqclient | 72 - ubi/create-default-mq-kdb.sh | 44 - .../ACE toolkit project interchange.zip | Bin 2888 -> 0 bytes ubi/generic_invalid/InvalidLicenseJava.jar | Bin 2470 -> 0 bytes ubi/generic_invalid/application.descriptor | 1 - ubi/generic_invalid/invalid_license.msgflow | 24 - ubi/install-mq-client-prereqs.sh | 32 - ubi/install-mq.sh | 112 - 156 files changed, 521 insertions(+), 22346 deletions(-) delete mode 100755 ApplyIFixes.sh delete mode 100644 CHANGELOG.md create mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 PULL_REQUEST_TEMPLATE.md delete mode 100755 ace_compile_bars.sh delete mode 100755 ace_config_agent.sh delete mode 100755 ace_config_bar_overrides.sh delete mode 100755 ace_config_bars.sh delete mode 100755 ace_config_extensions.sh delete mode 100755 ace_config_keystore.sh delete mode 100755 ace_config_odbcini.sh delete mode 100755 ace_config_policy.sh delete mode 100755 ace_config_serverconf.sh delete mode 100755 ace_config_ssl.sh delete mode 100755 ace_config_truststore.sh delete mode 100755 ace_config_workdir_overrides.sh delete mode 100755 ace_env.sh delete mode 100755 ace_forceflowhttps.sh delete mode 100755 ace_integration_server.sh delete mode 100755 ace_license_check.sh delete mode 100755 ace_mqsicommand.sh delete mode 100644 appconnect_enterprise_logo.svg delete mode 100755 build-rhel.sh delete mode 100644 cmd/chkacehealthy/.gitignore delete mode 100644 cmd/chkacehealthy/main.go delete mode 100644 cmd/chkacehealthy/main_test.go delete mode 100644 cmd/chkaceready/main.go delete mode 100644 cmd/runaceserver/integrationserver.go delete mode 100644 cmd/runaceserver/integrationserver_internal_test.go delete mode 100644 cmd/runaceserver/integrationserver_test.go delete mode 100644 cmd/runaceserver/license.go delete mode 100644 cmd/runaceserver/license_internal_test.go delete mode 100644 cmd/runaceserver/logging.go delete mode 100644 cmd/runaceserver/main.go delete mode 100644 cmd/runaceserver/main_internal_test.go delete mode 100644 cmd/runaceserver/signals.go delete mode 100644 cmd/runaceserver/version.go delete mode 100644 cmd/runaceserver/version_internal_test.go delete mode 100644 common/contentserver/bar.go delete mode 100644 common/contentserver/bar_test.go delete mode 100644 common/contentserver/mock_client.go delete mode 100644 common/designer/flow_validation.go delete mode 100644 common/designer/flow_validation_test.go delete mode 100644 common/designer/license_toggles.go delete mode 100644 common/designer/license_toggles_test.go delete mode 100644 common/logger/logger.go delete mode 100644 common/logger/logger_test.go delete mode 100644 deps/.gitignore delete mode 100644 deps/CSAPI/META-INF/bar-refresh.links delete mode 100644 deps/CSAPI/META-INF/broker.xml delete mode 100644 deps/CSAPI/META-INF/manifest.mf delete mode 100644 deps/CSAPI/META-INF/service.log delete mode 100644 deps/CSAPI/META-INF/user.log delete mode 100644 deps/CSAPI/Proxy.cmf delete mode 100644 deps/CSAPI/application.descriptor delete mode 100644 deps/OpenTracing/config/README delete mode 100644 deps/OpenTracing/library/README delete mode 100644 deps/package-connectors.json delete mode 100644 git.commit delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 internal/command/command.go delete mode 100644 internal/command/command_test.go delete mode 100644 internal/configuration/configuration.go delete mode 100644 internal/configuration/configuration_test.go delete mode 100644 internal/configuration/ioutil.go delete mode 100644 internal/configuration/techConnectorsConfiguration.go delete mode 100644 internal/configuration/techConnectorsConfiguration_test.go delete mode 100644 internal/isCommandsApi/apiServer_test.go delete mode 100644 internal/isCommandsApi/apiserver.go delete mode 100644 internal/isCommandsApi/dbparamsHandler.go delete mode 100644 internal/isCommandsApi/dbparamsHandler_test.go delete mode 100644 internal/metrics/exporter.go delete mode 100644 internal/metrics/exporter_test.go delete mode 100644 internal/metrics/mapping.go delete mode 100644 internal/metrics/mapping_test.go delete mode 100644 internal/metrics/metrics.go delete mode 100644 internal/metrics/update.go delete mode 100644 internal/metrics/update_test.go delete mode 100644 internal/name/name.go delete mode 100644 internal/name/name_internal_test.go delete mode 100644 internal/name/name_test.go delete mode 100644 internal/trace/trace_handler.go delete mode 100644 internal/trace/trace_handler_test.go delete mode 100644 internal/webadmin/testdata/initial-config/webusers/admin-users.txt delete mode 100644 internal/webadmin/testdata/initial-config/webusers/audit-users.txt delete mode 100644 internal/webadmin/testdata/initial-config/webusers/editor-users.txt delete mode 100644 internal/webadmin/testdata/initial-config/webusers/operator-users.txt delete mode 100644 internal/webadmin/testdata/initial-config/webusers/server.conf.yaml delete mode 100644 internal/webadmin/testdata/initial-config/webusers/viewer-users.txt delete mode 100644 internal/webadmin/webadmin.go delete mode 100644 internal/webadmin/webadmin_test.go create mode 100644 licenses/LICENSE delete mode 100644 licenses/non_ibm_license delete mode 100644 licenses/notices delete mode 100644 sample/Dockerfile.aceonly delete mode 100644 sample/PIs/CallHTTPSEcho.zip delete mode 100644 sample/PIs/CallMQEcho.zip delete mode 100644 sample/PIs/CallMQMultiEcho.zip delete mode 100644 sample/PIs/HTTPSEcho.zip delete mode 100644 sample/PIs/MQEcho.zip delete mode 100644 sample/PIs/MQMultiEcho.zip delete mode 100644 sample/README.md delete mode 100644 sample/bars_aceonly/CallHTTPSEcho.bar delete mode 100644 sample/bars_aceonly/HTTPSEcho.bar delete mode 100644 sample/dashboards/ibm-ace-grafana-dashboard.json delete mode 100644 sample/dashboards/ibm-ace-kibana5-dashboard.json delete mode 100644 sample/initial-config/keystore/extra-key.crt delete mode 100644 sample/initial-config/keystore/extra-key.key delete mode 100644 sample/initial-config/keystore/extra-key.pass delete mode 100644 sample/initial-config/keystore/mykey.crt delete mode 100644 sample/initial-config/keystore/mykey.key delete mode 100644 sample/initial-config/keystore/mykey.pass delete mode 100644 sample/initial-config/mqsc/config.mqsc delete mode 100644 sample/initial-config/odbcini/odbc.ini delete mode 100644 sample/initial-config/policy/default.policyxml delete mode 100644 sample/initial-config/policy/policy.descriptor delete mode 100644 sample/initial-config/serverconf/server.conf.yaml delete mode 100644 sample/initial-config/setdbparms/setdbparms.txt delete mode 100644 sample/initial-config/truststore/cacert.crt delete mode 100644 sample/initial-config/truststore/extra-cert.crt delete mode 100644 sample/initial-config/webusers/admin-users.txt delete mode 100644 sample/initial-config/webusers/viewer-users.txt create mode 100644 samples/README.md create mode 100644 samples/applyIfix/Dockerfile create mode 100644 samples/applyIfix/README.md create mode 100644 samples/applyIfix/ifix/.gitignore create mode 100644 samples/bars/CustomerDatabaseV1.bar create mode 100644 samples/bars/Dockerfile create mode 100644 samples/bars/README.md create mode 100755 samples/bars/test.sh create mode 100644 samples/scripts/Dockerfile create mode 100644 samples/scripts/README.md rename ace_config_logging.sh => samples/scripts/ace_config_logging.sh (58%) rename ace_config_setdbparms.sh => samples/scripts/ace_config_setdbparms.sh (54%) create mode 100644 samples/scripts/server.conf.yaml create mode 100644 samples/scripts/setdbparms/setdbparms.txt create mode 100644 samples/updateBase/Dockerfile create mode 100644 samples/updateBase/README.md delete mode 100755 test-and-coverage.sh create mode 100755 test.sh delete mode 100644 ubi/Dockerfile-legacy.aceonly delete mode 100644 ubi/Dockerfile.aceonly delete mode 100644 ubi/Dockerfile.connectors delete mode 100644 ubi/Dockerfile.ifix delete mode 100644 ubi/Dockerfile.mqclient delete mode 100755 ubi/create-default-mq-kdb.sh delete mode 100644 ubi/generic_invalid/ACE toolkit project interchange.zip delete mode 100644 ubi/generic_invalid/InvalidLicenseJava.jar delete mode 100644 ubi/generic_invalid/application.descriptor delete mode 100644 ubi/generic_invalid/invalid_license.msgflow delete mode 100755 ubi/install-mq-client-prereqs.sh delete mode 100755 ubi/install-mq.sh diff --git a/ApplyIFixes.sh b/ApplyIFixes.sh deleted file mode 100755 index 1d70c26..0000000 --- a/ApplyIFixes.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Check if any iFix's are to be applied. - -if [ -z $1 ]; then - echo "No iFix's being used skipping this phase." - exit 0 # No iFix specified -else # Create array of iFix's to loop through by removing commas from input. - IFIX_LIST_ARRAY=$(echo $1 | tr ',' ' ') - echo "iFix's being applied: ${IFIX_LIST_ARRAY}" -fi - -for ifixlink in $IFIX_LIST_ARRAY -do - # Make temporary fix directory - mkdir ./fix - cd fix - - # Download and unzip iFix tar file. - curl -Ls $ifixlink | tar -xz - - # Execute install command - ifixname="${ifixlink##*/}" - ifixname="${ifixname%.tar*}" - - ./mqsifixinst.sh /opt/ibm/ace-12 install $ifixname - - # Delete directory - cd .. - rm -rf ./fix - - rm -rf /opt/ibm/ace-12/fix-backups.12.0.1.0 - rm /opt/ibm/ace-12/mqsifixinst.log - rm /opt/ibm/ace-12/mqsifixinst.sh - - # We explicitly screen out /tools and TransformationAdvisor, so let's make sure to clean up after any iFixes that put them back - rm -rf /opt/ibm/ace-12/tools /opt/ibm/ace-12/server/bin/TADataCollector.sh /opt/ibm/ace-12/server/transformationAdvisor -done diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c15aeb0..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,81 +0,0 @@ -# Change log - -## 12.0.3.0-r2 - -**Updates** - -* Ability to override Statistics->Resource in server.conf.yaml -* Improved the logging for metrics when authentication is not enabled -* Includes IT39515 -* Includes IT39573 -* Includes IT39917 -* Remove need for python -* Fix for CVE-2022-21698 - -## 12.0.3.0-r1 - -**Updates** - -* Fix bug with capitlisation on "insecureSsl" for barAuth json - -## 11.0.0.6.1 (2019-11-20) - -**Updates**: - -* Updated kubectl to version v1.16.0 -* Updated MQ to version 9.1.3.0-r3 -* Added support for hostname and port overrides when routes are defined -* Created ACE roles for five different access levels: admin, operator, viewer, editor, and audit - -## 11.0.0.6 (2019-10-30) - -**Changes**: - -* Updated to use the 11.0.0.6 build -* Support metrics when Integration Server is using TLS - -## 11.0.0.5.1 (2019-09-24) - -**Updates**: - -* New image that includes an MQ client -* Supports MQ 9.1.3 images -* Support for defining custom ports -* Support for running switches -* Ability to set up operator, editor, and audit users for the ACE web UI, in addition to admin and viewer users -* Support for LEL User Exit files - -## 11.0.0.5 (2019-07-05) - -**Breaking changes**: - -* When using MQ, the 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 - -**Updates**: - -* Security fixes -* Web console added to production image -* Container built on RedHat host (UBI) -* Includes MQ 9.1.2 -* Supports configuring agent files -* Supports installing additional config files using extensions.zip - -## 11.0.0.3 (2019-02-04) - -**Other changes**: - -* Provides samples for building image with MQ Client -* Code to generate RHEL based images -* Fix for overriding the hostname and port for RestAPI in the UI / Swagger Docs. - -## 11.0.0.2 (2018-11-20) - -**Updates**: - -* Updated to support 11.0.0.2 runtime -* Updated to support ICP platform - -## 11.0.0.0 (2019-10-08) - -* Initial version diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..662d590 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# Build and run: +# +# docker build -t ace:12.0.2.0 -f Dockerfile . +# docker run -e LICENSE=accept -p 7600:7600 -p 7800:7800 --rm -ti ace:12.0.2.0 +# +# Can also mount a volume for the work directory: +# +# docker run -e LICENSE=accept -v /what/ever/dir:/home/aceuser/ace-server -p 7600:7600 -p 7800:7800 --rm -ti ace:12.0.2.0 +# +# This might require a local directory with the right permissions, or changing the userid further down . . . + +FROM registry.access.redhat.com/ubi8/ubi-minimal as builder + +RUN microdnf update && microdnf install util-linux curl tar + +ARG USERNAME +ARG PASSWORD +ARG DOWNLOAD_URL=http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/integration/12.0.2.0-ACE-LINUX64-DEVELOPER.tar.gz + +RUN mkdir -p /opt/ibm/ace-12 \ + && if [ -z $USERNAME ]; then curl ${DOWNLOAD_URL}; else curl -u "${USERNAME}:${PASSWORD}" ${DOWNLOAD_URL}; fi | \ + tar zx --absolute-names --exclude ace-12.\*/tools --exclude ace-12.\*/server/bin/TADataCollector.sh --exclude ace-12.\*/server/transformationAdvisor/ta-plugin-ace.jar --strip-components 1 --directory /opt/ibm/ace-12 + +FROM registry.access.redhat.com/ubi8/ubi-minimal + +RUN microdnf update && microdnf install findutils util-linux && microdnf clean all + +# Force reinstall tzdata package to get zoneinfo files +RUN microdnf reinstall tzdata -y + +# Prevent errors about having no terminal when using apt-get +ENV DEBIAN_FRONTEND noninteractive + +# Install ACE v12.0.2.0 and accept the license +COPY --from=builder /opt/ibm/ace-12 /opt/ibm/ace-12 +RUN /opt/ibm/ace-12/ace make registry global accept license deferred \ + && useradd --uid 1001 --create-home --home-dir /home/aceuser --shell /bin/bash -G mqbrkrs aceuser \ + && su - aceuser -c "export LICENSE=accept && . /opt/ibm/ace-12/server/bin/mqsiprofile && mqsicreateworkdir /home/aceuser/ace-server" \ + && echo ". /opt/ibm/ace-12/server/bin/mqsiprofile" >> /home/aceuser/.bashrc + +COPY git.commit* /home/aceuser/ + +# Add required license as text file in Liceses directory (GPL, MIT, APACHE, Partner End User Agreement, etc) +COPY /licenses/ /licenses/ + +# aceuser +USER 1001 + +# Expose ports. 7600, 7800, 7843 for ACE; +EXPOSE 7600 7800 7843 + +# Set entrypoint to run the server +ENTRYPOINT ["bash", "-c", ". /opt/ibm/ace-12/server/bin/mqsiprofile && IntegrationServer -w /home/aceuser/ace-server"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 14b6796..0000000 --- a/LICENSE +++ /dev/null @@ -1,909 +0,0 @@ -The translated license terms can be viewed here: https://www-03.ibm.com/software/sla/sladb.nsf/searchlis/?searchview&searchorder=4&searchmax=0&query=(App+Connect+Enterprise+V11.0.0.5) - -LICENSE INFORMATION - -The Programs listed below are licensed under the following License Information terms and conditions in addition to the Program license terms previously agreed to by Client and IBM. If Client does not have previously agreed to license terms in effect for the Program, the International Program License Agreement (Z125-3301-14) applies. - -Program Name (Program Number): -IBM App Connect Enterprise V11.0.0.5 (5724-J05) - -The following standard terms apply to Licensee's use of the Program. - -Limited use right - -As described in the International Program License Agreement ("IPLA") and this License Information, IBM grants Licensee a limited right to use the Program. This right is limited to the level of Authorized Use, such as a Processor Value Unit ("PVU"), a Resource Value Unit ("RVU"), a Value Unit ("VU"), or other specified level of use, paid for by Licensee as evidenced in the Proof of Entitlement. Licensee's use may also be limited to a specified machine, or only as a Supporting Program, or subject to other restrictions. As Licensee has not paid for all of the economic value of the Program, no other use is permitted without the payment of additional fees. In addition, Licensee is not authorized to use the Program to provide commercial IT services to any third party, to provide commercial hosting or timesharing, or to sublicense, rent, or lease the Program unless expressly provided for in the applicable agreements under which Licensee obtains authorizations to use the Program. Additional rights may be available to Licensee subject to the payment of additional fees or under different or supplementary terms. IBM reserves the right to determine whether to make such additional rights available to Licensee. - -Specifications - -Program's specifications can be found in the collective Description and Technical Information sections of the Program's Announcement Letters. - -Prohibited Uses - -Licensee may not use or authorize others to use the Program if failure of the Program could lead to death, bodily injury, or property or environmental damage. - -Bundled Programs - -Licensee is authorized to install and use the Bundled Programs identified below. A Bundled Program may be accompanied by license terms, and those terms, if any, apply to Licensee's use of that Bundled Program. In the event of conflict, the terms in this License Information document supersede the Bundled Program's terms. The Principal Program and any Bundled Programs are all part of the Program, as a whole. Therefore, Licensee must obtain sufficient entitlements to the Program, as a whole, to cover Licensee's installation and use of all of the Bundled Programs, unless separate entitlements are provided within this License Information document. For example, if this Program were licensed on a PVU (Processor Value Unit) basis and Licensee were to install the Principal Program or a Bundled Program on a 100 PVU machine (physical or virtual) and another Bundled Program on a second 100 PVU machine, Licensee would be required to obtain 200 PVU entitlements to the Program. - -Bundled Programs: -- IBM App Connect Professional V7.5.2.0 - -Supporting Programs - -Licensee is authorized to install and use the Supporting Programs identified below. Licensee is authorized to install and use such Supporting Programs only to support Licensee's use of the Principal Program under this Agreement. The phrase "to support Licensee's use" would only include those uses that are necessary or otherwise directly related to a licensed use of the Principal Program or another Supporting Program. The Supporting Programs may not be used for any other purpose. A Supporting Program may be accompanied by license terms, and those terms, if any, apply to Licensee's use of that Supporting Program. In the event of conflict, the terms in this License Information document supersede the Supporting Program's terms. Licensee must obtain sufficient entitlements to the Program, as a whole, to cover Licensee's installation and use of all of the Supporting Programs, unless separate entitlements are provided within this License Information document. For example, if this Program were licensed on a PVU (Processor Value Unit) basis and Licensee were to install the Principal Program or a Supporting Program on a 100 PVU machine (physical or virtual) and another Supporting Program on a second 100 PVU machine, Licensee would be required to obtain 200 PVU entitlements to the Program. - -Supporting Programs: -- IBM Support Assistant Data Collector V2.0.1 -- IBM WebSphere Adapter for JD Edwards EnterpriseOne V7.5 -- IBM WebSphere Adapter for PeopleSoft Enterprise V7.5 -- IBM WebSphere Adapter for SAP Software V7.5 -- IBM WebSphere Adapter for Siebel Business Applications V7.5 -- IBM MQ Advanced V9.0 -- IBM Data Server Driver Package V11.1 -- IBM MQ Advanced V9.1 - -Non-Production Limitation - -If the Program is designated as "Non-Production", the Program can only be deployed as part of the Licensee's internal development and test environment for internal non-production activities, including but not limited to testing, performance tuning, fault diagnosis, internal benchmarking, staging, quality assurance activity and/or developing internally used additions or extensions to the Program using published application programming interfaces. Licensee is not authorized to use any part of the Program for any other purposes without acquiring the appropriate production entitlements. - -Separately Licensed Code - -The provisions of this paragraph do not apply to the extent they are held to be invalid or unenforceable under the law that governs this license. Each of the components listed below is considered "Separately Licensed Code". IBM Separately Licensed Code is licensed to Licensee under the terms of the applicable third party license agreement(s) set forth in the NON_IBM_LICENSE file(s) that accompanies the Program. Notwithstanding any of the terms in the Agreement, or any other agreement Licensee may have with IBM, the terms of such third party license agreement(s) governs Licensee's use of all Separately Licensed Code unless otherwise noted below. - -Future Program updates or fixes may contain additional Separately Licensed Code. Such additional Separately Licensed Code and related licenses are listed in another NON_IBM_LICENSE file that accompanies the Program update or fix. Licensee acknowledges that Licensee has read and agrees to the license agreements contained in the NON_IBM_LICENSE file(s). If Licensee does not agree to the terms of these third party license agreements, Licensee may not use the Separately Licensed Code. - -For Programs acquired under the International Program License Agreement ("IPLA") or International Program License Agreement for Non Warranted Program ("ILAN") and Licensee is the original licensee of the Program, if Licensee does not agree with the third party license agreements, Licensee may return the Program in accordance with the terms of, and within the specified time frames stated in, the "Money-back Guarantee" section of the IPLA or ILAN IBM Agreement. - -Note: Notwithstanding any of the terms in the third party license agreement, the Agreement, or any other agreement Licensee may have with IBM: -(a) IBM provides this Separately Licensed Code to Licensee WITHOUT WARRANTIES OF ANY KIND; -(b) IBM DISCLAIMS ANY AND ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS INCLUDING, BUT NOT LIMITED TO, THE WARRANTY OF TITLE, NON-INFRINGEMENT OR INTERFERENCE AND THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, WITH RESPECT TO THE SEPARATELY LICENSED CODE; -(c) IBM is not liable to Licensee, and will not defend, indemnify, or hold Licensee harmless for any claims arising from or related to the Separately Licensed Code; and -(d) IBM is not liable for any direct, indirect, incidental, special, exemplary, punitive or consequential damages including, but not limited to, lost data, lost savings, and lost profits, with respect to the Separately Licensed Code. - -Notwithstanding these exclusions, in Germany and Austria, IBM's warranty and liability for the Separately Licensed Code is governed only by the respective terms applicable for Germany and Austria in IBM license agreements. - -Note: IBM may provide limited support for some Separately Licensed Code. If such support is available, the details and any additional terms related to such support will be set forth in the License Information document. - -The following are Separately Licensed Code: -The Program includes some or all of the following licensed to you as Separately Licensed Code. -1. CC-BY-3.0 - spdx-exceptions(node) , spdx-expression-parse -2. CC0-1.0 - spdx-license-ids(node) , lodash.fill , lodash.intersection , lodash.partialright , libnpx make-fetch-happen spdx-license-ids spdx-license-ids -3. Ubuntu -4. Red Hat Enterprise Linux (RHEL) -5.Red Hat Universal Base Image v7.0 -=================================================================== -1. spdx-exceptions(node) , spdx-expression-parse -License for CreativeCommons-by-3.0 -=================================================================== -=================================================================== -2. spdx-license-ids(node) , lodash.fill , lodash.intersection , lodash.partialright , libnpx make-fetch-happen spdx-license-ids spdx-license-ids -License for Creative Commons CC0-1.0 -=================================================================== -=================================================================== -3. Ubuntu Operating System -=================================================================== -When IBM App Connect Enterprise is provided in a container format, license information for Ubuntu packages may be found in /usr/share/doc/${package}/copyright -========================================================================= -4.Red Hat Enterprise Linux Operating System -========================================================================= -When IBM App Connect Enterprise is provided in a container format, license information for Red Hat Enterprise Linux packages may be found in /usr/share/doc/${package} -========================================================================= -5.Red Hat Universal Base Image v7.0 -========================================================================= -Red Hat Universal Base Image v7.0 has been taken under EULA > https://www.redhat.com/licenses/eulas - -The Program maybe provided with third party software programs subject to their own license terms. The license terms either accompany the third party software programs or, in some instances, may be viewed at > https://access.redhat.com/containers/ -========================================================================= - -Redistributables - -If the Program includes components that are Redistributable, they will be identified in the REDIST file that accompanies the Program. In addition to the license rights granted in the Agreement, Licensee may distribute the Redistributables subject to the following terms: -1) Redistribution must be in object code form only and must conform to all directions, instruction and specifications in the Program's accompanying REDIST or documentation; -2) If the Program's accompanying documentation expressly allows Licensee to modify the Redistributables, such modification must conform to all directions, instruction and specifications in that documentation and these modifications, if any, must be treated as Redistributables; -3) Redistributables may be distributed only as part of Licensee's application that was developed using the Program ("Licensee's Application") and only to support Licensee's customers in connection with their use of Licensee's Application. Licensee's Application must constitute significant value add such that the Redistributables are not a substantial motivation for the acquisition by end users of Licensee's software product; -4) If the Redistributables include a Java Runtime Environment, Licensee must also include other non-Java Redistributables with Licensee's Application, unless the Application is designed to run only on general computer devices (for example, laptops, desktops and servers) and not on handheld or other pervasive devices (i.e., devices that contain a microprocessor but do not have computing as their primary purpose); -5) Licensee may not remove any copyright or notice files contained in the Redistributables; -6) Licensee must hold IBM, its suppliers or distributors harmless from and against any claim arising out of the use or distribution of Licensee's Application; -7) Licensee may not use the same path name as the original Redistributable files/modules; -8) Licensee may not use IBM's, its suppliers or distributors names or trademarks in connection with the marketing of Licensee's Application without IBM's or that supplier's or distributor's prior written consent; -9) IBM, its suppliers and distributors provide the Redistributables and related documentation without obligation of support and "AS IS", WITH NO WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTY OF TITLE, NON-INFRINGEMENT OR NON-INTERFERENCE AND THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE; -10) Licensee is responsible for all technical assistance for Licensee's Application and any modifications to the Redistributables; and -11) Licensee's license agreement with the end user of Licensee's Application must notify the end user that the Redistributables or their modifications may not be i) used for any purpose other than to enable Licensee's Application, ii) copied (except for backup purposes), iii) further distributed or transferred without Licensee's Application or iv) reverse assembled, reverse compiled, or otherwise translated except as specifically permitted by law and without the possibility of a contractual waiver. Furthermore, Licensee's license agreement must be at least as protective of IBM as the terms of this Agreement. - -Source Components and Sample Materials - -The Program may include some components in source code form ("Source Components") and other materials identified as Sample Materials. Licensee may copy and modify Source Components and Sample Materials for internal use only provided such use is within the limits of the license rights under this Agreement, provided however that Licensee may not alter or delete any copyright information or notices contained in the Source Components or Sample Materials. IBM provides the Source Components and Sample Materials without obligation of support and "AS IS", WITH NO WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTY OF TITLE, NON-INFRINGEMENT OR NON-INTERFERENCE AND THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - -For Source Components and Sample Materials listed in a Program's REDIST file, Licensee may redistribute modified versions of those Source Components or Sample Materials consistent with the terms of this license and any instructions in the REDIST file. - -Third Party Operating Systems - -For the convenience of Licensee, the Program may be accompanied by a third party operating system. The operating system is not part of the Program, and is licensed directly by the operating system provider (e.g., Red Hat Inc., Novell Inc., etc.) to Licensee. IBM is not a party to the license between Licensee and the third party operating system provider, and includes the third party operating system "AS IS", without representation or warranty, express or implied, including any implied warranty of merchantability, fitness for a particular purpose or non-infringement. - -Third Party Data and Services - -The Program may contain links to or be used to access third party data services, databases, web services, software, or other third party content (all, "content"). Access to this content is provided "AS-IS", WITH NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING THE WARRANTY OF TITLE, NON-INFRINGEMENT OR NON-INTERFERENCE AND THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Access can be terminated by the relevant third parties at their sole discretion at any time. Licensee may be required to enter into separate agreements with the third parties for the access to or use of such content. IBM is not a party to any such separate agreements and as an express condition of this license Licensee agrees to comply with the terms of such separate agreements. - -The following units of measure may apply to Licensee's use of the Program. - -Processor Value Unit (PVU) - -Processor Value Unit (PVU) is a unit of measure by which the Program can be licensed. The number of PVU entitlements required is based on the processor technology (defined within the PVU Table by Processor Vendor, Brand, Type and Model Number at http://www.ibm.com/software/lotus/passportadvantage/pvu_licensing_for_customers.html) and the number of processors made available to the Program. IBM continues to define a processor, for the purpose of PVU-based licensing, to be each processor core on a chip. A dual-core processor chip, for example, has two processor cores. - -Licensee can deploy the Program using either Full Capacity licensing or Virtualization Capacity (Sub-Capacity) licensing according to the Passport Advantage Sub-Capacity Licensing Terms (see webpage below). If using Full Capacity licensing, Licensee must obtain PVU entitlements sufficient to cover all activated processor cores* in the physical hardware environment made available to or managed by the Program, except for those servers from which the Program has been permanently removed. If using Virtualization Capacity licensing, Licensee must obtain entitlements sufficient to cover all activated processor cores made available to or managed by the Program, as defined according to the Virtualization Capacity License Counting Rules at http://www.ibm.com/software/lotus/passportadvantage/Counting_Software_licenses_using_specific_virtualization_technologies.html. - -* An Activated processor core is a processor core that is available for use in a physical or virtual server, regardless of whether the capacity of the processor core can be or is limited through virtualization technologies, operating system commands, BIOS settings, or similar restrictions. - -Virtual Processor Core - -Virtual Processor Core is a unit of measure by which the Program can be licensed. A Server is a physical computer that is comprised of processing units, memory, and input/output capabilities and that executes requested procedures, commands, or applications for one or more users or client devices. Where racks, blade enclosures, or other similar equipment is being employed, each separable physical device (for example, a blade or a rack-mounted device) that has the required components is considered itself a separate Server. A Virtual Server is either a virtual computer created by partitioning the resources available to a physical Server or an unpartitioned physical Server. A Processor Core is a functional unit within a computing device that interprets and executes instructions. A Processor Core consists of at least an instruction control unit and one or more arithmetic or logic unit. A Virtual Processor Core is a Processor Core on a Virtual Server created by partitioning the resources available to a physical Server or an unpartitioned physical Server. Licensee must obtain entitlement for each Virtual Processor Core made available to the Program. - -For each physical Server, Licensee must have sufficient entitlements for the lesser of 1) the sum of all available Virtual Processor Cores on all Virtual Servers or 2) all available Processor Cores on the physical Server. - -In addition to the above, the following terms apply to Licensee's use of the Program. - -1. Components Not Used for Establishing Required Entitlements - -When determining the number of entitlements required for Licensee's installation or use of the Program, the installation or use of the following Program and components are not taken into consideration. In other words, Licensee may install and use the following Program and components, under the license terms, but these components are not used to determine the number of entitlements required for the Program. - -Use of the Program and Components when used for Development and Functional Test purposes: - -Under Development and Functional Test use, the Program or components can only be deployed as part of Licensee's internal development, unit, and functional testing environments. In functional test environments the developer can integrate their code with that of others on shared use machines, and that code can be tested in a test harness. Functional testing is limited to ensuring that the Program can be used to connect to local or remote endpoints, and that a sample of data sent between those endpoints is processed correctly. Licensee is not authorized to use the Program or components for system test, meaning integrated code is tested as a product, processing production workloads, simulating production workloads or testing the scalability of any code, application or system. - -2. Operation mode - -The Program is capable of running in different operational modes, with associated restrictions, which are detailed in the "Operation Modes" and the "Restrictions that apply in each operation mode" sections of the Program's Knowledge Center, which is provided by IBM in the English, Brazilian Portuguese, German, Japanese and Spanish languages. The term Execution Group (used below) is defined in the "Technical Overview" section of the Program's Knowledge Center. - -In Standard mode, all features are enabled for use with a single Execution Group. The number of message flows that Licensee can deploy is unlimited. - -By default, the Program runs in the Advanced mode; see the "Operation Modes" section of the Program's Information Center for details of how to change the Program's operational mode, using the 'mqsimode' command. - -The entitlement to use each operational mode must be acquired separately, and it is Licensee's obligation to ensure that the Program is not running in an operational mode with more functionality (or with fewer restrictions) than the operational mode entitlement which Licensee has acquired. - -3. Supporting Program Details - -IBM Support Assistant Data Collector -- Support: Nonsupported - -"Nonsupported" means the Supporting Program is provided without obligation of support and "AS IS", WITH NO WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTY OF TITLE, NON-INFRINGEMENT OR NON-INTERFERENCE AND THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - -IBM Data Server Driver Package -- Use Limitation: Supported only on the IBM Cloud Private platform or on the IBM Kubernetes system platform and only for use with connecting to any version of IBM DB2 on a Linux, Unix or Windows platform. - -IBM MQ -- Entitlement: Ratio 1 VPC/1 VPC -- Entitlement: Ratio 1 PVU /1 PVU - -"Ratio n/m" means that Licensee receives some number ('n') entitlements of the indicated metric for the Supporting or Bundled Program for every specified number ('m') entitlements of the specified metric for the Program as a whole. The specified ratio does not apply to any entitlements for the Program that are not of the required metric type. The number of entitlements for the Supporting or Bundled Program is rounded up to a multiple of 'n'. For example, if a Program includes 100 PVUs for a Supporting or Bundled Program for every 500 PVUs obtained of the Principal Program and Licensee acquires 1,200 PVUs of the Program, Licensee may install the Supporting or Bundled Program and have processor cores available to or managed by it of up to 300 PVUs. Those PVUs would not need to be counted as part of the total PVU requirement for Licensee's installation of the Program on account of the installation of the Supporting or Bundled Program (although those PVUs might need to be counted for other reasons, such as the processor cores being made available to other components of the Program, as well). - -4. Bundled Program details - -IBM App Connect Professional -- Entitlement: Ratio 1 VPC/1 VPC -- Entitlement: Ratio 1 PVU /1 PVU - -"Ratio n/m" means that Licensee receives some number ('n') entitlements of the indicated metric for the Supporting or Bundled Program for every specified number ('m') entitlements of the specified metric for the Program as a whole. The specified ratio does not apply to any entitlements for the Program that are not of the required metric type. The number of entitlements for the Supporting or Bundled Program is rounded up to a multiple of 'n'. For example, if a Program includes 100 PVUs for a Supporting or Bundled Program for every 500 PVUs obtained of the Principal Program and Licensee acquires 1,200 PVUs of the Program, Licensee may install the Supporting or Bundled Program and have processor cores available to or managed by it of up to 300 PVUs. Those PVUs would not need to be counted as part of the total PVU requirement for Licensee's installation of the Program on account of the installation of the Supporting or Bundled Program (although those PVUs might need to be counted for other reasons, such as the processor cores being made available to other components of the Program, as well). - -5. Distributable Microsoft Code - -The distribution of the Program that is released for Microsoft Windows operating systems contains some distributable code (source code and DLLs) from Microsoft Corporation ('Microsoft Code'). - -Licensee may not exploit any distributable Microsoft Code (i.e., source code and DLLs), unless Licensee separately downloads (or otherwise properly obtains) its own copy of the Microsoft source and DLLs directly from Microsoft. Licensee will be subject to the terms and conditions of the Microsoft license that accompanies such Microsoft source code and DLLs (and not the terms and conditions of this license), which will apply to the use and distribution (if any) by Licensee of such material downloaded or otherwise by Licensee. - -The Program contains sample source code that is derived from the distributable Microsoft Code in source code form. Licensee must not include any such sample source code derived from distributable Microsoft Code in any malicious, deceptive or unlawful program. - -L/N: L-DFOX-BC9LDF -D/N: L-DFOX-BC9LDF -P/N: L-DFOX-BC9LDF - -International Program License Agreement - -Part 1 - General Terms - -BY DOWNLOADING, INSTALLING, COPYING, ACCESSING, CLICKING ON AN "ACCEPT" BUTTON, OR OTHERWISE USING THE PROGRAM, LICENSEE AGREES TO THE TERMS OF THIS AGREEMENT. IF YOU ARE ACCEPTING THESE TERMS ON BEHALF OF LICENSEE, YOU REPRESENT AND WARRANT THAT YOU HAVE FULL AUTHORITY TO BIND LICENSEE TO THESE TERMS. IF YOU DO NOT AGREE TO THESE TERMS, - -* DO NOT DOWNLOAD, INSTALL, COPY, ACCESS, CLICK ON AN "ACCEPT" BUTTON, OR USE THE PROGRAM; AND - -* PROMPTLY RETURN THE UNUSED MEDIA, DOCUMENTATION, AND PROOF OF ENTITLEMENT TO THE PARTY FROM WHOM IT WAS OBTAINED FOR A REFUND OF THE AMOUNT PAID. IF THE PROGRAM WAS DOWNLOADED, DESTROY ALL COPIES OF THE PROGRAM. - -1. Definitions - -"Authorized Use" - the specified level at which Licensee is authorized to execute or run the Program. That level may be measured by number of users, millions of service units ("MSUs"), Processor Value Units ("PVUs"), or other level of use specified by IBM. - -"IBM" - International Business Machines Corporation or one of its subsidiaries. - -"License Information" ("LI") - a document that provides information and any additional terms specific to a Program. The Program's LI is available at www.ibm.com/software/sla. The LI can also be found in the Program's directory, by the use of a system command, or as a booklet included with the Program. - -"Program" - the following, including the original and all whole or partial copies: 1) machine-readable instructions and data, 2) components, files, and modules, 3) audio-visual content (such as images, text, recordings, or pictures), and 4) related licensed materials (such as keys and documentation). - -"Proof of Entitlement" ("PoE") - evidence of Licensee's Authorized Use. The PoE is also evidence of Licensee's eligibility for warranty, future update prices, if any, and potential special or promotional opportunities. If IBM does not provide Licensee with a PoE, then IBM may accept as the PoE the original paid sales receipt or other sales record from the party (either IBM or its reseller) from whom Licensee obtained the Program, provided that it specifies the Program name and Authorized Use obtained. - -"Warranty Period" - one year, starting on the date the original Licensee is granted the license. - -2. Agreement Structure - -This Agreement includes Part 1 - General Terms, Part 2 - Country-unique Terms (if any), the LI, and the PoE and is the complete agreement between Licensee and IBM regarding the use of the Program. It replaces any prior oral or written communications between Licensee and IBM concerning Licensee's use of the Program. The terms of Part 2 may replace or modify those of Part 1. To the extent of any conflict, the LI prevails over both Parts. - -3. License Grant - -The Program is owned by IBM or an IBM supplier, and is copyrighted and licensed, not sold. - -IBM grants Licensee a nonexclusive license to 1) use the Program up to the Authorized Use specified in the PoE, 2) make and install copies to support such Authorized Use, and 3) make a backup copy, all provided that - -a. Licensee has lawfully obtained the Program and complies with the terms of this Agreement; - -b. the backup copy does not execute unless the backed-up Program cannot execute; - -c. Licensee reproduces all copyright notices and other legends of ownership on each copy, or partial copy, of the Program; - -d. Licensee ensures that anyone who uses the Program (accessed either locally or remotely) 1) does so only on Licensee's behalf and 2) complies with the terms of this Agreement; - -e. Licensee does not 1) use, copy, modify, or distribute the Program except as expressly permitted in this Agreement; 2) reverse assemble, reverse compile, otherwise translate, or reverse engineer the Program, except as expressly permitted by law without the possibility of contractual waiver; 3) use any of the Program's components, files, modules, audio-visual content, or related licensed materials separately from that Program; or 4) sublicense, rent, or lease the Program; and - -f. if Licensee obtains this Program as a Supporting Program, Licensee uses this Program only to support the Principal Program and subject to any limitations in the license to the Principal Program, or, if Licensee obtains this Program as a Principal Program, Licensee uses all Supporting Programs only to support this Program, and subject to any limitations in this Agreement. For purposes of this Item "f," a "Supporting Program" is a Program that is part of another IBM Program ("Principal Program") and identified as a Supporting Program in the Principal Program's LI. (To obtain a separate license to a Supporting Program without these restrictions, Licensee should contact the party from whom Licensee obtained the Supporting Program.) - -This license applies to each copy of the Program that Licensee makes. - -3.1 Trade-ups, Updates, Fixes, and Patches - -3.1.1 Trade-ups - -If the Program is replaced by a trade-up Program, the replaced Program's license is promptly terminated. - -3.1.2 Updates, Fixes, and Patches - -When Licensee receives an update, fix, or patch to a Program, Licensee accepts any additional or different terms that are applicable to such update, fix, or patch that are specified in its LI. If no additional or different terms are provided, then the update, fix, or patch is subject solely to this Agreement. If the Program is replaced by an update, Licensee agrees to promptly discontinue use of the replaced Program. - -3.2 Fixed Term Licenses - -If IBM licenses the Program for a fixed term, Licensee's license is terminated at the end of the fixed term, unless Licensee and IBM agree to renew it. - -3.3 Term and Termination - -This Agreement is effective until terminated. - -IBM may terminate Licensee's license if Licensee fails to comply with the terms of this Agreement. - -If the license is terminated for any reason by either party, Licensee agrees to promptly discontinue use of and destroy all of Licensee's copies of the Program. Any terms of this Agreement that by their nature extend beyond termination of this Agreement remain in effect until fulfilled, and apply to both parties' respective successors and assignees. - -4. Charges - -Charges are based on Authorized Use obtained, which is specified in the PoE. IBM does not give credits or refunds for charges already due or paid, except as specified elsewhere in this Agreement. - -If Licensee wishes to increase its Authorized Use, Licensee must notify IBM or an authorized IBM reseller in advance and pay any applicable charges. - -5. Taxes - -If any authority imposes on the Program a duty, tax, levy, or fee, excluding those based on IBM's net income, then Licensee agrees to pay that amount, as specified in an invoice, or supply exemption documentation. Licensee is responsible for any personal property taxes for the Program from the date that Licensee obtains it. If any authority imposes a customs duty, tax, levy, or fee for the import into or the export, transfer, access, or use of the Program outside the country in which the original Licensee was granted the license, then Licensee agrees that it is responsible for, and will pay, any amount imposed. - -6. Money-back Guarantee - -If Licensee is dissatisfied with the Program for any reason and is the original Licensee, Licensee may terminate the license and obtain a refund of the amount Licensee paid for the Program, provided that Licensee returns the Program and PoE to the party from whom Licensee obtained it within 30 days of the date the PoE was issued to Licensee. If the license is for a fixed term that is subject to renewal, then Licensee may obtain a refund only if the Program and its PoE are returned within the first 30 days of the initial term. If Licensee downloaded the Program, Licensee should contact the party from whom Licensee obtained it for instructions on how to obtain the refund. - -7. Program Transfer - -Licensee may transfer the Program and all of Licensee's license rights and obligations to another party only if that party agrees to the terms of this Agreement. If the license is terminated for any reason by either party, Licensee is prohibited from transferring the Program to another party. Licensee may not transfer a portion of 1) the Program or 2) the Program's Authorized Use. When Licensee transfers the Program, Licensee must also transfer a hard copy of this Agreement, including the LI and PoE. Immediately after the transfer, Licensee's license terminates. - -8. Warranty and Exclusions - -8.1 Limited Warranty - -IBM warrants that the Program, when used in its specified operating environment, will conform to its specifications. The Program's specifications, and specified operating environment information, can be found in documentation accompanying the Program (such as a read-me file) or other information published by IBM (such as an announcement letter). Licensee agrees that such documentation and other Program content may be supplied only in the English language, unless otherwise required by local law without the possibility of contractual waiver or limitation. - -The warranty applies only to the unmodified portion of the Program. IBM does not warrant uninterrupted or error-free operation of the Program, or that IBM will correct all Program defects. Licensee is responsible for the results obtained from the use of the Program. - -During the Warranty Period, IBM provides Licensee with access to IBM databases containing information on known Program defects, defect corrections, restrictions, and bypasses at no additional charge. Consult the IBM Software Support Handbook for further information at www.ibm.com/software/support. - -If the Program does not function as warranted during the Warranty Period and the problem cannot be resolved with information available in the IBM databases, Licensee may return the Program and its PoE to the party (either IBM or its reseller) from whom Licensee obtained it and receive a refund of the amount Licensee paid. After returning the Program, Licensee's license terminates. If Licensee downloaded the Program, Licensee should contact the party from whom Licensee obtained it for instructions on how to obtain the refund. - -8.2 Exclusions - -THESE WARRANTIES ARE LICENSEE'S EXCLUSIVE WARRANTIES AND REPLACE ALL OTHER WARRANTIES OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND ANY WARRANTY OR CONDITION OF NON-INFRINGEMENT. SOME STATES OR JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF EXPRESS OR IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO LICENSEE. IN THAT EVENT, SUCH WARRANTIES ARE LIMITED IN DURATION TO THE WARRANTY PERIOD. NO WARRANTIES APPLY AFTER THAT PERIOD. SOME STATES OR JURISDICTIONS DO NOT ALLOW LIMITATIONS ON HOW LONG AN IMPLIED WARRANTY LASTS, SO THE ABOVE LIMITATION MAY NOT APPLY TO LICENSEE. - -THESE WARRANTIES GIVE LICENSEE SPECIFIC LEGAL RIGHTS. LICENSEE MAY ALSO HAVE OTHER RIGHTS THAT VARY FROM STATE TO STATE OR JURISDICTION TO JURISDICTION. - -THE WARRANTIES IN THIS SECTION 8 (WARRANTY AND EXCLUSIONS) ARE PROVIDED SOLELY BY IBM. THE DISCLAIMERS IN THIS SUBSECTION 8.2 (EXCLUSIONS), HOWEVER, ALSO APPLY TO IBM'S SUPPLIERS OF THIRD PARTY CODE. THOSE SUPPLIERS PROVIDE SUCH CODE WITHOUT WARRANTIES OR CONDITION OF ANY KIND. THIS PARAGRAPH DOES NOT NULLIFY IBM'S WARRANTY OBLIGATIONS UNDER THIS AGREEMENT. - -9. Licensee Data and Databases - -To assist Licensee in isolating the cause of a problem with the Program, IBM may request that Licensee 1) allow IBM to remotely access Licensee's system or 2) send Licensee information or system data to IBM. However, IBM is not obligated to provide such assistance unless IBM and Licensee enter a separate written agreement under which IBM agrees to provide to Licensee that type of support, which is beyond IBM's warranty obligations in this Agreement. In any event, IBM uses information about errors and problems to improve its products and services, and assist with its provision of related support offerings. For these purposes, IBM may use IBM entities and subcontractors (including in one or more countries other than the one in which Licensee is located), and Licensee authorizes IBM to do so. - -Licensee remains responsible for 1) any data and the content of any database Licensee makes available to IBM, 2) the selection and implementation of procedures and controls regarding access, security, encryption, use, and transmission of data (including any personally-identifiable data), and 3) backup and recovery of any database and any stored data. Licensee will not send or provide IBM access to any personally-identifiable information, whether in data or any other form, and will be responsible for reasonable costs and other amounts that IBM may incur relating to any such information mistakenly provided to IBM or the loss or disclosure of such information by IBM, including those arising out of any third party claims. - -10. Limitation of Liability - -The limitations and exclusions in this Section 10 (Limitation of Liability) apply to the full extent they are not prohibited by applicable law without the possibility of contractual waiver. - -10.1 Items for Which IBM May Be Liable - -Circumstances may arise where, because of a default on IBM's part or other liability, Licensee is entitled to recover damages from IBM. Regardless of the basis on which Licensee is entitled to claim damages from IBM (including fundamental breach, negligence, misrepresentation, or other contract or tort claim), IBM's entire liability for all claims in the aggregate arising from or related to each Program or otherwise arising under this Agreement will not exceed the amount of any 1) damages for bodily injury (including death) and damage to real property and tangible personal property and 2) other actual direct damages up to the charges (if the Program is subject to fixed term charges, up to twelve months' charges) Licensee paid for the Program that is the subject of the claim. - -This limit also applies to any of IBM's Program developers and suppliers. It is the maximum for which IBM and its Program developers and suppliers are collectively responsible. - -10.2 Items for Which IBM Is Not Liable - -UNDER NO CIRCUMSTANCES IS IBM, ITS PROGRAM DEVELOPERS OR SUPPLIERS LIABLE FOR ANY OF THE FOLLOWING, EVEN IF INFORMED OF THEIR POSSIBILITY: - -a. LOSS OF, OR DAMAGE TO, DATA; - -b. SPECIAL, INCIDENTAL, EXEMPLARY, OR INDIRECT DAMAGES, OR FOR ANY ECONOMIC CONSEQUENTIAL DAMAGES; OR - -c. LOST PROFITS, BUSINESS, REVENUE, GOODWILL, OR ANTICIPATED SAVINGS. - -11. Compliance Verification - -For purposes of this Section 11 (Compliance Verification), "IPLA Program Terms" means 1) this Agreement and applicable amendments and transaction documents provided by IBM, and 2) IBM software policies that may be found at the IBM Software Policy website (www.ibm.com/softwarepolicies), including but not limited to those policies concerning backup, sub-capacity pricing, and migration. - -The rights and obligations set forth in this Section 11 remain in effect during the period the Program is licensed to Licensee, and for two years thereafter. - -11.1 Verification Process - -Licensee agrees to create, retain, and provide to IBM and its auditors accurate written records, system tool outputs, and other system information sufficient to provide auditable verification that Licensee's use of all Programs is in compliance with the IPLA Program Terms, including, without limitation, all of IBM's applicable licensing and pricing qualification terms. Licensee is responsible for 1) ensuring that it does not exceed its Authorized Use, and 2) remaining in compliance with IPLA Program Terms. - -Upon reasonable notice, IBM may verify Licensee's compliance with IPLA Program Terms at all sites and for all environments in which Licensee uses (for any purpose) Programs subject to IPLA Program Terms. Such verification will be conducted in a manner that minimizes disruption to Licensee's business, and may be conducted on Licensee's premises, during normal business hours. IBM may use an independent auditor to assist with such verification, provided IBM has a written confidentiality agreement in place with such auditor. - -11.2 Resolution - -IBM will notify Licensee in writing if any such verification indicates that Licensee has used any Program in excess of its Authorized Use or is otherwise not in compliance with the IPLA Program Terms. Licensee agrees to promptly pay directly to IBM the charges that IBM specifies in an invoice for 1) any such excess use, 2) support for such excess use for the lesser of the duration of such excess use or two years, and 3) any additional charges and other liabilities determined as a result of such verification. - -12. Third Party Notices - -The Program may include third party code that IBM, not the third party, licenses to Licensee under this Agreement. Notices, if any, for the third party code ("Third Party Notices") are included for Licensee's information only. These notices can be found in the Program's NOTICES file(s). Information on how to obtain source code for certain third party code can be found in the Third Party Notices. If in the Third Party Notices IBM identifies third party code as "Modifiable Third Party Code," IBM authorizes Licensee to 1) modify the Modifiable Third Party Code and 2) reverse engineer the Program modules that directly interface with the Modifiable Third Party Code provided that it is only for the purpose of debugging Licensee's modifications to such third party code. IBM's service and support obligations, if any, apply only to the unmodified Program. - -13. General - -a. Nothing in this Agreement affects any statutory rights of consumers that cannot be waived or limited by contract. - -b. For Programs IBM provides to Licensee in tangible form, IBM fulfills its shipping and delivery obligations upon the delivery of such Programs to the IBM-designated carrier, unless otherwise agreed to in writing by Licensee and IBM. - -c. If any provision of this Agreement is held to be invalid or unenforceable, the remaining provisions of this Agreement remain in full force and effect. - -d. Licensee agrees to comply with all applicable export and import laws and regulations, including U.S. embargo and sanctions regulations and prohibitions on export for certain end uses or to certain users. - -e. Licensee authorizes International Business Machines Corporation and its subsidiaries (and their successors and assigns, contractors and IBM Business Partners) to store and use Licensee's business contact information wherever they do business, in connection with IBM products and services, or in furtherance of IBM's business relationship with Licensee. - -f. Each party will allow the other reasonable opportunity to comply before it claims that the other has not met its obligations under this Agreement. The parties will attempt in good faith to resolve all disputes, disagreements, or claims between the parties relating to this Agreement. - -g. Unless otherwise required by applicable law without the possibility of contractual waiver or limitation: 1) neither party will bring a legal action, regardless of form, for any claim arising out of or related to this Agreement more than two years after the cause of action arose; and 2) upon the expiration of such time limit, any such claim and all respective rights related to the claim lapse. - -h. Neither Licensee nor IBM is responsible for failure to fulfill any obligations due to causes beyond its control. - -i. No right or cause of action for any third party is created by this Agreement, nor is IBM responsible for any third party claims against Licensee, except as permitted in Subsection 10.1 (Items for Which IBM May Be Liable) above for bodily injury (including death) or damage to real or tangible personal property for which IBM is legally liable to that third party. - -j. In entering into this Agreement, neither party is relying on any representation not specified in this Agreement, including but not limited to any representation concerning: 1) the performance or function of the Program, other than as expressly warranted in Section 8 (Warranty and Exclusions) above; 2) the experiences or recommendations of other parties; or 3) any results or savings that Licensee may achieve. - -k. IBM has signed agreements with certain organizations (called "IBM Business Partners") to promote, market, and support certain Programs. IBM Business Partners remain independent and separate from IBM. IBM is not responsible for the actions or statements of IBM Business Partners or obligations they have to Licensee. - -l. The license and intellectual property indemnification terms of Licensee's other agreements with IBM (such as the IBM Customer Agreement) do not apply to Program licenses granted under this Agreement. - -14. Geographic Scope and Governing Law - -14.1 Governing Law - -Both parties agree to the application of the laws of the country in which Licensee obtained the Program license to govern, interpret, and enforce all of Licensee's and IBM's respective rights, duties, and obligations arising from, or relating in any manner to, the subject matter of this Agreement, without regard to conflict of law principles. - -The United Nations Convention on Contracts for the International Sale of Goods does not apply. - -14.2 Jurisdiction - -All rights, duties, and obligations are subject to the courts of the country in which Licensee obtained the Program license. - -Part 2 - Country-unique Terms - -For licenses granted in the countries specified below, the following terms replace or modify the referenced terms in Part 1. All terms in Part 1 that are not changed by these amendments remain unchanged and in effect. This Part 2 is organized as follows: - -* Multiple country amendments to Part 1, Section 14 (Governing Law and Jurisdiction); - -* Americas country amendments to other Agreement terms; - -* Asia Pacific country amendments to other Agreement terms; and - -* Europe, Middle East, and Africa country amendments to other Agreement terms. - -Multiple country amendments to Part 1, Section 14 (Governing Law and Jurisdiction) - -14.1 Governing Law - -The phrase "the laws of the country in which Licensee obtained the Program license" in the first paragraph of 14.1 Governing Law is replaced by the following phrases in the countries below: - -AMERICAS - -(1) In Canada: the laws in the Province of Ontario; - -(2) in Mexico: the federal laws of the Republic of Mexico; - -(3) in the United States, Anguilla, Antigua/Barbuda, Aruba, British Virgin Islands, Cayman Islands, Dominica, Grenada, Guyana, Saint Kitts and Nevis, Saint Lucia, Saint Maarten, and Saint Vincent and the Grenadines: the laws of the State of New York, United States; - -(4) in Venezuela: the laws of the Bolivarian Republic of Venezuela; - -ASIA PACIFIC - -(5) in Cambodia and Laos: the laws of the State of New York, United States; - -(6) in Australia: the laws of the State or Territory in which the transaction is performed; - -(7) in Hong Kong SAR and Macau SAR: the laws of Hong Kong Special Administrative Region ("SAR"); - -(8) in Taiwan: the laws of Taiwan; - -EUROPE, MIDDLE EAST, AND AFRICA - -(9) in Albania, Armenia, Azerbaijan, Belarus, Bosnia-Herzegovina, Bulgaria, Croatia, Former Yugoslav Republic of Macedonia, Georgia, Hungary, Kazakhstan, Kyrgyzstan, Moldova, Montenegro, Poland, Romania, Russia, Serbia, Slovakia, Tajikistan, Turkmenistan, Ukraine, and Uzbekistan: the laws of Austria; - -(10) in Algeria, Andorra, Benin, Burkina Faso, Cameroon, Cape Verde, Central African Republic, Chad, Comoros, Congo Republic, Djibouti, Democratic Republic of Congo, Equatorial Guinea, French Guiana, French Polynesia, Gabon, Gambia, Guinea, Guinea-Bissau, Ivory Coast, Lebanon, Madagascar, Mali, Mauritania, Mauritius, Mayotte, Morocco, New Caledonia, Niger, Reunion, Senegal, Seychelles, Togo, Tunisia, Vanuatu, and Wallis and Futuna: the laws of France; - -(11) in Estonia, Latvia, and Lithuania: the laws of Finland; - -(12) in Angola, Bahrain, Botswana, Burundi, Egypt, Eritrea, Ethiopia, Ghana, Jordan, Kenya, Kuwait, Liberia, Malawi, Malta, Mozambique, Nigeria, Oman, Pakistan, Qatar, Rwanda, Sao Tome and Principe, Saudi Arabia, Sierra Leone, Somalia, Tanzania, Uganda, United Arab Emirates, the United Kingdom, West Bank/Gaza, Yemen, Zambia, and Zimbabwe: the laws of England; and - -(13) in South Africa, Namibia, Lesotho, and Swaziland: the laws of the Republic of South Africa. - -14.2 Jurisdiction - -The following paragraph pertains to jurisdiction and replaces Subsection 14.2 (Jurisdiction) as it applies for those countries identified below: - -All rights, duties, and obligations are subject to the courts of the country in which Licensee obtained the Program license except that in the countries identified below all disputes arising out of or related to this Agreement, including summary proceedings, will be brought before and subject to the exclusive jurisdiction of the following courts of competent jurisdiction: - -AMERICAS - -(1) In Argentina: the Ordinary Commercial Court of the city of Buenos Aires; - -(2) in Brazil: the court of Rio de Janeiro, RJ; - -(3) in Chile: the Civil Courts of Justice of Santiago; - -(4) in Ecuador: the civil judges of Quito for executory or summary proceedings (as applicable); - -(5) in Mexico: the courts located in Mexico City, Federal District; - -(6) in Peru: the judges and tribunals of the judicial district of Lima, Cercado; - -(7) in Uruguay: the courts of the city of Montevideo; - -(8) in Venezuela: the courts of the metropolitan area of the city of Caracas; - -EUROPE, MIDDLE EAST, AND AFRICA - -(9) in Austria: the court of law in Vienna, Austria (Inner-City); - -(10) in Algeria, Andorra, Benin, Burkina Faso, Cameroon, Cape Verde, Central African Republic, Chad, Comoros, Congo Republic, Djibouti, Democratic Republic of Congo, Equatorial Guinea, France, French Guiana, French Polynesia, Gabon, Gambia, Guinea, Guinea-Bissau, Ivory Coast, Lebanon, Madagascar, Mali, Mauritania, Mauritius, Mayotte, Monaco, Morocco, New Caledonia, Niger, Reunion, Senegal, Seychelles, Togo, Tunisia, Vanuatu, and Wallis and Futuna: the Commercial Court of Paris; - -(11) in Angola, Bahrain, Botswana, Burundi, Egypt, Eritrea, Ethiopia, Ghana, Jordan, Kenya, Kuwait, Liberia, Malawi, Malta, Mozambique, Nigeria, Oman, Pakistan, Qatar, Rwanda, Sao Tome and Principe, Saudi Arabia, Sierra Leone, Somalia, Tanzania, Uganda, United Arab Emirates, the United Kingdom, West Bank/Gaza, Yemen, Zambia, and Zimbabwe: the English courts; - -(12) in South Africa, Namibia, Lesotho, and Swaziland: the High Court in Johannesburg; - -(13) in Greece: the competent court of Athens; - -(14) in Israel: the courts of Tel Aviv-Jaffa; - -(15) in Italy: the courts of Milan; - -(16) in Portugal: the courts of Lisbon; - -(17) in Spain: the courts of Madrid; and - -(18) in Turkey: the Istanbul Central Courts and Execution Directorates of Istanbul, the Republic of Turkey. - -14.3 Arbitration - -The following paragraph is added as a new Subsection 14.3 (Arbitration) as it applies for those countries identified below. The provisions of this Subsection 14.3 prevail over those of Subsection 14.2 (Jurisdiction) to the extent permitted by the applicable governing law and rules of procedure: - -ASIA PACIFIC - -(1) In Cambodia, India, Laos, Philippines, and Vietnam: - -Disputes arising out of or in connection with this Agreement will be finally settled by arbitration which will be held in Singapore in accordance with the Arbitration Rules of Singapore International Arbitration Center ("SIAC Rules") then in effect. The arbitration award will be final and binding for the parties without appeal and will be in writing and set forth the findings of fact and the conclusions of law. - -The number of arbitrators will be three, with each side to the dispute being entitled to appoint one arbitrator. The two arbitrators appointed by the parties will appoint a third arbitrator who will act as chairman of the proceedings. Vacancies in the post of chairman will be filled by the president of the SIAC. Other vacancies will be filled by the respective nominating party. Proceedings will continue from the stage they were at when the vacancy occurred. - -If one of the parties refuses or otherwise fails to appoint an arbitrator within 30 days of the date the other party appoints its, the first appointed arbitrator will be the sole arbitrator, provided that the arbitrator was validly and properly appointed. - -All proceedings will be conducted, including all documents presented in such proceedings, in the English language. The English language version of this Agreement prevails over any other language version. - -(2) In the People's Republic of China: - -In case no settlement can be reached, the disputes will be submitted to China International Economic and Trade Arbitration Commission for arbitration according to the then effective rules of the said Arbitration Commission. The arbitration will take place in Beijing and be conducted in Chinese. The arbitration award will be final and binding on both parties. During the course of arbitration, this agreement will continue to be performed except for the part which the parties are disputing and which is undergoing arbitration. - -(3) In Indonesia: - -Each party will allow the other reasonable opportunity to comply before it claims that the other has not met its obligations under this Agreement. The parties will attempt in good faith to resolve all disputes, disagreements, or claims between the parties relating to this Agreement. Unless otherwise required by applicable law without the possibility of contractual waiver or limitation, i) neither party will bring a legal action, regardless of form, arising out of or related to this Agreement or any transaction under it more than two years after the cause of action arose; and ii) after such time limit, any legal action arising out of this Agreement or any transaction under it and all respective rights related to any such action lapse. - -Disputes arising out of or in connection with this Agreement shall be finally settled by arbitration that shall be held in Jakarta, Indonesia in accordance with the rules of Board of the Indonesian National Board of Arbitration (Badan Arbitrase Nasional Indonesia or "BANI") then in effect. The arbitration award shall be final and binding for the parties without appeal and shall be in writing and set forth the findings of fact and the conclusions of law. - -The number of arbitrators shall be three, with each side to the dispute being entitled to appoint one arbitrator. The two arbitrators appointed by the parties shall appoint a third arbitrator who shall act as chairman of the proceedings. Vacancies in the post of chairman shall be filled by the chairman of the BANI. Other vacancies shall be filled by the respective nominating party. Proceedings shall continue from the stage they were at when the vacancy occurred. - -If one of the parties refuses or otherwise fails to appoint an arbitrator within 30 days of the date the other party appoints its, the first appointed arbitrator shall be the sole arbitrator, provided that the arbitrator was validly and properly appointed. - -All proceedings shall be conducted, including all documents presented in such proceedings, in the English and/or Indonesian language. - -EUROPE, MIDDLE EAST, AND AFRICA - -(4) In Albania, Armenia, Azerbaijan, Belarus, Bosnia-Herzegovina, Bulgaria, Croatia, Former Yugoslav Republic of Macedonia, Georgia, Hungary, Kazakhstan, Kyrgyzstan, Moldova, Montenegro, Poland, Romania, Russia, Serbia, Slovakia, Tajikistan, Turkmenistan, Ukraine, and Uzbekistan: - -All disputes arising out of this Agreement or related to its violation, termination or nullity will be finally settled under the Rules of Arbitration and Conciliation of the International Arbitral Center of the Federal Economic Chamber in Vienna (Vienna Rules) by three arbitrators appointed in accordance with these rules. The arbitration will be held in Vienna, Austria, and the official language of the proceedings will be English. The decision of the arbitrators will be final and binding upon both parties. Therefore, pursuant to paragraph 598 (2) of the Austrian Code of Civil Procedure, the parties expressly waive the application of paragraph 595 (1) figure 7 of the Code. IBM may, however, institute proceedings in a competent court in the country of installation. - -(5) In Estonia, Latvia, and Lithuania: - -All disputes arising in connection with this Agreement will be finally settled in arbitration that will be held in Helsinki, Finland in accordance with the arbitration laws of Finland then in effect. Each party will appoint one arbitrator. The arbitrators will then jointly appoint the chairman. If arbitrators cannot agree on the chairman, then the Central Chamber of Commerce in Helsinki will appoint the chairman. - -AMERICAS COUNTRY AMENDMENTS - -CANADA - -10.1 Items for Which IBM May be Liable - -The following replaces Item 1 in the first paragraph of this Subsection 10.1 (Items for Which IBM May be Liable): - -1) damages for bodily injury (including death) and physical harm to real property and tangible personal property caused by IBM's negligence; and - -13. General - -The following replaces Item 13.d: - -d. Licensee agrees to comply with all applicable export and import laws and regulations, including those of that apply to goods of United States origin and that prohibit or limit export for certain uses or to certain users. - -The following replaces Item 13.i: - -i. No right or cause of action for any third party is created by this Agreement or any transaction under it, nor is IBM responsible for any third party claims against Licensee except as permitted by the Limitation of Liability section above for bodily injury (including death) or physical harm to real or tangible personal property caused by IBM's negligence for which IBM is legally liable to that third party. - -The following is added as Item 13.m: - -m. For purposes of this Item 13.m, "Personal Data" refers to information relating to an identified or identifiable individual made available by one of the parties, its personnel or any other individual to the other in connection with this Agreement. The following provisions apply in the event that one party makes Personal Data available to the other: - -(1) General - -(a) Each party is responsible for complying with any obligations applying to it under applicable Canadian data privacy laws and regulations ("Laws"). - -(b) Neither party will request Personal Data beyond what is necessary to fulfill the purpose(s) for which it is requested. The purpose(s) for requesting Personal Data must be reasonable. Each party will agree in advance as to the type of Personal Data that is required to be made available. - -(2) Security Safeguards - -(a) Each party acknowledges that it is solely responsible for determining and communicating to the other the appropriate technological, physical and organizational security measures required to protect Personal Data. - -(b) Each party will ensure that Personal Data is protected in accordance with the security safeguards communicated and agreed to by the other. - -(c) Each party will ensure that any third party to whom Personal Data is transferred is bound by the applicable terms of this section. - -(d) Additional or different services required to comply with the Laws will be deemed a request for new services. - -(3) Use - -Each party agrees that Personal Data will only be used, accessed, managed, transferred, disclosed to third parties or otherwise processed to fulfill the purpose(s) for which it was made available. - -(4) Access Requests - -(a) Each party agrees to reasonably cooperate with the other in connection with requests to access or amend Personal Data. - -(b) Each party agrees to reimburse the other for any reasonable charges incurred in providing each other assistance. - -(c) Each party agrees to amend Personal Data only upon receiving instructions to do so from the other party or its personnel. - -(5) Retention - -Each party will promptly return to the other or destroy all Personal Data that is no longer necessary to fulfill the purpose(s) for which it was made available, unless otherwise instructed by the other or its personnel or required by law. - -(6) Public Bodies Who Are Subject to Public Sector Privacy Legislation - -For Licensees who are public bodies subject to public sector privacy legislation, this Item 13.m applies only to Personal Data made available to Licensee in connection with this Agreement, and the obligations in this section apply only to Licensee, except that: 1) section (2)(a) applies only to IBM; 2) sections (1)(a) and (4)(a) apply to both parties; and 3) section (4)(b) and the last sentence in (1)(b) do not apply. - -PERU - -10. Limitation of Liability - -The following is added to the end of this Section 10 (Limitation of Liability): - -Except as expressly required by law without the possibility of contractual waiver, Licensee and IBM intend that the limitation of liability in this Limitation of Liability section applies to damages caused by all types of claims and causes of action. If any limitation on or exclusion from liability in this section is held by a court of competent jurisdiction to be unenforceable with respect to a particular claim or cause of action, the parties intend that it nonetheless apply to the maximum extent permitted by applicable law to all other claims and causes of action. - -10.1 Items for Which IBM May be Liable - -The following is added at the end of this Subsection 10.1: - -In accordance with Article 1328 of the Peruvian Civil Code, the limitations and exclusions specified in this section will not apply to damages caused by IBM's willful misconduct ("dolo") or gross negligence ("culpa inexcusable"). - -UNITED STATES OF AMERICA - -5. Taxes - -The following is added at the end of this Section 5 (Taxes) - -For Programs delivered electronically in the United States for which Licensee claims a state sales and use tax exemption, Licensee agrees not to receive any tangible personal property (e.g., media and publications) associated with the electronic program. - -Licensee agrees to be responsible for any sales and use tax liabilities that may arise as a result of Licensee's subsequent redistribution of Programs after delivery by IBM. - -13. General - -The following is added to Section 13 as Item 13.m: - -U.S. Government Users Restricted Rights - Use, duplication or disclosure is restricted by the GSA IT Schedule 70 Contract with the IBM Corporation. - -The following is added to Item 13.f: - -Each party waives any right to a jury trial in any proceeding arising out of or related to this Agreement. - -ASIA PACIFIC COUNTRY AMENDMENTS - -AUSTRALIA - -5. Taxes - -The following sentences replace the first two sentences of Section 5 (Taxes): - -If any government or authority imposes a duty, tax (other than income tax), levy, or fee, on this Agreement or on the Program itself, that is not otherwise provided for in the amount payable, Licensee agrees to pay it when IBM invoices Licensee. If the rate of GST changes, IBM may adjust the charge or other amount payable to take into account that change from the date the change becomes effective. - -8.1 Limited Warranty - -The following is added to Subsection 8.1 (Limited Warranty): - -The warranties specified this Section are in addition to any rights Licensee may have under the Competition and Consumer Act 2010 or other legislation and are only limited to the extent permitted by the applicable legislation. - -10.1 Items for Which IBM May be Liable - -The following is added to Subsection 10.1 (Items for Which IBM May be Liable): - -Where IBM is in breach of a condition or warranty implied by the Competition and Consumer Act 2010, IBM's liability is limited to the repair or replacement of the goods, or the supply of equivalent goods. Where that condition or warranty relates to right to sell, quiet possession or clear title, or the goods are of a kind ordinarily obtained for personal, domestic or household use or consumption, then none of the limitations in this paragraph apply. - -HONG KONG SAR, MACAU SAR, AND TAIWAN - -As applies to licenses obtained in Taiwan and the special administrative regions, phrases throughout this Agreement containing the word "country" (for example, "the country in which the original Licensee was granted the license" and "the country in which Licensee obtained the Program license") are replaced with the following: - -(1) In Hong Kong SAR: "Hong Kong SAR" - -(2) In Macau SAR: "Macau SAR" except in the Governing Law clause (Section 14.1) - -(3) In Taiwan: "Taiwan." - -INDIA - -10.1 Items for Which IBM May be Liable - -The following replaces the terms of Items 1 and 2 of the first paragraph: - -1) liability for bodily injury (including death) or damage to real property and tangible personal property will be limited to that caused by IBM's negligence; and 2) as to any other actual damage arising in any situation involving nonperformance by IBM pursuant to, or in any way related to the subject of this Agreement, IBM's liability will be limited to the charge paid by Licensee for the individual Program that is the subject of the claim. - -13. General - -The following replaces the terms of Item 13.g: - -If no suit or other legal action is brought, within three years after the cause of action arose, in respect of any claim that either party may have against the other, the rights of the concerned party in respect of such claim will be forfeited and the other party will stand released from its obligations in respect of such claim. - -INDONESIA - -3.3 Term and Termination - -The following is added to the last paragraph: - -Both parties waive the provision of article 1266 of the Indonesian Civil Code, to the extent the article provision requires such court decree for the termination of an agreement creating mutual obligations. - -JAPAN - -13. General - -The following is inserted after Item 13.f: - -Any doubts concerning this Agreement will be initially resolved between us in good faith and in accordance with the principle of mutual trust. - -MALAYSIA - -10.2 Items for Which IBM Is not Liable - -The word "SPECIAL" in Item 10.2b is deleted. - -NEW ZEALAND - -8.1 Limited Warranty - -The following is added: - -The warranties specified in this Section are in addition to any rights Licensee may have under the Consumer Guarantees Act 1993 or other legislation which cannot be excluded or limited. The Consumer Guarantees Act 1993 will not apply in respect of any goods which IBM provides, if Licensee requires the goods for the purposes of a business as defined in that Act. - -10. Limitation of Liability - -The following is added: - -Where Programs are not obtained for the purposes of a business as defined in the Consumer Guarantees Act 1993, the limitations in this Section are subject to the limitations in that Act. - -PEOPLE'S REPUBLIC OF CHINA - -4. Charges - -The following is added: - -All banking charges incurred in the People's Republic of China will be borne by Licensee and those incurred outside the People's Republic of China will be borne by IBM. - -PHILIPPINES - -10.2 Items for Which IBM Is not Liable - -The following replaces the terms of Item 10.2b: - -b. special (including nominal and exemplary damages), moral, incidental, or indirect damages or for any economic consequential damages; or - -SINGAPORE - -10.2 Items for Which IBM Is not Liable - -The words "SPECIAL" and "ECONOMIC" are deleted from Item 10.2b. - -13. General - -The following replaces the terms of Item 13.i: - -Subject to the rights provided to IBM's suppliers and Program developers as provided in Section 10 above (Limitation of Liability), a person who is not a party to this Agreement will have no right under the Contracts (Right of Third Parties) Act to enforce any of its terms. - -TAIWAN - -8.1 Limited Warranty - -The last paragraph is deleted. - -10.1 Items for Which IBM May Be Liable - -The following sentences are deleted: - -This limit also applies to any of IBM's subcontractors and Program developers. It is the maximum for which IBM and its subcontractors and Program developers are collectively responsible. - -EUROPE, MIDDLE EAST, AFRICA (EMEA) COUNTRY AMENDMENTS - -EUROPEAN UNION MEMBER STATES - -8. Warranty and Exclusions - -The following is added to Section 8 (Warranty and Exclusion): - -In the European Union ("EU"), consumers have legal rights under applicable national legislation governing the sale of consumer goods. Such rights are not affected by the provisions set out in this Section 8 (Warranty and Exclusions). The territorial scope of the Limited Warranty is worldwide. - -EU MEMBER STATES AND THE COUNTRIES IDENTIFIED BELOW - -Iceland, Liechtenstein, Norway, Switzerland, Turkey, and any other European country that has enacted local data privacy or protection legislation similar to the EU model. - -13. General - -The following replaces Item 13.e: - -(1) Definitions - For the purposes of this Item 13.e, the following additional definitions apply: - -(a) Business Contact Information - business-related contact information disclosed by Licensee to IBM, including names, job titles, business addresses, telephone numbers and email addresses of Licensee's employees and contractors. For Austria, Italy and Switzerland, Business Contact Information also includes information about Licensee and its contractors as legal entities (for example, Licensee's revenue data and other transactional information) - -(b) Business Contact Personnel - Licensee employees and contractors to whom the Business Contact Information relates. - -(c) Data Protection Authority - the authority established by the Data Protection and Electronic Communications Legislation in the applicable country or, for non-EU countries, the authority responsible for supervising the protection of personal data in that country, or (for any of the foregoing) any duly appointed successor entity thereto. - -(d) Data Protection & Electronic Communications Legislation - (i) the applicable local legislation and regulations in force implementing the requirements of EU Directive 95/46/EC (on the protection of individuals with regard to the processing of personal data and on the free movement of such data) and of EU Directive 2002/58/EC (concerning the processing of personal data and the protection of privacy in the electronic communications sector); or (ii) for non-EU countries, the legislation and/or regulations passed in the applicable country relating to the protection of personal data and the regulation of electronic communications involving personal data, including (for any of the foregoing) any statutory replacement or modification thereof. - -(e) IBM Group - International Business Machines Corporation of Armonk, New York, USA, its subsidiaries, and their respective Business Partners and subcontractors. - -(2) Licensee authorizes IBM: - -(a) to process and use Business Contact Information within IBM Group in support of Licensee including the provision of support services, and for the purpose of furthering the business relationship between Licensee and IBM Group, including, without limitation, contacting Business Contact Personnel (by email or otherwise) and marketing IBM Group products and services (the "Specified Purpose"); and - -(b) to disclose Business Contact Information to other members of IBM Group in pursuit of the Specified Purpose only. - -(3) IBM agrees that all Business Contact Information will be processed in accordance with the Data Protection & Electronic Communications Legislation and will be used only for the Specified Purpose. - -(4) To the extent required by the Data Protection & Electronic Communications Legislation, Licensee represents that (a) it has obtained (or will obtain) any consents from (and has issued (or will issue) any notices to) the Business Contact Personnel as are necessary in order to enable IBM Group to process and use the Business Contact Information for the Specified Purpose. - -(5) Licensee authorizes IBM to transfer Business Contact Information outside the European Economic Area, provided that the transfer is made on contractual terms approved by the Data Protection Authority or the transfer is otherwise permitted under the Data Protection & Electronic Communications Legislation. - -AUSTRIA - -8.2 Exclusions - -The following is deleted from the first paragraph: - -MERCHANTABILITY, SATISFACTORY QUALITY - -10. Limitation of Liability - -The following is added: - -The following limitations and exclusions of IBM's liability do not apply for damages caused by gross negligence or willful misconduct. - -10.1 Items for Which IBM May Be Liable - -The following replaces the first sentence in the first paragraph: - -Circumstances may arise where, because of a default by IBM in the performance of its obligations under this Agreement or other liability, Licensee is entitled to recover damages from IBM. - -In the second sentence of the first paragraph, delete entirely the parenthetical phrase: - -"(including fundamental breach, negligence, misrepresentation, or other contract or tort claim)". - -10.2 Items for Which IBM Is Not Liable - -The following replaces Item 10.2b: - -b. indirect damages or consequential damages; or - -BELGIUM, FRANCE, ITALY, AND LUXEMBOURG - -10. Limitation of Liability - -The following replaces the terms of Section 10 (Limitation of Liability) in its entirety: - -Except as otherwise provided by mandatory law: - -10.1 Items for Which IBM May Be Liable - -IBM's entire liability for all claims in the aggregate for any damages and losses that may arise as a consequence of the fulfillment of its obligations under or in connection with this Agreement or due to any other cause related to this Agreement is limited to the compensation of only those damages and losses proved and actually arising as an immediate and direct consequence of the non-fulfillment of such obligations (if IBM is at fault) or of such cause, for a maximum amount equal to the charges (if the Program is subject to fixed term charges, up to twelve months' charges) Licensee paid for the Program that has caused the damages. - -The above limitation will not apply to damages for bodily injuries (including death) and damages to real property and tangible personal property for which IBM is legally liable. - -10.2 Items for Which IBM Is Not Liable - -UNDER NO CIRCUMSTANCES IS IBM OR ANY OF ITS PROGRAM DEVELOPERS LIABLE FOR ANY OF THE FOLLOWING, EVEN IF INFORMED OF THEIR POSSIBILITY: 1) LOSS OF, OR DAMAGE TO, DATA; 2) INCIDENTAL, EXEMPLARY OR INDIRECT DAMAGES, OR FOR ANY ECONOMIC CONSEQUENTIAL DAMAGES; AND / OR 3) LOST PROFITS, BUSINESS, REVENUE, GOODWILL, OR ANTICIPATED SAVINGS, EVEN IF THEY ARISE AS AN IMMEDIATE CONSEQUENCE OF THE EVENT THAT GENERATED THE DAMAGES. - -10.3 Suppliers and Program Developers - -The limitation and exclusion of liability herein agreed applies not only to the activities performed by IBM but also to the activities performed by its suppliers and Program developers, and represents the maximum amount for which IBM as well as its suppliers and Program developers are collectively responsible. - -GERMANY - -8.1 Limited Warranty - -The following is inserted at the beginning of Section 8.1: - -The Warranty Period is twelve months from the date of delivery of the Program to the original Licensee. - -8.2 Exclusions - -Section 8.2 is deleted in its entirety and replaced with the following: - -Section 8.1 defines IBM's entire warranty obligations to Licensee except as otherwise required by applicable statutory law. - -10. Limitation of Liability - -The following replaces the Limitation of Liability section in its entirety: - -a. IBM will be liable without limit for 1) loss or damage caused by a breach of an express guarantee; 2) damages or losses resulting in bodily injury (including death); and 3) damages caused intentionally or by gross negligence. - -b. In the event of loss, damage and frustrated expenditures caused by slight negligence or in breach of essential contractual obligations, IBM will be liable, regardless of the basis on which Licensee is entitled to claim damages from IBM (including fundamental breach, negligence, misrepresentation, or other contract or tort claim), per claim only up to the greater of 500,000 euro or the charges (if the Program is subject to fixed term charges, up to 12 months' charges) Licensee paid for the Program that caused the loss or damage. A number of defaults which together result in, or contribute to, substantially the same loss or damage will be treated as one default. - -c. In the event of loss, damage and frustrated expenditures caused by slight negligence, IBM will not be liable for indirect or consequential damages, even if IBM was informed about the possibility of such loss or damage. - -d. In case of delay on IBM's part: 1) IBM will pay to Licensee an amount not exceeding the loss or damage caused by IBM's delay and 2) IBM will be liable only in respect of the resulting damages that Licensee suffers, subject to the provisions of Items a and b above. - -13. General - -The following replaces the provisions of 13.g: - -Any claims resulting from this Agreement are subject to a limitation period of three years, except as stated in Section 8.1 (Limited Warranty) of this Agreement. - -The following replaces the provisions of 13.i: - -No right or cause of action for any third party is created by this Agreement, nor is IBM responsible for any third party claims against Licensee, except (to the extent permitted in Section 10 (Limitation of Liability)) for: i) bodily injury (including death); or ii) damage to real or tangible personal property for which (in either case) IBM is legally liable to that third party. - -IRELAND - -8.2 Exclusions - -The following paragraph is added: - -Except as expressly provided in these terms and conditions, or Section 12 of the Sale of Goods Act 1893 as amended by the Sale of Goods and Supply of Services Act, 1980 (the "1980 Act"), all conditions or warranties (express or implied, statutory or otherwise) are hereby excluded including, without limitation, any warranties implied by the Sale of Goods Act 1893 as amended by the 1980 Act (including, for the avoidance of doubt, Section 39 of the 1980 Act). - -IRELAND AND UNITED KINGDOM - -2. Agreement Structure - -The following sentence is added: - -Nothing in this paragraph shall have the effect of excluding or limiting liability for fraud. - -10.1 Items for Which IBM May Be Liable - -The following replaces the first paragraph of the Subsection: - -For the purposes of this section, a "Default" means any act, statement, omission or negligence on the part of IBM in connection with, or in relation to, the subject matter of an Agreement in respect of which IBM is legally liable to Licensee, whether in contract or in tort. A number of Defaults which together result in, or contribute to, substantially the same loss or damage will be treated as one Default. - -Circumstances may arise where, because of a Default by IBM in the performance of its obligations under this Agreement or other liability, Licensee is entitled to recover damages from IBM. Regardless of the basis on which Licensee is entitled to claim damages from IBM and except as expressly required by law without the possibility of contractual waiver, IBM's entire liability for any one Default will not exceed the amount of any direct damages, to the extent actually suffered by Licensee as an immediate and direct consequence of the default, up to the greater of (1) 500,000 euro (or the equivalent in local currency) or (2) 125% of the charges (if the Program is subject to fixed term charges, up to 12 months' charges) for the Program that is the subject of the claim. Notwithstanding the foregoing, the amount of any damages for bodily injury (including death) and damage to real property and tangible personal property for which IBM is legally liable is not subject to such limitation. - -10.2 Items for Which IBM is Not Liable - -The following replaces Items 10.2b and 10.2c: - -b. special, incidental, exemplary, or indirect damages or consequential damages; or - -c. wasted management time or lost profits, business, revenue, goodwill, or anticipated savings. - -Z125-3301-14 (07/2011) - - diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index b3f41ef..0000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ - - -## Relevant issue, Epic or stories. - - -## Description of what this PR accomplishes and why it's needed -Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. - -## How Has This Been Tested? - -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - -Link to ot4i-ace-docker test build: diff --git a/README.md b/README.md index d0cf02d..e9eb647 100644 --- a/README.md +++ b/README.md @@ -1,312 +1,61 @@ # Overview -IBM ACE logo +![IBM ACE logo](./app_connect_light_256x256.png) Run [IBM® App Connect Enterprise](https://developer.ibm.com/integration/docs/app-connect-enterprise/faq/) in a container. -You can build an image containing one of the following combinations: -- IBM App Connect Enterprise -- IBM App Connect Enterprise with IBM MQ Client -- IBM App Connect Enterprise for Developers -- IBM App Connect Enterprise for Developers with IBM MQ Client +This repo is designed to provide information about how to build a simple ACE container and how to extend it with extra capability as per the requirements of your use case. -The IBM App Connect operator now supports a single image which includes both the ACE server runtime as well as an MQ client. This readme will describe how you can build an equivalent image. - -Pre-built developer and production edition image can be found on IBM Container Registry - [Obtaining the IBM App Connect Enterprise server image from the IBM Cloud Container Registry](https://www.ibm.com/support/knowledgecenter/en/SSTTDS_11.0.0/com.ibm.ace.icp.doc/certc_install_obtaininstallationimageser.html) +If you would like to use pre-built containers please refer to [Pre-Built Containers](#pre-built-containers) +If your looking for information on the previous images that were documented in this repo, please refer to the previous [releases](https://github.com/ot4i/ace-docker/releases). The previous images are designed only for use with the App Connect operator. They are not designed for use in your own non operator led deployment. ## Building a container image -Download a copy of App Connect Enterprise (ie. `ace-12.0.1.0.tar.gz`) and place it in the `deps` folder. When building the image use `build-arg` to specify the name of the file: `--build-arg ACE_INSTALL=ace-12.0.1.0.tar.gz` - **Important:** Only ACE version **12.0.1.0 or greater** is supported. -Choose if you want to have an image with just App Connect Enterprise or an image with both App Connect Enterprise and the IBM MQ Client libraries. The second of these is used by the IBM App Connect operator. - -### Building a container image which contains an IBM Service provided fix for ACE - -You may have been provided with a fix for App Connect Enterprise by IBM Support, this fix will have a name of the form `12.0.X.Y-ACE-LinuxX64-TF12345.tar.gz`. This fix can be used to create a container image in one of two different ways: +Before building the image you must obtain a copy of the relavent build of ACE and make it available on a HTTP endpoint. -#### Installation during container image build -This method builds a new container image derived from an existing ACE container image and applies the ifix using the standard `mqsifixinst.sh` script. The ifix image can be built from any existing ACE container image, e.g. `ace-only`, `ace-mqclient`, or another ifix image. Simply build `Dockerfile.ifix` passing in the full `BASE_IMAGE` name and the `IFIX_ID` arguments set: +When using an insecure http endpoint, build the image using a command such as: ```bash -docker build -t ace-server:12.0.x.y-r1-tfit12345 --build-arg BASE_IMAGE=ace-server:12.0.x.y-1 --build-arg IFIX_ID=12.0.X.Y-ACE-LinuxX64-TFIT12345 --file ubi/Dockerfile.ifix path/to/folder/containing/ifix -``` - -#### Pre-applying the fix to the ACE install image -This method applies the ifix directly to the ACE installation image that is consumed to make the full container image. **NB**: Only follow these instructions if you have been instructed by IBM Support to "manually install" the ifix, or that the above method is not applicable to your issue. If you follow these instructions then the ifix ID will _not_ appear in the output of `mqsiservice -v`. - -In order to apply this fix manually follow these steps. - - On a local system extract the App Connect Enterprise archive - `tar -xvf ace-12.0.1.0.tar.gz` - - Extract the fix package into expanded App Connect Enterprise installation - `tar -xvf /path/to/12.0.1.0-ACE-LinuxX64-TF12345.tar.gz --directory ace-12.0.1.0` - - Tar and compress the resulting App Connect Enterprise installation - `tar -cvf ace-12.0.1.0_with_IT12345.tar ace-12.0.1.0` - `gzip ace-12.0.1.0_with_IT12345.tar` - - Place the resulting `ace-12.0.1.0_with_IT12345.tar.gz` file in the `deps` folder and when building using the `build-arg` to specify the name of the file: `--build-arg ACE_INSTALL=ace-12.0.1.0_with_IT12345.tar.gz` - -### Using App Connect Enterprise for Developers - -Get [ACE for Developers edition](https://www.ibm.com/marketing/iwm/iwm/web/pick.do?source=swg-wmbfd). Then place it in the `deps` folder as mentioned above. - -### Build an image with App Connect Enterprise only - -NOTE: The current dockerfiles are tailored towards use by the App Connect Operator and as a result may have function removed from it if we are no longer using it in our operator. If you prefer to use the old dockerfiles for building your containers please use the `Dockerfile-legacy.aceonly` file - - -The `deps` folder must contain a copy of ACE, **version 12.0.1.0 or greater**. If using ACE for Developers, download it from [here](https://www.ibm.com/marketing/iwm/iwm/web/pick.do?source=swg-wmbfd). -Then set the build argument `ACE_INSTALL` to the name of the ACE file placed in `deps`. - -1. ACE for Developers only: - - `docker build -t ace-dev-only --build-arg ACE_INSTALL={ACE-dev-file-in-deps-folder} --file ubi/Dockerfile.aceonly .` -2. ACE production only: - - `docker build -t ace-only --build-arg ACE_INSTALL={ACE-file-in-deps-folder} --file ubi/Dockerfile.aceonly .` - -### Build an image with App Connect Enterprise and MQ Client - -Follow the instructions above for building an image with App Connect Enterprise Only. - -Add the MQ Client libraries to your existing image by running `docker build -t ace-mqclient --build-arg BASE_IMAGE= --file ubi/Dockerfile.mqclient .` - -`` is the tag of the image you want to add the client libs to i.e. ace-only. You can supply a customer URL for the MQ binaries by setting the argument MQ_URL - -### Applying an iFix to an existing image - -If you need to apply an iFix to an existing image, this can be done using the following Dockerfile sample. It requires the iFix to use the mqsifixinst.sh installation method. - -```Dockerfile -ARG ACE_IMAGE=cp.icr.io/cp/appc/ace-server-prod@sha256:f31b9adcfd4a77ba8c62b92c6f34985ef1f2d53e8082f628f170013eaf4c9003 -FROM $ACE_IMAGE - -ENV IFIX_TAR=12.0.2.0-ACE-LinuxX64-TFIT38649.tar.gz -ADD $IFIX_TAR ./fix - -USER root - -RUN cd /home/aceuser/fix \ - && IFIX=${IFIX_TAR::${#IFIX_TAR}-7} \ - && ./mqsifixinst.sh /opt/ibm/ace-12 install $IFIX \ - && cd /home/aceuser \ - && rm -rf /home/aceuser/fix - -USER 1000 +docker build -t ace --build-arg DOWNLOAD_URL=${DOWNLOAD_URL} --file ./Dockerfile . ``` -Update the Dockerfile such that - -- ACE_IMAGE points at your base image -- IFIX_TAR is the name of the iFix download file. This must be located in the same directory as the Dockerfile -- USER must be updated to match the ID of the ace user - -To build the image use a command such as the following: +If you want to connect to a secure endpoint build the image using a command such as: +i.e. ```bash -docker build -f Dockerfile --tag myregistry.com/ace/ace-server-prod:12.0.2.0-ACE-LinuxX64-TFIT38649 . +docker build -t ace --build-arg USERNAME= --build-arg PASSWORD= --build-arg DOWNLOAD_URL=${DOWNLOAD_URL} --file ./Dockerfile . ``` -The resulting inage should then be used using the same options as the original base image - -## Usage - -### Accepting the License - -In order to use the image, it is necessary to accept the terms of the IBM App Connect Enterprise 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. - -### Red Hat OpenShift SecurityContextConstraints Requirements - -The predefined SecurityContextConstraint (SCC) `restricted` has been verified with the image when being run in a Red Hat OpenShift environment. - -### Running the container - -To run a container with ACE only with default configuration and these settings: - -- ACE server name `ACESERVER` -- listener for ACE web ui on port `7600` -- listener for ACE HTTP on port `7600` -run the following command: - -`docker run --name aceserver -p 7600:7600 -p 7800:7800 -p 7843:7843 --env LICENSE=accept --env ACE_SERVER_NAME=ACESERVER ace-only:latest` - -Once the console shows that the integration server is listening on port 7600, you can go to the ACE UI at http://localhost:7600/. To stop the container, run `docker stop aceserver` and the container will shut down cleanly, stopping the integration server. - -### Sample image - -In the `sample` folder there is an example on how to build a server image with a set of configuration and BAR files based on a previously built ACE image. **[How to use the sample.](sample/README.md)** - -### Environment variables supported by this image - -- **LICENSE** - Set this to `accept` to agree to the App Connect Enterprise 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. -- **LOG_FORMAT** - Set this to change the format of the logs which are printed on the container's stdout. Set to "json" to use JSON format (JSON object per line); set to "basic" to use a simple human-readable format. Defaults to "basic". -- **ACE_ENABLE_METRICS** - Set this to `true` to generate Prometheus metrics for your Integration Server. -- **ACE_SERVER_NAME** - Set this to the name you want your Integration Server to run with. -- **ACE_TRUSTSTORE_PASSWORD** - Set this to the password you wish to use for the trust store (if using one). -- **ACE_KEYSTORE_PASSWORD** - Set this to the password you wish to use for the key store (if using one). - -- **ACE_ADMIN_SERVER_SECURITY** - Set to `true` if you intend to secure your Integration Server using SSL. -- **ACE_ADMIN_SERVER_NAME** - Set this to the DNS name of your Integration Server for SSL SAN checking. -- **ACE_ADMIN_SERVER_CA** - Set this to your Integration Server SSL CA certificates folder. -- **ACE_ADMIN_SERVER_CERT** - Set this to your Integration Server SSL certificate. -- **ACE_ADMIN_SERVER_KEY** - Set this to your Integration Server SSL key certificate. - -- **FORCE_FLOW_HTTPS** - Set to 'true' and the *.key and *.crt present in `/home/aceuser/httpsNodeCerts/` are used to force all your flows to use https - -## How to dynamically configure the ACE Integration Server - -To enable dynamic configuration of the ACE Integration Server, this setup supports configuration injected into the image as files. - -Before the Integration Server starts, the container is checked for the folder `/home/aceuser/initial-config`. For each folder in `/home/aceuser/initial-config` a script called `ace_config_{folder-name}.sh` will be run to process the information in the folder. -Shell scripts are supplied for the list of folders below, but you can extend this mechanism by adding your own folders and associated shell scripts. - -- **Note**: The work dir for the Integration Server in the image is `/home/aceuser/ace-server`. -- **Note**: An example `initial-config` directory with data can be found in the `sample` folder, as well as the [command on how to mount it when running the image]((sample/README.md#run-the-sample-image). - -You can mount the following file structure at `/home/aceuser/initial-config`. Missing folders will be skipped, but *empty* folders will cause an error: - -- `/home/aceuser/initial-config/keystore` - - A text file containing a certificate file in PEM format. This will be imported into the keystore file, along with the private key. The filename must be the *alias* for the certificate in the keystore, with the suffix `.crt`. The alias must not contain any whitespace characters. - - A text file containing a private key file in PEM format. This will be imported into the keystore file, along with the certificate. The filename must be the *alias* for the certificate in the keystore, with the suffix `.key`. - - If the private key is encrypted, then the passphrase may be specified in a file with the filename of *alias* with the suffix `.pass`. - - The keystore file that will be created for these files needs a password. You must set the keystore password using the environment variable `ACE_KEYSTORE_PASSWORD`. - - You can place multiple sets of files, each with a different file name/alias; each `.crt` file must have an associated `.key` file, and a `.pass` file must be present if the private key has a passphrase. -- `/home/aceuser/initial-config/odbcini` - - A text file called `odbc.ini`. This must be an `odbc.ini` file suitable for the Integration Server to use when connecting to a database. This will be copied to `/home/aceuser/ace-server/odbc.ini`. -- `/home/aceuser/initial-config/policy` - - A set of `.policyxml` files, each with the suffix `.policyxml`, and a single `policy.descriptor` file. These will be copied to `/home/aceuser/ace-server/overrides/DefaultPolicies/`. They should be specified in the `server.conf.yaml` section in order to be used. -- `/home/aceuser/initial-config/serverconf` - - A text file called `server.conf.yaml` that contains a `server.conf.yaml` overrides file. This will be copied to `/home/aceuser/ace-server/overrides/server.conf.yaml` -- `/home/aceuser/initial-config/setdbparms` - - For any parameters that need to be set via `mqsisetdbparms` include a text file called `setdbparms.txt` This supports 2 formats: - - ```script - # Lines starting with a "#" are ignored - # Each line which starts mqsisetdbparms will be run as written - # Alternatively each line should specify the , separated by a single space - # Each line will be processed by calling... - # mqsisetdbparms ${ACE_SERVER_NAME} -n -u -p - resource1 user1 password1 - resource2 user2 password2 - mqsisetdbparms -w /home/aceuser/ace-server -n salesforce::SecurityIdentity -u myUsername -p myPassword -c myClientID -s myClientSecret - ``` - -- `/home/aceuser/initial-config/truststore` - - A text file containing a certificate file in PEM format. This will be imported into the truststore file as a trusted Certificate Authority's certificate. The filename must be the *alias* for the certificate in the keystore, with the suffix `.crt`. The alias must not contain any whitespace characters. - - The truststore file that will be created for these files needs a password. You must set a truststore password using the environment variable `ACE_TRUSTSTORE_PASSWORD` - - You can place multiple files, each with a different file name/alias. -- `/home/aceuser/initial-config/webusers` - - A text file called `admin-users.txt`. It contains a list of users to be created as `admin` users using the command `mqsiwebuseradmin`. These users will have READ, WRITE and EXECUTE access on the Integration Server. The file has the following format: - - ```script - # Lines starting with a "#" are ignored - # Each line should specify the , separated by a single space - # Each user will have "READ", "WRITE" and "EXECUTE" access on the integration server - # Each line will be processed by calling... - # mqsiwebuseradmin -w /home/aceuser/ace-server -c -u -a -r admin - admin1 password1 - admin2 password2 - ``` - - - A text file called `operator-users.txt`. It contains a list of users to be created as `operator` users using the command `mqsiwebuseradmin`. These users will have READ and EXECUTE access on the Integration Server. The file has the following format: - - ```script - # Lines starting with a "#" are ignored - # Each line should specify the , separated by a single space - # Each user will have "READ" and "EXECUTE" access on the integration server - # Each line will be processed by calling... - # mqsiwebuseradmin -w /home/aceuser/ace-server -c -u -a -r operator - operator1 password1 - operator2 password2 - ``` - - - A text file called `editor-users.txt`. It contains a list of users to be created as `editor` users using the command `mqsiwebuseradmin`. These users will have READ and WRITE access on the Integration Server. The file has the following format: - - ```script - # Lines starting with a "#" are ignored - # Each line should specify the , separated by a single space - # Each user will have "READ" and "WRITE" access on the integration server - # Each line will be processed by calling... - # mqsiwebuseradmin -w /home/aceuser/ace-server -c -u -a -r editor - editor1 password1 - editor2 password2 - ``` - - - A text file called `audit-users.txt`. It contains a list of users to be created as `audit` users using the command `mqsiwebuseradmin`. These users will have READ access on the Integration Server. The file has the following format: - - ```script - # Lines starting with a "#" are ignored - # Each line should specify the , separated by a single space - # Each user will have "READ" access on the integration server - # Each line will be processed by calling... - # mqsiwebuseradmin -w /home/aceuser/ace-server -c -u -a -r audit - audit1 password1 - audit2 password2 - ``` - - - A text file called `viewer-users.txt`. It contains a list of users to be created as `viewer` users using the command `mqsiwebuseradmin`. These users will have READ access on the Integration Server. The file has the following format: - - ```script - # Lines starting with a "#" are ignored - # Each line should specify the , separated by a single space - # Each user will have "READ" access on the integration server - # Each line will be processed by calling... - # mqsiwebuseradmin -w /home/aceuser/ace-server -c -u -a -r viewer - viewer1 password1 - viewer2 password2 - ``` - -- `/home/aceuser/initial-config/agent` - - A json file called 'switch.json' containing configuration information for the switch, this will be copied into the appropriate iibswitch directory - - A json file called 'agentx.json' containing configuration information for the agent connectivity, this will be copied into the appropriate iibswitch directory - - A json file called 'agenta.json' containing configuration information for the agent connectivity, this will be copied into the appropriate iibswitch directory - - A json file called 'agentc.json' containing configuration information for the agent connectivity, this will be copied into the appropriate iibswitch directory - - A json file called 'agentp.json' containing configuration information for the agent connectivity, this will be copied into the appropriate iibswitch directory -- `/home/aceuser/initial-config/extensions` - - A zip file called `extensions.zip` will be extracted into the directory `/home/aceuser/ace-server/extensions`. This allows you to place extra files into a directory you can then reference in, for example, the server.conf.yaml -- `/home/aceuser/initial-config/ssl` - - A pem file called 'ca.crt' will be extracted into the directory `/home/aceuser/ace-server/ssl` - - A pem file called 'tls.key' will be extracted into the directory `/home/aceuser/ace-server/ssl` - - A pem file called 'tls.cert' will be extracted into the directory `/home/aceuser/ace-server/ssl` -- `/home/aceuser/initial-config/bar_overrides` - - For any parameters that need to be set via `mqsiapplybaroverride` include text files with extension `.properties` Eg: - ```script - sampleFlow#MQInput.queueName=NEWC - ``` -- `/home/aceuser/initial-config/workdir_overrides` - - For any parameters that need to be set via `ibm int apply overrides --work-directory /home/aceuser/ace-server` on the integration server include text files Eg: - ```script - TestFlow#HTTP Input.URLSpecifier=/production - ``` - - -## Logging +NOTE: If no DOWNLOAD_URL is provided the build will use a copy of the App Connect Enterprise developer edition as referenced in the Dockerfile -The logs from the integration server running within the container will log to standard out. The log entries can be output in two format: +### Running the image -- basic: human-headable for use in development when using `docker logs` or `kubectl logs` -- json: for pushing into ELK stack for searching and visualising in Kibana +To run the image use a command such as -The output format is controlled by the `LOG_FORMAT` environment variable +`docker run -d -p 7600:7600 -p 7800:7800 -e LICENSE=accept ace:latest` -A sample Kibana dashboard is available at sample/dashboards/ibm-ace-kibana-dashboard.json +### Extending the image -## Monitoring +To add extra artifacts into the container such as server.conf.yaml overrides, bars files etc please refer to the sample on adding files in [Samples](samples/README.md) -The accounting and statistics feature in IBM App Connect Enterprise provides the component level data with detailed insight into the running message flows to enabled problem determination, profiling, capacity planning, situation alert monitoring and charge-back modelling. +## Pre-Built Containers -A Prometheus exporter runs on port 9483 if `ACE_ENABLE_METRICS` is set to `true` - the exporter listens for accounting and statistics, and resource statistics, data on a websocket from the integration server, then aggregates this data to make available to Prometheus when requested. +Pre-built production images can be found on IBM Container Registry at `cp.icr.io/cp/appc/ace` - [Building a sample IBM App Connect Enterprise image using Docker](https://www.ibm.com/docs/en/app-connect/12.0?topic=cacerid-building-sample-app-connect-enterprise-image-using-docker) -A sample Grafana dashboard is available at sample/dashboards/ibm-ace-grafana-dashboard.json +### Fixing security vulnerabilities in the base software -## License +If you find there are vulnerabilities in the base redhat software there are two options to fix this -The Dockerfile and associated scripts are licensed under the [Eclipse Public License 2.0](LICENSE). Licenses for the products installed within the images are as follows: +- Apply the fix yourself using a sample docker file to install all available updates - [Samples/updateBase](samples/updateBase/Dockerfile) +- Pick up the latest version of the image which will include all fixes at the point it was built. -- IBM App Connect Enterprise for Developers is licensed under the IBM International License Agreement for Non-Warranted Programs. This license may be viewed from the image using the `LICENSE=view` environment variable as described above. +### Fixing issues with ACE -Note that the IBM App Connect Enterprise for Developers license does not permit further distribution. +If you find a problem with ACE software, raise a PMR to obtain a fix. Once the fix is provided this can be applied to any existing image using a [Samples](samples/README.md#ifix-sample) dockerfile -## Copyright +## Support -© Copyright IBM Corporation 2015, 2018 +All information provided in this repo as supported as-is. diff --git a/ace_compile_bars.sh b/ace_compile_bars.sh deleted file mode 100755 index a1ebbf2..0000000 --- a/ace_compile_bars.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -if ls /home/aceuser/bars/*.bar >/dev/null 2>&1; then - for bar in /home/aceuser/bars/*.bar - do - mqsibar -a $bar -w /home/aceuser/ace-server -c - done -fi diff --git a/ace_config_agent.sh b/ace_config_agent.sh deleted file mode 100755 index c61a8a5..0000000 --- a/ace_config_agent.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling agent configuration" - -if [ -s "/home/aceuser/initial-config/agent/switch.json" ]; then - mkdir -p /home/aceuser/ace-server/config/iibswitch/switch - cp /home/aceuser/initial-config/agent/switch.json /home/aceuser/ace-server/config/iibswitch/switch/switch.json -fi - -if [ -s "/home/aceuser/initial-config/agent/agentx.json" ]; then - mkdir -p /home/aceuser/ace-server/config/iibswitch/agentx - cp /home/aceuser/initial-config/agent/agentx.json /home/aceuser/ace-server/config/iibswitch/agentx/agentx.json -fi - -if [ -s "/home/aceuser/initial-config/agent/agenta.json" ]; then - mkdir -p /home/aceuser/ace-server/config/iibswitch/agenta - cp /home/aceuser/initial-config/agent/agenta.json /home/aceuser/ace-server/config/iibswitch/agenta/agenta.json -fi - -if [ -s "/home/aceuser/initial-config/agent/agentp.json" ]; then - mkdir -p /home/aceuser/ace-server/config/iibswitch/agentp - cp /home/aceuser/initial-config/agent/agentp.json /home/aceuser/ace-server/config/iibswitch/agentp/agentp.json -fi - -if [ -s "/home/aceuser/initial-config/agent/agentc.json" ]; then - mkdir -p /home/aceuser/ace-server/config/iibswitch/agentc - cp /home/aceuser/initial-config/agent/agentc.json /home/aceuser/ace-server/config/iibswitch/agentc/agentc.json -fi - -log "agent configuration complete" diff --git a/ace_config_bar_overrides.sh b/ace_config_bar_overrides.sh deleted file mode 100755 index 718efb0..0000000 --- a/ace_config_bar_overrides.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -if ls /home/aceuser/initial-config/bar_overrides/*.properties >/dev/null 2>&1; then - for propertyFile in /home/aceuser/initial-config/bar_overrides/*.properties - do - for bar in /home/aceuser/initial-config/bars/*.bar - do - mqsiapplybaroverride -b $bar -p $propertyFile -r - echo $propertyFile >> /home/aceuser/initial-config/bar_overrides/logs.txt - done - done -fi \ No newline at end of file diff --git a/ace_config_bars.sh b/ace_config_bars.sh deleted file mode 100755 index d9fef56..0000000 --- a/ace_config_bars.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -if ls /home/aceuser/initial-config/bars/*.bar >/dev/null 2>&1; then - for bar in /home/aceuser/initial-config/bars/*.bar - do - mqsibar -a $bar -w /home/aceuser/ace-server - done -fi diff --git a/ace_config_extensions.sh b/ace_config_extensions.sh deleted file mode 100755 index dafa802..0000000 --- a/ace_config_extensions.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling extensions configuration" - -if [ -s "/home/aceuser/initial-config/extensions/extensions.zip" ]; then - mkdir /home/aceuser/ace-server/extensions - unzip /home/aceuser/initial-config/extensions/extensions.zip -d /home/aceuser/ace-server/extensions -fi - -log "extensions configuration complete" diff --git a/ace_config_keystore.sh b/ace_config_keystore.sh deleted file mode 100755 index e213fe8..0000000 --- a/ace_config_keystore.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling keystore configuration" - -if ls /home/aceuser/initial-config/keystore/*.key >/dev/null 2>&1; then - - if [ $(cat /home/aceuser/initial-config/keystore/*.key | wc -l) -gt 0 ]; then - if [ -f /home/aceuser/ace-server/keystore.jks ]; then - OUTPUT=$(rm /home/aceuser/ace-server/keystore.jks 2>&1) - logAndExitIfError $? "${OUTPUT}" - fi - fi - - IFS=$'\n' - KEYTOOL=/opt/ibm/ace-12/common/jdk/jre/bin/keytool - if [ ! -f "$KEYTOOL" ]; then - KEYTOOL=/opt/ibm/ace-12/common/jre/bin/keytool - fi - for keyfile in `ls /home/aceuser/initial-config/keystore/*.key`; do - if [ -s "${keyfile}" ]; then - if [ -z "${ACE_KEYSTORE_PASSWORD}" ]; then - log "No keystore password defined" - exit 1 - fi - - filename=$(basename ${keyfile}) - dirname=$(dirname ${keyfile}) - alias=$(echo ${filename} | sed -e 's/\.key$'//) - certfile=${dirname}/${alias}.crt - passphrasefile=${dirname}/${alias}.pass - - if [ ! -f ${certfile} ]; then - log "Certificate file ${certfile} not found." - exit 1 - fi - - if [ -f ${passphrasefile} ];then - ACE_PRI_KEY_PASS=$(cat ${passphrasefile}) - OUTPUT=$(openssl pkcs12 -export -in ${certfile} -inkey ${keyfile} -passin pass:${ACE_PRI_KEY_PASS} -out /home/aceuser/ace-server/keystore.p12 -name ${alias} -password pass:${ACE_KEYSTORE_PASSWORD} 2>&1) - else - OUTPUT=$(openssl pkcs12 -export -in ${certfile} -inkey ${keyfile} -out /home/aceuser/ace-server/keystore.p12 -name ${alias} -password pass:${ACE_KEYSTORE_PASSWORD} 2>&1) - fi - logAndExitIfError $? "${OUTPUT}" - - OUTPUT=$(${KEYTOOL} -importkeystore -srckeystore /home/aceuser/ace-server/keystore.p12 -destkeystore /home/aceuser/ace-server/keystore.jks -srcstorepass ${ACE_KEYSTORE_PASSWORD} -deststorepass ${ACE_KEYSTORE_PASSWORD} -srcalias ${alias} -destalias ${alias} -srcstoretype PKCS12 -noprompt 2>&1) - logAndExitIfError $? "${OUTPUT}" - - OUTPUT=$(rm /home/aceuser/ace-server/keystore.p12 2>&1) - logAndExitIfError $? "${OUTPUT}" - fi - done -fi - -log "Keystore configuration complete" diff --git a/ace_config_odbcini.sh b/ace_config_odbcini.sh deleted file mode 100755 index 7e7e3e7..0000000 --- a/ace_config_odbcini.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling odbcini configuration" - -if [ -s "/home/aceuser/initial-config/odbcini/odbc.ini" ]; then - ODBCINI=/home/aceuser/ace-server/odbc.ini - cp /home/aceuser/initial-config/odbcini/odbc.ini ${ODBCINI} -fi - -log "Odbcini configuration complete" diff --git a/ace_config_policy.sh b/ace_config_policy.sh deleted file mode 100755 index 428e5ff..0000000 --- a/ace_config_policy.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling policy configuration" - -mkdir /home/aceuser/ace-server/overrides/DefaultPolicies - -if ls /home/aceuser/initial-config/policy/*.policyxml >/dev/null 2>&1; then - for policyfile in `ls /home/aceuser/initial-config/policy/*.policyxml`; do - if [ -s "${policyfile}" ]; then - cp "${policyfile}" /home/aceuser/ace-server/overrides/DefaultPolicies/. - fi - done -fi - -if [ -s "/home/aceuser/initial-config/policy/policy.descriptor" ]; then - cp /home/aceuser/initial-config/policy/policy.descriptor /home/aceuser/ace-server/overrides/DefaultPolicies/policy.descriptor -fi - -log "Policy configuration complete" diff --git a/ace_config_serverconf.sh b/ace_config_serverconf.sh deleted file mode 100755 index 0d18ded..0000000 --- a/ace_config_serverconf.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling server.conf configuration" - -if [ -s "/home/aceuser/initial-config/serverconf/server.conf.yaml" ]; then - cp /home/aceuser/initial-config/serverconf/server.conf.yaml /home/aceuser/ace-server/overrides/server.conf.yaml -fi - -log "server.conf configuration complete" diff --git a/ace_config_ssl.sh b/ace_config_ssl.sh deleted file mode 100755 index ea521cf..0000000 --- a/ace_config_ssl.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling SSL files" - -mkdir /home/aceuser/ace-server/ssl/ - -if ls /home/aceuser/initial-config/ssl/* >/dev/null 2>&1; then - for sslfile in `ls /home/aceuser/initial-config/ssl/*`; do - if [ -s "${sslfile}" ]; then - cp "${sslfile}" /home/aceuser/ace-server/ssl/. - fi - done -fi - -log "SSL configuration complete" diff --git a/ace_config_truststore.sh b/ace_config_truststore.sh deleted file mode 100755 index 979a17b..0000000 --- a/ace_config_truststore.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Handling truststore configuration" - -if ls /home/aceuser/initial-config/truststore/*.crt >/dev/null 2>&1; then - - if [ $(cat /home/aceuser/initial-config/truststore/*.crt | wc -l) -gt 0 ]; then - if [ -f /home/aceuser/ace-server/truststore.jks ]; then - OUTPUT=$(rm /home/aceuser/ace-server/truststore.jks 2>&1) - logAndExitIfError $? "${OUTPUT}" - fi - fi - - IFS=$'\n' - KEYTOOL=/opt/ibm/ace-12/common/jdk/jre/bin/keytool - if [ ! -f "$KEYTOOL" ]; then - KEYTOOL=/opt/ibm/ace-12/common/jre/bin/keytool - fi - for file in `ls /home/aceuser/initial-config/truststore/*.crt`; do - if [ -s "${file}" ]; then - if [ -z "${ACE_TRUSTSTORE_PASSWORD}" ]; then - log "No truststore password defined" - exit 1 - fi - - filename=$(basename $file) - alias=$(echo $filename | sed -e 's/\.crt$'//) - OUTPUT=$(${KEYTOOL} -importcert -trustcacerts -alias ${filename} -file ${file} -keystore /home/aceuser/ace-server/truststore.jks -storepass ${ACE_TRUSTSTORE_PASSWORD} -noprompt 2>&1) - logAndExitIfError $? "${OUTPUT}" - fi - done -fi - -log "Truststore configuration complete" diff --git a/ace_config_workdir_overrides.sh b/ace_config_workdir_overrides.sh deleted file mode 100755 index 207d16e..0000000 --- a/ace_config_workdir_overrides.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2021. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -if ls /home/aceuser/initial-config/workdir_overrides/* >/dev/null 2>&1; then - for workdiroverride in /home/aceuser/initial-config/workdir_overrides/* - do - ibmint apply overrides $workdiroverride --work-directory /home/aceuser/ace-server - done -fi diff --git a/ace_env.sh b/ace_env.sh deleted file mode 100755 index 20ac4e3..0000000 --- a/ace_env.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi diff --git a/ace_forceflowhttps.sh b/ace_forceflowhttps.sh deleted file mode 100755 index 289cc83..0000000 --- a/ace_forceflowhttps.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2021. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source ${SCRIPT_DIR}/ace_config_logging.sh - -log "Creating force flows to be https keystore" - -if [ -f /home/aceuser/ace-server/https-keystore.p12 ]; then - OUTPUT=$(rm /home/aceuser/ace-server/https-keystore.p12 2>&1) - logAndExitIfError $? "${OUTPUT}" -fi - -IFS=$'\n' -KEYTOOL=/opt/ibm/ace-12/common/jdk/jre/bin/keytool -if [ ! -f "$KEYTOOL" ]; then - KEYTOOL=/opt/ibm/ace-12/common/jre/bin/keytool -fi - -if [ ! -f /home/aceuser/httpsNodeCerts/*.key ]; then - log "No keystore files found at location /home/aceuser/httpsNodeCerts/*.key cannot create Force Flows HTTPS keystore" - exit 1 -fi - -for keyfile in `ls /home/aceuser/httpsNodeCerts/*.key`; do - if [ -s "${keyfile}" ]; then - if [ -z "${1}" ]; then - log "No keystore password defined" - exit 1 - fi - - filename=$(basename ${keyfile}) - dirname=$(dirname ${keyfile}) - alias=$(echo ${filename} | sed -e 's/\.key$'//) - certfile=${dirname}/${alias}.crt - passphrasefile=${dirname}/${alias}.pass - - if [ ! -f ${certfile} ]; then - log "Certificate file ${certfile} not found." - exit 1 - fi - - OUTPUT=$(openssl pkcs12 -export -in ${certfile} -inkey ${keyfile} -out /home/aceuser/ace-server/https-keystore.p12 -name ${alias} -password pass:${1} 2>&1) - logAndExitIfError $? "${OUTPUT}" - - log "Setting https keystore password" - cmd="mqsisetdbparms -w /home/aceuser/ace-server -n brokerHTTPSKeystore::password -u anything -p \"${1}\" 2>&1" - OUTPUT=`eval "$cmd"` - echo $OUTPUT - - fi -done - -log "Force flows to be https keystore creation complete" diff --git a/ace_integration_server.sh b/ace_integration_server.sh deleted file mode 100755 index 0cb1dfe..0000000 --- a/ace_integration_server.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -# Enable TLS on both MQ and DB2 -if [ -d /opt/mqm/gskit8/lib64 ]; then - export LD_LIBRARY_PATH=/opt/mqm/gskit8/lib64:$LD_LIBRARY_PATH -fi - -if [ -s /home/aceuser/ace-server/odbc.ini ]; then - export ODBCINI=/home/aceuser/ace-server/odbc.ini -fi - -# For customers running pod environment -if [ -s /home/aceuser/generic/odbcinst.ini ]; then - export ODBCSYSINI=/home/aceuser/generic -fi - -# For customers running ace in docker themselves -if [ -s /home/aceuser/ace-server/extensions/odbcinst.ini ]; then - export ODBCSYSINI=/home/aceuser/ace-server/extensions -fi - -# We need to keep the kubernetes port overrides as customers could be running ace in docker themselves -# but we need to allow the ports to be overwritten in the pod environment if set by the operator -if ! [[ -z "${KUBERNETES_PORT}" ]] && ! [[ -z "${SERVICE_NAME}" ]] && ! [[ -z "${MQSI_OVERRIDE_HTTP_PORT}" ]] && ! [[ -z "${MQSI_OVERRIDE_HTTPS_PORT}" ]] ; then - . /home/aceuser/portOverrides -fi - -exec IntegrationServer $* diff --git a/ace_license_check.sh b/ace_license_check.sh deleted file mode 100755 index 0d80acf..0000000 --- a/ace_license_check.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ "$LICENSE" = "accept" ]; then - exit 0 -elif [ "$LICENSE" = "view" ]; then - case "$LANG" in - zh_TW*) LICENSE_FILE=Chinese_TW.txt ;; - zh*) LICENSE_FILE=Chinese.txt ;; - cs*) LICENSE_FILE=Czech.txt ;; - en*) LICENSE_FILE=English.txt ;; - fr*) LICENSE_FILE=French.txt ;; - de*) LICENSE_FILE=German.txt ;; - el*) LICENSE_FILE=Greek.txt ;; - id*) LICENSE_FILE=Indonesian.txt ;; - it*) LICENSE_FILE=Italian.txt ;; - ja*) LICENSE_FILE=Japanese.txt ;; - ko*) LICENSE_FILE=Korean.txt ;; - lt*) LICENSE_FILE=Lithuanian.txt ;; - pl*) LICENSE_FILE=Polish.txt ;; - pt*) LICENSE_FILE=Portuguese.txt ;; - ru*) LICENSE_FILE=Russian.txt ;; - sl*) LICENSE_FILE=Slovenian.txt ;; - es*) LICENSE_FILE=Spanish.txt ;; - tr*) LICENSE_FILE=Turkish.txt ;; - *) LICENSE_FILE=English.txt ;; - esac - cat /opt/ibm/ace-12/license/$LICENSE_FILE - exit 1 -else - echo -e "Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions.\n\nLicense agreements and information can be viewed by running this image with the environment variable LICENSE=view. You can also set the LANG environment variable to view the license in a different language." - exit 1 -fi diff --git a/ace_mqsicommand.sh b/ace_mqsicommand.sh deleted file mode 100755 index 4efa025..0000000 --- a/ace_mqsicommand.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# © Copyright IBM Corporation 2018. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v2.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v20.html - -if [ -z "$MQSI_VERSION" ]; then - source /opt/ibm/ace-12/server/bin/mqsiprofile -fi - -mqsi$* diff --git a/appconnect_enterprise_logo.svg b/appconnect_enterprise_logo.svg deleted file mode 100644 index 85b78c5..0000000 --- a/appconnect_enterprise_logo.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - appconnect - Created with Sketch. - - - - - - - - - - - - - - - diff --git a/build-rhel.sh b/build-rhel.sh deleted file mode 100755 index ce53a98..0000000 --- a/build-rhel.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -ex -echo "Building ACE build container" -buildType=$1 -buildTag=$2 -aceInstall=$3 -mqImage=$4 - - -if [ -z "$aceInstall" ] -then - echo "Building temporary container with default ACE install parameters" - docker build --no-cache --build-arg -t ace/builder:11.0.0.4 . -f ./rhel/Dockerfile.build -else - echo "Building temporary container with ACE install $buildType" - docker build --no-cache --build-arg ACE_INSTALL=$aceInstall -t ace/builder:11.0.0.4 . -f ./rhel/Dockerfile.build -fi - -docker create --name builder ace/builder:11.0.0.4 -docker cp builder:/opt/ibm/ace-12 ./rhel/ace-12 -docker cp builder:/go/src/github.com/ot4i/ace-docker/runaceserver ./rhel/runaceserver -docker cp builder:/go/src/github.com/ot4i/ace-docker/chkaceready ./rhel/chkaceready -docker cp builder:/go/src/github.com/ot4i/ace-docker/chkacehealthy ./rhel/chkacehealthy -docker rm -f builder - -echo "Building ACE runtime container" - -# Replace the FROM statement to use the MQ container -sed -i "s%^FROM .*%FROM $mqImage%" ./rhel/Dockerfile.acemqrhel - -case $buildType in -"ace-dev-only") - echo "Building ACE only for development" - docker build --no-cache -t $buildTag -f ./rhel/Dockerfile.acerhel . - ;; -"ace-only") - echo "Building ACE only for production" - docker build --no-cache -t $buildTag -f ./rhel/Dockerfile.acerhel . - ;; -"ace-mq") - echo "Building ACE with MQ for production" - docker build --no-cache -t $buildTag --build-arg BASE_IMAGE=$mqImage -f ./rhel/Dockerfile.acemqrhel . - ;; -"ace-dev-mq-dev") - echo "Building ACE with MQ for production" - docker build --no-cache -t $buildTag --build-arg BASE_IMAGE=$mqImage -f ./rhel/Dockerfile.acemqrhel . - ;; -*) echo "Invalid option" - ;; -esac diff --git a/cmd/chkacehealthy/.gitignore b/cmd/chkacehealthy/.gitignore deleted file mode 100644 index 1349ec4..0000000 --- a/cmd/chkacehealthy/.gitignore +++ /dev/null @@ -1 +0,0 @@ -chkacehealthy \ No newline at end of file diff --git a/cmd/chkacehealthy/main.go b/cmd/chkacehealthy/main.go deleted file mode 100644 index 987633e..0000000 --- a/cmd/chkacehealthy/main.go +++ /dev/null @@ -1,177 +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. -*/ - -// chkacelively checks that ACE is still runing, by checking if the admin REST endpoint port is available. -package main - -import ( - "context" - "fmt" - "log" - "net" - "net/http" - "os" - "time" - - "k8s.io/apimachinery/pkg/api/errors" -) - -var checkACE = checkACElocal -var httpCheck = httpChecklocal -var socketCheck = socketChecklocal -var osExit = os.Exit - -const restartIsTimeoutInSeconds = 60 - -var netDial = net.Dial -var httpGet = http.Get - -func main() { - - err := checkACE() - if err != nil { - log.Fatal(err) - } - - // If knative service also check FDR is up - knative := os.Getenv("KNATIVESERVICE") - if knative == "true" || knative == "1" { - fmt.Println("KNATIVESERVICE set so checking FDR container") - err := checkDesignerHealth() - if err != nil { - log.Fatal(err) - } - } else { - fmt.Println("KNATIVESERVICE is not set so skipping FDR checks") - } - -} - -func checkDesignerHealth() error { - // HTTP LMAP endpoint - err := httpCheck("LMAP Port", "http://localhost:3002/admin/ready") - if err != nil { - return err - } - - isConnectorService := os.Getenv("CONNECTOR_SERVICE") - if isConnectorService == "true" || isConnectorService == "1" { - // HTTP LCP Connector service endpoint - err = httpCheck("LCP Port", "http://localhost:3001/admin/ready") - if err != nil { - return err - } - } - - // LCP api flow endpoint - lcpsocket := "/tmp/lcp.socket" - if value, ok := os.LookupEnv("LCP_IPC_PATH"); ok { - lcpsocket = value - } - err = socketCheck("LCP socket", lcpsocket) - if err != nil { - return err - } - - // LMAP endpoint - lmapsocket := "/tmp/lmap.socket" - if value, ok := os.LookupEnv("LMAP_IPC_PATH"); ok { - lmapsocket = value - } - err = socketCheck("LMAP socket", lmapsocket) - if err != nil { - return err - } - - return nil -} - -func isEnvExist(key string) bool { - if _, ok := os.LookupEnv(key); ok { - return true - } - return false -} - -func checkACElocal() error { - // Check if the integration server has started the admin REST endpoint - conn, err := netDial("tcp", "127.0.0.1:7600") - - if err != nil { - - fmt.Println("Unable to connect to IntegrationServer REST endpoint: " + err.Error() + ", ") - - fileInfo, statErr := os.Stat("/tmp/integration_server_restart.timestamp") - - if os.IsNotExist(statErr) { - fmt.Println("Integration server is not active") - return errors.NewBadRequest("Integration server is not active") - } else if statErr != nil { - fmt.Println(statErr) - return errors.NewBadRequest("stat error " + statErr.Error()) - } else { - fmt.Println("Integration server restart file found") - timeNow := time.Now() - timeDiff := timeNow.Sub(fileInfo.ModTime()) - - if timeDiff.Seconds() < restartIsTimeoutInSeconds { - fmt.Println("Integration server is restarting") - } else { - fmt.Println("Integration restart time elapsed") - return errors.NewBadRequest("Integration restart time elapsed") - } - } - } else { - fmt.Println("ACE ready check passed") - } - conn.Close() - return nil -} - -func httpChecklocal(name string, addr string) error { - resp, err := httpGet(addr) - if err != nil { - return err - } - if resp.StatusCode != 200 { - fmt.Println(name + " ready check failed - HTTP Status is not 200 range") - return errors.NewBadRequest(name + " ready check failed - HTTP Status is not 200 range") - } else { - fmt.Println(name + " ready check passed") - } - return nil -} - -func socketChecklocal(name string, socket string) error { - httpc := http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socket) - }, - }, - } - response, err := httpc.Get("http://dummyHostname/admin/ready") - if err != nil { - return err - } - if response.StatusCode != 200 { - log.Fatal(name + " ready check failed - HTTP Status is not 200 range") - return errors.NewBadRequest(name + " ready check failed - HTTP Status is not 200 range") - } else { - fmt.Println(name + " ready check passed") - } - return nil -} diff --git a/cmd/chkacehealthy/main_test.go b/cmd/chkacehealthy/main_test.go deleted file mode 100644 index 4406566..0000000 --- a/cmd/chkacehealthy/main_test.go +++ /dev/null @@ -1,134 +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. -*/ - -// chkacelively checks that ACE is still runing, by checking if the admin REST endpoint port is available. -package main - -import ( - "net/http" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/errors" -) - -func Test_httpChecklocal(t *testing.T) { - - t.Run("http get succeeds", func(t *testing.T) { - - oldhttpGet := httpGet - defer func() { httpGet = oldhttpGet }() - httpGet = func(string) (resp *http.Response, err error) { - response := &http.Response{ - StatusCode: 200, - } - return response, nil - } - - err := httpChecklocal("LMAP Port", "http://localhost:3002/admin/ready") - assert.Nil(t, err) - }) - - t.Run("http get fails with err on get", func(t *testing.T) { - - oldhttpGet := httpGet - defer func() { httpGet = oldhttpGet }() - httpGet = func(string) (resp *http.Response, err error) { - response := &http.Response{} - return response, errors.NewBadRequest("mock err") - } - - err := httpChecklocal("LMAP Port", "http://localhost:3002/admin/ready") - assert.Error(t, err, "mock err") - }) - - t.Run("http get fails with non 200", func(t *testing.T) { - - oldhttpGet := httpGet - defer func() { httpGet = oldhttpGet }() - httpGet = func(string) (resp *http.Response, err error) { - response := &http.Response{ - StatusCode: 404, - } - return response, nil - } - - err := httpChecklocal("Test", "http://localhost:3002/admin/ready") - assert.Error(t, err, "Test ready check failed - HTTP Status is not 200 range") - }) -} - -func Test_checkDesignerHealth(t *testing.T) { - t.Run("connector service enabled and health check is successful", func(t *testing.T) { - os.Setenv("CONNECTOR_SERVICE", "true") - defer os.Unsetenv("CONNECTOR_SERVICE") - oldhttpGet := httpGet - defer func() { httpGet = oldhttpGet }() - httpGet = func(string) (resp *http.Response, err error) { - response := &http.Response{ - StatusCode: 200, - } - return response, nil - } - - oldSocketCheck := socketCheck - defer func() { socketCheck = oldSocketCheck }() - socketCheck = func(string, string) (err error) { - return nil - } - err := checkDesignerHealth() - assert.Nil(t, err) - }) - - t.Run("connector service enabled and health check fails", func(t *testing.T) { - os.Setenv("CONNECTOR_SERVICE", "true") - defer os.Unsetenv("CONNECTOR_SERVICE") - oldhttpGet := httpGet - defer func() { httpGet = oldhttpGet }() - httpGet = func(string) (resp *http.Response, err error) { - response := &http.Response{} - return response, errors.NewBadRequest("mock err") - } - - oldSocketCheck := socketCheck - defer func() { socketCheck = oldSocketCheck }() - socketCheck = func(string, string) (err error) { - return nil - } - err := checkDesignerHealth() - assert.Error(t, err, "mock err") - }) - - t.Run("health check fails for socket http server", func(t *testing.T) { - oldhttpGet := httpGet - defer func() { httpGet = oldhttpGet }() - httpGet = func(string) (resp *http.Response, err error) { - response := &http.Response{ - StatusCode: 200, - } - return response, nil - } - - oldSocketCheck := socketCheck - defer func() { socketCheck = oldSocketCheck }() - socketCheck = func(string, string) (err error) { - return errors.NewBadRequest("mock err") - } - err := checkDesignerHealth() - assert.Error(t, err, "mock err") - }) -} diff --git a/cmd/chkaceready/main.go b/cmd/chkaceready/main.go deleted file mode 100644 index b3c861c..0000000 --- a/cmd/chkaceready/main.go +++ /dev/null @@ -1,35 +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. -*/ - -// chkaceready checks that ACE is ready for work, by checking if the admin REST endpoint port is available. -package main - -import ( - "fmt" - "net" - "os" -) - -func main() { - // Check if the integration server has started the admin REST endpoint - conn, err := net.Dial("tcp", "127.0.0.1:7600") - if err != nil { - fmt.Println(err) - os.Exit(1) - } - conn.Close() - -} diff --git a/cmd/runaceserver/integrationserver.go b/cmd/runaceserver/integrationserver.go deleted file mode 100644 index e2da12c..0000000 --- a/cmd/runaceserver/integrationserver.go +++ /dev/null @@ -1,1230 +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 ( - "context" - "crypto/rand" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io" - "io/ioutil" - "math/big" - "net" - "net/http" - "net/url" - "os" - "os/exec" - "os/user" - "path" - "sort" - "strings" - "time" - - "github.com/fsnotify/fsnotify" - "github.com/ot4i/ace-docker/common/contentserver" - "github.com/ot4i/ace-docker/internal/command" - "github.com/ot4i/ace-docker/internal/configuration" - "github.com/ot4i/ace-docker/internal/name" - "github.com/ot4i/ace-docker/internal/webadmin" - "gopkg.in/yaml.v2" - - "software.sslmate.com/src/go-pkcs12" -) - -var osMkdir = os.Mkdir -var osCreate = os.Create -var osStat = os.Stat -var ioutilReadFile = ioutil.ReadFile -var ioutilReadDir = ioutil.ReadDir -var ioCopy = io.Copy -var contentserverGetBAR = contentserver.GetBAR -var watcher *fsnotify.Watcher -var createSHAServerConfYaml = createSHAServerConfYamlLocal -var homedir string = "/home/aceuser" -var initialConfigDir string = "/home/aceuser/initial-config" -var ConfigureWebAdminUsers = webadmin.ConfigureWebAdminUsers -var readServerConfFile = readServerConfFileLocal -var yamlUnmarshal = yaml.Unmarshal -var yamlMarshal = yaml.Marshal -var writeServerConfFile = writeServerConfFileLocal -var getConfigurationFromContentServer = getConfigurationFromContentServerLocal -var commandRunCmd = command.RunCmd -var packageBarFile = packageBarFileLocal -var deployBarFile = deployBarFileLocal - -// initialIntegrationServerConfig walks through the /home/aceuser/initial-config directory -// looking for directories (each containing some config data), then runs a shell script -// called ace_config_{directory-name}.sh to process that data. -func initialIntegrationServerConfig() error { - log.Printf("Performing initial configuration of integration server") - - if configuration.ContentServer { - getConfError := getConfigurationFromContentServer() - if getConfError != nil { - log.Errorf("Error getting configuration from content server: %v", getConfError) - return getConfError - } - } - - fileList, err := ioutilReadDir(homedir) - if err != nil { - log.Errorf("Error checking for an initial configuration folder: %v", err) - return err - } - - configDirExists := false - for _, file := range fileList { - if file.IsDir() && file.Name() == "initial-config" { - configDirExists = true - } - } - - if !configDirExists { - log.Printf("No initial configuration of integration server to perform") - return nil - } - - fileList, err = ioutil.ReadDir(initialConfigDir) - if err != nil { - log.Errorf("Error checking for initial configuration folders: %v", err) - return err - } - - // Sort filelist to server.conf.yaml gets written before webusers are processedconfigDirExists - SortFileNameAscend(fileList) - for _, file := range fileList { - if file.IsDir() && file.Name() != "mqsc" && file.Name() != "workdir_overrides" { - log.Printf("Processing configuration in folder %v", file.Name()) - if file.Name() == "webusers" { - log.Println("Configuring server.conf.yaml overrides - Webadmin") - updateServerConf := createSHAServerConfYaml() - if updateServerConf != nil { - log.Errorf("Error setting webadmin SHA server.conf.yaml: %v", updateServerConf) - return updateServerConf - } - log.Println("Configuring WebAdmin Users") - err := ConfigureWebAdminUsers(log) - if err != nil { - log.Errorf("Error configuring the WebAdmin users : %v", err) - return err - } - } - - // do not generate script for webusers dir or designerflowyaml - // designerflowyaml is where we mount the ConfigMaps containing the IntegrationFlow resources - if file.Name() != "webusers" && file.Name() != "designerflowyaml" && file.Name() != "generated-bar" { - cmd := exec.Command("ace_config_" + file.Name() + ".sh") - out, _, err := command.RunCmd(cmd) - if err != nil { - log.LogDirect(out) - log.Errorf("Error processing configuration in folder %v: %v", file.Name(), err) - return err - } - log.LogDirect(out) - } - } - - } - - enableMetrics := os.Getenv("ACE_ENABLE_METRICS") - if enableMetrics == "true" || enableMetrics == "1" { - enableMetricsError := enableMetricsInServerConf() - if enableMetricsError != nil { - log.Errorf("Error enabling metrics in server.conf.yaml: %v", enableMetricsError) - return enableMetricsError - } - } - - enableOpenTracing := os.Getenv("ACE_ENABLE_OPEN_TRACING") - if enableOpenTracing == "true" || enableOpenTracing == "1" { - enableOpenTracingError := enableOpenTracingInServerConf() - if enableOpenTracingError != nil { - log.Errorf("Error enabling user exits in server.conf.yaml: %v", enableOpenTracingError) - return enableOpenTracingError - } - } - - enableAdminssl := os.Getenv("ACE_ADMIN_SERVER_SECURITY") - if enableAdminssl == "true" || enableAdminssl == "1" { - enableAdminsslError := enableAdminsslInServerConf() - if enableAdminsslError != nil { - log.Errorf("Error enabling admin server security in server.conf.yaml: %v", enableAdminsslError) - return enableAdminsslError - } - } - - forceFlowHttps := os.Getenv("FORCE_FLOW_HTTPS") - if forceFlowHttps == "true" || forceFlowHttps == "1" { - log.Printf("Forcing all flows to be https. FORCE_FLOW_HTTPS=%v", forceFlowHttps) - - // create the https nodes keystore and password - password := generatePassword(10) - - log.Println("Force Flows to be HTTPS running keystore creation commands") - cmd := exec.Command("ace_forceflowhttps.sh", password) - out, _, err := command.RunCmd(cmd) - if err != nil { - log.Errorf("Error creating force flow https keystore and password, retrying. Error is %v", err) - log.LogDirect(out) - return err - } - - log.Println("Force Flows to be HTTPS in server.conf.yaml") - forceFlowHttpsError := forceFlowsHttpsInServerConf() - if forceFlowHttpsError != nil { - log.Errorf("Error forcing flows to https in server.conf.yaml: %v", forceFlowHttpsError) - return forceFlowHttpsError - } - - // Start watching the ..data/tls.key file where the tls.key secret is stored and if it changes recreate the p12 keystore and restart the HTTPSConnector dynamically - // need to watch the mounted ..data/tls.key file here as the tls.key symlink timestamp never changes - log.Println("Force Flows to be HTTPS starting to watch /home/aceuser/httpsNodeCerts/..data/tls.key") - watcher = watchForceFlowsHTTPSSecret(password) - err = watcher.Add("/home/aceuser/httpsNodeCerts/..data/tls.key") - if err != nil { - log.Errorf("Error watching /home/aceuser/httpsNodeCerts/tls.key for Force Flows to be HTTPS: %v", err) - } - } else { - log.Printf("Not Forcing all flows to be https as FORCE_FLOW_HTTPS=%v", forceFlowHttps) - } - - isEnabled, checkGlobalCacheError := checkGlobalCacheConfigurations() - if checkGlobalCacheError != nil { - log.Errorf("Error checking global cache configurations in server.conf.yaml: %v", checkGlobalCacheError) - return checkGlobalCacheError - } - if isEnabled { - log.Printf("*******") - log.Printf("The embedded global cache is enabled. This configuration is not supported in a containerized environment. For more information, see https://ibm.biz/aceglobalcache.") - log.Printf("*******") - } - - log.Println("Initial configuration of integration server complete") - - return nil -} - -func SortFileNameAscend(files []os.FileInfo) { - sort.Slice(files, func(i, j int) bool { - return files[i].Name() < files[j].Name() - }) -} - -func createSHAServerConfYamlLocal() error { - - oldserverconfContent, readError := readServerConfFile() - if readError != nil { - if !os.IsNotExist(readError) { - // Error is different from file not existing (if the file does not exist we will create it ourselves) - log.Errorf("Error reading server.conf.yaml: %v", readError) - return readError - } - } - - serverconfMap := make(map[interface{}]interface{}) - unmarshallError := yamlUnmarshal([]byte(oldserverconfContent), &serverconfMap) - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return unmarshallError - } - - if serverconfMap["RestAdminListener"] == nil { - serverconfMap["RestAdminListener"] = map[string]interface{}{ - "authorizationEnabled": true, - "authorizationMode": "file", - "basicAuth": true, - } - } else { - restAdminListener := serverconfMap["RestAdminListener"].(map[interface{}]interface{}) - restAdminListener["authorizationEnabled"] = true - restAdminListener["authorizationMode"] = "file" - restAdminListener["basicAuth"] = true - - } - - serverconfYaml, marshallError := yamlMarshal(&serverconfMap) - if marshallError != nil { - log.Errorf("Error marshalling server.conf.yaml: %v", marshallError) - return marshallError - } - writeError := writeServerConfFile(serverconfYaml) - if writeError != nil { - return writeError - } - - return nil - -} - -// enableMetricsInServerConf adds Statistics fields to the server.conf.yaml in overrides -// If the file does not exist already it gets created. -func enableMetricsInServerConf() error { - - log.Println("Enabling metrics in server.conf.yaml") - - serverconfContent, readError := readServerConfFile() - if readError != nil { - if !os.IsNotExist(readError) { - // Error is different from file not existing (if the file does not exist we will create it ourselves) - log.Errorf("Error reading server.conf.yaml: %v", readError) - return readError - } - } - - serverconfYaml, manipulationError := addMetricsToServerConf(serverconfContent) - if manipulationError != nil { - return manipulationError - } - - writeError := writeServerConfFile(serverconfYaml) - if writeError != nil { - return writeError - } - - log.Println("Metrics enabled in server.conf.yaml") - - return nil -} - -// enableOpenTracingInServerConf adds OpenTracing UserExits fields to the server.conf.yaml in overrides -// If the file does not exist already it gets created. -func enableOpenTracingInServerConf() error { - - log.Println("Enabling OpenTracing in server.conf.yaml") - - serverconfContent, readError := readServerConfFile() - if readError != nil { - if !os.IsNotExist(readError) { - // Error is different from file not existing (if the file does not exist we will create it ourselves) - log.Errorf("Error reading server.conf.yaml: %v", readError) - return readError - } - } - - serverconfYaml, manipulationError := addOpenTracingToServerConf(serverconfContent) - if manipulationError != nil { - return manipulationError - } - - writeError := writeServerConfFile(serverconfYaml) - if writeError != nil { - return writeError - } - - log.Println("OpenTracing enabled in server.conf.yaml") - - return nil -} - -// readServerConfFile returns the content of the server.conf.yaml file in the overrides folder -func readServerConfFileLocal() ([]byte, error) { - content, err := ioutil.ReadFile("/home/aceuser/ace-server/overrides/server.conf.yaml") - return content, err -} - -// writeServerConfFile writes the yaml content to the server.conf.yaml file in the overrides folder -// It creates the file if it doesn't already exist -func writeServerConfFileLocal(content []byte) error { - writeError := ioutil.WriteFile("/home/aceuser/ace-server/overrides/server.conf.yaml", content, 0644) - if writeError != nil { - log.Errorf("Error writing server.conf.yaml: %v", writeError) - return writeError - } - return nil -} - -// enableAdminsslInServerConf adds RestAdminListener configuration fields to the server.conf.yaml in overrides -// based on the env vars ACE_ADMIN_SERVER_KEY, ACE_ADMIN_SERVER_CERT, ACE_ADMIN_SERVER_CA -// If the file does not exist already it gets created. -func enableAdminsslInServerConf() error { - - log.Println("Enabling Admin Server Security in server.conf.yaml") - - serverconfContent, readError := readServerConfFile() - if readError != nil { - if !os.IsNotExist(readError) { - // Error is different from file not existing (if the file does not exist we will create it ourselves) - log.Errorf("Error reading server.conf.yaml: %v", readError) - return readError - } - } - - serverconfYaml, manipulationError := addAdminsslToServerConf(serverconfContent) - if manipulationError != nil { - return manipulationError - } - - writeError := writeServerConfFile(serverconfYaml) - if writeError != nil { - return writeError - } - - log.Println("Admin Server Security enabled in server.conf.yaml") - - return nil -} - -// addMetricsToServerConf gets the content of the server.conf.yaml and adds the metrics fields to it -// It returns the updated server.conf.yaml content -func addMetricsToServerConf(serverconfContent []byte) ([]byte, error) { - serverconfMap := make(map[interface{}]interface{}) - unmarshallError := yamlUnmarshal([]byte(serverconfContent), &serverconfMap) - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return nil, unmarshallError - } - - snapshotObj := map[string]string{ - "publicationOn": "active", - "nodeDataLevel": "basic", - "outputFormat": "json", - "threadDataLevel": "none", - "accountingOrigin": "none", - } - - resourceObj := map[string]bool{ - "reportingOn": true, - } - - if serverconfMap["Statistics"] != nil { - statistics := serverconfMap["Statistics"].(map[interface{}]interface{}) - - if statistics["Snapshot"] != nil { - snapshot := statistics["Snapshot"].(map[interface{}]interface{}) - if snapshot["publicationOn"] == nil { - snapshot["publicationOn"] = "active" - } - if snapshot["nodeDataLevel"] == nil { - snapshot["nodeDataLevel"] = "basic" - } - if snapshot["outputFormat"] == nil { - snapshot["outputFormat"] = "json" - } else { - snapshot["outputFormat"] = fmt.Sprintf("%v", snapshot["outputFormat"]) + ",json" - } - if snapshot["threadDataLevel"] == nil { - snapshot["threadDataLevel"] = "none" - } - } else { - statistics["Snapshot"] = snapshotObj - } - - if statistics["Resource"] != nil { - resource := statistics["Resource"].(map[interface{}]interface{}) - if resource["reportingOn"] == nil { - resource["reportingOn"] = true - } - } else { - statistics["Resource"] = resourceObj - } - - } else { - serverconfMap["Statistics"] = map[string]interface{}{ - "Snapshot": snapshotObj, - "Resource": resourceObj, - } - } - - serverconfYaml, marshallError := yaml.Marshal(&serverconfMap) - if marshallError != nil { - log.Errorf("Error marshalling server.conf.yaml: %v", marshallError) - return nil, marshallError - } - - return serverconfYaml, nil -} - -// addOpenTracingToServerConf gets the content of the server.conf.yaml and adds the OpenTracing UserExits fields to it -// It returns the updated server.conf.yaml content -func addOpenTracingToServerConf(serverconfContent []byte) ([]byte, error) { - serverconfMap := make(map[interface{}]interface{}) - unmarshallError := yaml.Unmarshal([]byte(serverconfContent), &serverconfMap) - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return nil, unmarshallError - } - - if serverconfMap["UserExits"] != nil { - userExits := serverconfMap["UserExits"].(map[interface{}]interface{}) - - userExits["activeUserExitList"] = "ACEOpenTracingUserExit" - userExits["userExitPath"] = "/opt/ACEOpenTracing" - - } else { - serverconfMap["UserExits"] = map[interface{}]interface{}{ - "activeUserExitList": "ACEOpenTracingUserExit", - "userExitPath": "/opt/ACEOpenTracing", - } - } - - serverconfYaml, marshallError := yaml.Marshal(&serverconfMap) - if marshallError != nil { - log.Errorf("Error marshalling server.conf.yaml: %v", marshallError) - return nil, marshallError - } - - return serverconfYaml, nil -} - -// addAdminsslToServerConf gets the content of the server.conf.yaml and adds the Admin Server Security fields to it -// It returns the updated server.conf.yaml content -func addAdminsslToServerConf(serverconfContent []byte) ([]byte, error) { - serverconfMap := make(map[interface{}]interface{}) - unmarshallError := yaml.Unmarshal([]byte(serverconfContent), &serverconfMap) - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return nil, unmarshallError - } - - // Get the keys, certs location and default if not found - cert := os.Getenv("ACE_ADMIN_SERVER_CERT") - if cert == "" { - cert = "/home/aceuser/adminssl/tls.crt.pem" - } - - key := os.Getenv("ACE_ADMIN_SERVER_KEY") - if key == "" { - key = "/home/aceuser/adminssl/tls.key.pem" - } - - cacert := os.Getenv("ACE_ADMIN_SERVER_CA") - if cacert == "" { - cacert = "/home/aceuser/adminssl" - } - - isTrue := true - // Only update if there is not an existing entry in the override server.conf.yaml - // so we don't overwrite any customer provided configuration - if serverconfMap["RestAdminListener"] == nil { - serverconfMap["RestAdminListener"] = map[string]interface{}{ - "sslCertificate": cert, - "sslPassword": key, - "requireClientCert": isTrue, - "caPath": cacert, - } - log.Printf("Admin Server Security updating RestAdminListener using ACE_ADMIN_SERVER environment variables") - } else { - restAdminListener := serverconfMap["RestAdminListener"].(map[interface{}]interface{}) - - if restAdminListener["sslCertificate"] == nil { - restAdminListener["sslCertificate"] = cert - } - if restAdminListener["sslPassword"] == nil { - restAdminListener["sslPassword"] = key - } - if restAdminListener["requireClientCert"] == nil { - restAdminListener["requireClientCert"] = isTrue - } - if restAdminListener["caPath"] == nil { - restAdminListener["caPath"] = cacert - } - log.Printf("Admin Server Security merging RestAdminListener using ACE_ADMIN_SERVER environment variables") - } - - serverconfYaml, marshallError := yaml.Marshal(&serverconfMap) - if marshallError != nil { - log.Errorf("Error marshalling server.conf.yaml: %v", marshallError) - return nil, marshallError - } - - return serverconfYaml, nil -} - -// getConfigurationFromContentServer checks if ACE_CONTENT_SERVER_URL exists. If so then it pulls -// a bar file from that URL -func getConfigurationFromContentServerLocal() error { - - // ACE_CONTENT_SERVER_URL can contain 1 or more comma separated urls - urls := os.Getenv("ACE_CONTENT_SERVER_URL") - if urls == "" { - log.Printf("No content server url available") - return nil - } - - defaultContentServer := os.Getenv("DEFAULT_CONTENT_SERVER") - if defaultContentServer == "" { - log.Printf("Can't tell if content server is default one so defaulting") - defaultContentServer = "true" - } - - err := osMkdir("/home/aceuser/initial-config/bars", os.ModePerm) - if err != nil { - log.Errorf("Error creating directory /home/aceuser/initial-config/bars: %v", err) - return err - } - - // check for AUTH env parameters (needed if auth is not encoded in urls for backward compatibility pending operator changes) - envServerName := os.Getenv("ACE_CONTENT_SERVER_NAME") - envToken := os.Getenv("ACE_CONTENT_SERVER_TOKEN") - - urlArray := strings.Split(urls, ",") - for _, barurl := range urlArray { - - serverName := envServerName - token := envToken - - // the ace content server name is the name of the secret where this cert is - // eg. secretName: {{ index (splitList ":" (index (splitList "/" (trim .Values.contentServerURL)) 2)) 0 | quote }} ? - // https://domsdash-ibm-ace-dashboard-prod:3443/v1/directories/CustomerDatabaseV1?userid=fsdjfhksdjfhsd - // or https://test-acecontentserver-ace-dom.svc:3443/v1/directories/testdir?e31d23f6-e3ba-467d-ab3b-ceb0ab12eead - // Mutli-tenant : https://test-acecontentserver-ace-dom.svc:3443/v1/namespace/directories/testdir - // https://dataplane-api-dash.appconnect:3443/v1/appc-fakeid/directories/ace_manualtest_callableflow - - splitOnSlash := strings.Split(barurl, "/") - - if len(splitOnSlash) > 2 { - serverName = strings.Split(splitOnSlash[2], ":")[0] // test-acecontentserver.ace-dom - } else { - // if we have not found serverName from either env or url error - log.Printf("No content server name available but a url is defined - Have you forgotten to define your BAR_AUTH configuration resource?") - return errors.New("No content server name available but a url is defined - Have you forgotten to define your BAR_AUTH configuration resource?") - } - - // if ACE_CONTENT_SERVER_TOKEN was set use it. It may have been read from a secret - // otherwise then look in the url for ? - if token == "" { - splitOnQuestion := strings.Split(barurl, "?") - if len(splitOnQuestion) > 1 && splitOnQuestion[1] != "" { - barurl = splitOnQuestion[0] // https://test-acecontentserver.ace-dom.svc:3443/v1/directories/testdir - token = splitOnQuestion[1] //userid=fsdjfhksdjfhsd - } else if defaultContentServer == "true" { - // if we have not found token from either env or url error - log.Errorf("No content server token available but a url is defined") - return errors.New("No content server token available but a url is defined") - } - } - - // use the last part of the url path (base) for the filename - u, err := url.Parse(barurl) - if err != nil { - log.Errorf("Error parsing content server url : %v", err) - return err - } - - var filename string - if len(urlArray) == 1 { - // temporarily override the bar name with "barfile.bar" if we only have ONE bar file until mq connector is fixed to support any bar name - filename = "/home/aceuser/initial-config/bars/barfile.bar" - } else { - // Multiple bar support. Need to loop to check that the file does not already exist - // (case where multiple bars have the same name) - isAvailable := false - count := 0 - for !isAvailable { - if count == 0 { - filename = "/home/aceuser/initial-config/bars/" + path.Base(u.Path) + ".bar" - } else { - filename = "/home/aceuser/initial-config/bars/" + path.Base(u.Path) + "-" + fmt.Sprint(count) + ".bar" - log.Printf("Previous path already in use. Testing filename: " + filename) - } - - if _, err := osStat(filename); os.IsNotExist(err) { - log.Printf("No existing file on that path so continuing") - isAvailable = true - } - count++ - } - } - - log.Printf("Will save bar as: " + filename) - - file, err := osCreate(filename) - if err != nil { - log.Errorf("Error creating file %v: %v", file, err) - return err - } - defer file.Close() - - // Create a CA certificate pool and add cacert to it - var contentServerCACert string - if defaultContentServer == "true" { - log.Printf("Getting configuration from content server") - contentServerCACert = "/home/aceuser/ssl/cacert.pem" - barurl = barurl + "?archive=true" - } else { - log.Printf("Getting configuration from custom content server") - contentServerCACert = os.Getenv("CONTENT_SERVER_CA") - if token != "" { - barurl = barurl + "?" + token - } - if contentServerCACert == "" { - log.Printf("CONTENT_SERVER_CA not defined") - return errors.New("CONTENT_SERVER_CA not defined") - } - } - log.Printf("Using the following url: " + barurl) - - log.Printf("Using ca file %s", contentServerCACert) - caCert, err := ioutilReadFile(contentServerCACert) - if err != nil { - log.Errorf("Error reading CA Certificate") - return errors.New("Error reading CA Certificate") - } - - contentServerCert := os.Getenv("CONTENT_SERVER_CERT") - contentServerKey := os.Getenv("CONTENT_SERVER_KEY") - - bar, err := contentserverGetBAR(barurl, serverName, token, caCert, contentServerCert, contentServerKey, log) - if err != nil { - return err - } - defer bar.Close() - - _, err = ioCopy(file, bar) - if err != nil { - log.Errorf("Error writing file %v: %v", file, err) - return err - } - - log.Printf("Configuration pulled from content server successfully") - - } - return nil -} - -// startIntegrationServer launches the IntegrationServer process in the background as the user "aceuser". -// This returns a BackgroundCmd, wrapping the backgrounded process, or an error if we completely failed to -// start the process -func startIntegrationServer() command.BackgroundCmd { - logOutputFormat := getLogOutputFormat() - - serverName, err := name.GetIntegrationServerName() - if err != nil { - log.Printf("Error getting integration server name: %v", err) - returnErr := command.BackgroundCmd{} - returnErr.ReturnCode = -1 - returnErr.ReturnError = err - return returnErr - } - - defaultAppName := os.Getenv("ACE_DEFAULT_APPLICATION_NAME") - if defaultAppName == "" { - log.Printf("No default application name supplied. Using the integration server name instead.") - defaultAppName = serverName - } - - thisUser, err := user.Current() - if err != nil { - log.Errorf("Error finding this user: %v", err) - returnErr := command.BackgroundCmd{} - returnErr.ReturnCode = -1 - returnErr.ReturnError = err - return returnErr - } - - return command.RunAsUserBackground(thisUser.Username, "ace_integration_server.sh", log, "-w", "/home/aceuser/ace-server", "--name", serverName, "--log-output-format", logOutputFormat, "--console-log", "--default-application-name", defaultAppName) -} - -func waitForIntegrationServer() error { - for { - cmd := exec.Command("chkaceready") - _, rc, err := command.RunCmd(cmd) - if rc != 0 || err != nil { - knative := os.Getenv("KNATIVESERVICE") - if knative == "true" || knative == "1" { - log.Printf("Integration server & FDR not ready yet") - } else { - log.Printf("Integration server not ready yet") - } - } - if rc == 0 { - break - } - time.Sleep(5 * time.Second) - } - return nil -} - -func stopIntegrationServer(integrationServerProcess command.BackgroundCmd) { - if integrationServerProcess.Cmd != nil && integrationServerProcess.Started && !integrationServerProcess.Finished { - command.SigIntBackground(integrationServerProcess) - command.WaitOnBackground(integrationServerProcess) - } -} - -func createWorkDir() error { - if _, err := os.Stat("/home/aceuser/ace-server/server.conf.yaml"); errors.Is(err, os.ErrNotExist) { - log.Printf("Attempting to initialise /home/aceuser/ace-server") - - // Run mqsicreateworkdir code - cmd := exec.Command("/opt/ibm/ace-12/server/bin/mqsicreateworkdir", "/home/aceuser/ace-server") - _, _, err := command.RunCmd(cmd) - if err != nil { - log.Printf("Error initializing work dir") - return err - } - log.Printf("Work dir initialization complete") - } else { - log.Printf("/home/aceuser/ace-server/server.conf.yaml found, not initializing Work dir") - } - return nil -} - -func createWorkDirSymLink() error { - log.Printf("Attempting to move / symlink /home/aceuser/ace-server to shared mount") - cmd := exec.Command("cp", "-r", "/home/aceuser/ace-server", "/workdir-shared/") - _, _, err := command.RunCmd(cmd) - if err != nil { - log.Printf("Error copying workdir to shared work dir") - return err - } - cmd = exec.Command("rm", "-rf", "/home/aceuser/ace-server") - _, _, err = command.RunCmd(cmd) - if err != nil { - log.Printf("Error deleting original work dir") - return err - } - cmd = exec.Command("ln", "-s", "/workdir-shared/ace-server", "/home/aceuser/") - _, _, err = command.RunCmd(cmd) - if err != nil { - log.Printf("Error creating symlink") - return err - } - - log.Printf("Work dir symlink complete") - return nil -} - -func checkLogs() error { - log.Printf("Contents of log directory") - system("ls", "-l", "/home/aceuser/ace-server/config/common/log") - - if os.Getenv("MQSI_PREVENT_CONTAINER_SHUTDOWN") == "true" { - log.Printf("MQSI_PREVENT_CONTAINER_SHUTDOWN set to blocking container shutdown to enable log copy out") - log.Printf("Once all logs have been copied out please kill container") - select {} - } - - log.Printf("If you want to stop the container shutting down to enable retrieval of these files please set the environment variable \"MQSI_PREVENT_CONTAINER_SHUTDOWN=true\"") - log.Printf("If you are running under kubernetes you will also need to disable the livenessProbe") - log.Printf("Log checking complete") - return nil -} - -func system(cmd string, arg ...string) { - out, err := exec.Command(cmd, arg...).Output() - if err != nil { - log.Printf(err.Error()) - } - log.Printf(string(out)) -} - -// applyWorkdirOverrides walks through the home/aceuser/initial-config/workdir_overrides directory -// we want to do this here rather than the loop above as we want to make sure we have done everything -// else before applying the workdir overrides and then start the integration server -func applyWorkdirOverrides() error { - - fileList, err := ioutil.ReadDir("/home/aceuser") - if err != nil { - log.Errorf("Error checking for the aceuser home directoy: %v", err) - return err - } - - configDirExists := false - for _, file := range fileList { - if file.IsDir() && file.Name() == "initial-config" { - configDirExists = true - } - } - - if !configDirExists { - log.Printf("No initial-config directory found") - return nil - } - - fileList, err = ioutil.ReadDir("/home/aceuser/initial-config") - if err != nil { - log.Errorf("Error checking for initial configuration folders: %v", err) - return err - } - - for _, file := range fileList { - if file.IsDir() && file.Name() == "workdir_overrides" { - log.Println("Applying workdir overrides to the integration server") - cmd := exec.Command("ace_config_workdir_overrides.sh") - out, _, err := command.RunCmd(cmd) - log.LogDirect(out) - if err != nil { - log.Errorf("Error processing workdir overrides in folder %v: %v", file.Name(), err) - return err - } - log.Printf("Workdir overrides applied to the integration server complete") - } - } - - return nil -} - -// forceFlowHttps adds ResourceManagers HTTPSConnector fields to the server.conf.yaml in overrides using the keystore and password created -// If the file does not exist already it gets created. -func forceFlowsHttpsInServerConf() error { - serverconfContent, readError := readServerConfFile() - if readError != nil { - if !os.IsNotExist(readError) { - // Error is different from file not existing (if the file does not exist we will create it ourselves) - log.Errorf("Error reading server.conf.yaml: %v", readError) - return readError - } - } - - serverconfYaml, manipulationError := addforceFlowsHttpsToServerConf(serverconfContent) - if manipulationError != nil { - return manipulationError - } - - writeError := writeServerConfFile(serverconfYaml) - if writeError != nil { - return writeError - } - log.Println("Force Flows to be HTTPS in server.conf.yaml completed") - - return nil -} - -// addforceFlowsHttpsToServerConf gets the content of the server.conf.yaml and adds the Force Flow Security fields to it -// It returns the updated server.conf.yaml content -func addforceFlowsHttpsToServerConf(serverconfContent []byte) ([]byte, error) { - serverconfMap := make(map[interface{}]interface{}) - unmarshallError := yaml.Unmarshal([]byte(serverconfContent), &serverconfMap) - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return nil, unmarshallError - } - - isTrue := true - keystoreFile := "/home/aceuser/ace-server/https-keystore.p12" - keystorePassword := "brokerHTTPSKeystore::password" - keystoreType := "PKCS12" - - ResourceManagersMap := make(map[interface{}]interface{}) - ResourceManagersMap["HTTPSConnector"] = map[string]interface{}{ - "KeystoreFile": keystoreFile, - "KeystorePassword": keystorePassword, - "KeystoreType": keystoreType, - } - // Only update if there is not an existing entry in the override server.conf.yaml - // so we don't overwrite any customer provided configuration - if serverconfMap["forceServerHTTPS"] == nil { - serverconfMap["forceServerHTTPS"] = isTrue - log.Printf("Force Flows HTTPS Security setting forceServerHTTPS to true") - } - - if serverconfMap["ResourceManagers"] == nil { - serverconfMap["ResourceManagers"] = ResourceManagersMap - log.Printf("Force Flows HTTPS Security creating ResourceManagers->HTTPSConnector") - } else { - resourceManagers := serverconfMap["ResourceManagers"].(map[interface{}]interface{}) - if resourceManagers["HTTPSConnector"] == nil { - resourceManagers["HTTPSConnector"] = ResourceManagersMap["HTTPSConnector"] - log.Printf("Force Flows HTTPS Security updating ResourceManagers creating HTTPSConnector") - } else { - httpsConnector := resourceManagers["HTTPSConnector"].(map[interface{}]interface{}) - log.Printf("Force Flows HTTPS Security merging ResourceManagers->HTTPSConnector") - - if httpsConnector["KeystoreFile"] == nil { - httpsConnector["KeystoreFile"] = keystoreFile - } else { - log.Printf("Force Flows HTTPS Security leaving ResourceManagers->HTTPSConnector->KeystoreFile unchanged") - } - if httpsConnector["KeystorePassword"] == nil { - httpsConnector["KeystorePassword"] = keystorePassword - } else { - log.Printf("Force Flows HTTPS Security leaving ResourceManagers->HTTPSConnector->KeystorePassword unchanged") - } - if httpsConnector["KeystoreType"] == nil { - httpsConnector["KeystoreType"] = keystoreType - } else { - log.Printf("Force Flows HTTPS Security leaving ResourceManagers->HTTPSConnector->KeystoreType unchanged") - } - } - } - - serverconfYaml, marshallError := yaml.Marshal(&serverconfMap) - if marshallError != nil { - log.Errorf("Error marshalling server.conf.yaml: %v", marshallError) - return nil, marshallError - } - - return serverconfYaml, nil -} - -func checkGlobalCacheConfigurations() (bool, error) { - isEmbeddedCacheEnabled := false - serverconfContent, readError := readServerConfFile() - if readError != nil { - if !os.IsNotExist(readError) { - // Error is different from file not existing (if the file does not exist we will create it ourselves) - log.Errorf("Error reading server.conf.yaml: %v", readError) - return isEmbeddedCacheEnabled, readError - } - } - - serverconfMap := make(map[interface{}]interface{}) - unmarshallError := yaml.Unmarshal([]byte(serverconfContent), &serverconfMap) - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return isEmbeddedCacheEnabled, unmarshallError - } - - if serverconfMap["ResourceManagers"] != nil { - resourceManagers := serverconfMap["ResourceManagers"].(map[interface{}]interface{}) - if resourceManagers["GlobalCache"] != nil { - globalCache := resourceManagers["GlobalCache"].(map[interface{}]interface{}) - if globalCache["cacheOn"] == true && globalCache["enableCatalogService"] == true && globalCache["enableContainerService"] == true { - isEmbeddedCacheEnabled = true - } - } - } - - return isEmbeddedCacheEnabled, nil -} - -func generatePassword(length int64) string { - var i, e = big.NewInt(length), big.NewInt(10) - bigInt, _ := rand.Int(rand.Reader, i.Exp(e, i, nil)) - return bigInt.String() -} - -func watchForceFlowsHTTPSSecret(password string) *fsnotify.Watcher { - - //set up watch on the /home/aceuser/httpsNodeCerts/tls.key file - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Errorf("Error creating new watcher for Force Flows to be HTTPS: %v", err) - } - - go func() { - for { - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - - // https://github.com/istio/istio/issues/7877 Remove is triggered for the ..data directory when the secret is updated so check for remove too - if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Remove == fsnotify.Remove { - log.Println("modified file regenerating /home/aceuser/ace-server/https-keystore.p12 and restarting HTTPSConnector:", event.Name) - time.Sleep(1 * time.Second) - - // 1. generate new p12 /home/aceuser/ace-server/https-keystore.p12 and then - generateHTTPSKeystore("/home/aceuser/httpsNodeCerts/tls.key", "/home/aceuser/httpsNodeCerts/tls.crt", "/home/aceuser/ace-server/https-keystore.p12", password) - - // 2. patch the HTTPSConnector to pick this up - patchHTTPSConnector("/home/aceuser/ace-server/config/IntegrationServer.uds") - - // 3. Need to start watching the newly created/ mounted tls.key - err = watcher.Add("/home/aceuser/httpsNodeCerts/..data/tls.key") - if err != nil { - log.Errorf("Error watching /home/aceuser/httpsNodeCerts/tls.key for Force Flows to be HTTPS: %v", err) - } - } - - case err, ok := <-watcher.Errors: - log.Println("error from Force Flows to be HTTPS watcher:", err) - if !ok { - return - } - } - } - }() - - return watcher -} - -var generateHTTPSKeystore = localGenerateHTTPSKeystore - -func localGenerateHTTPSKeystore(privateKeyLocation string, certificateLocation string, keystoreLocation string, password string) { - // create /home/aceuser/ace-server/https-keystore.p12 using: - // single /home/aceuser/httpsNodeCerts/tls.key - // single /home/aceuser/httpsNodeCerts/tls.crt - - //Script version: openssl pkcs12 -export -in ${certfile} -inkey ${keyfile} -out /home/aceuser/ace-server/https-keystore.p12 -name ${alias} -password pass:${1} 2>&1) - - // Load the private key file into a rsa.PrivateKey - privateKeyFile, err := ioutil.ReadFile(privateKeyLocation) - if err != nil { - log.Error("Error loading "+privateKeyLocation, err) - } - privateKeyPem, _ := pem.Decode(privateKeyFile) - if privateKeyPem.Type != "RSA PRIVATE KEY" { - log.Error(privateKeyLocation + " is not of type RSA private key") - } - privateKeyPemBytes := privateKeyPem.Bytes - parsedPrivateKey, err := x509.ParsePKCS1PrivateKey(privateKeyPemBytes) - if err != nil { - log.Error("Error parsing "+privateKeyLocation+" RSA PRIVATE KEY", err) - } - - // Load the single cert file into a x509.Certificate - certificateFile, err := ioutil.ReadFile(certificateLocation) - if err != nil { - log.Error("Error loading "+certificateLocation, err) - } - certificatePem, _ := pem.Decode(certificateFile) - if certificatePem.Type != "CERTIFICATE" { - log.Error(certificateLocation+" is not CERTIFICATE type ", certificatePem.Type) - } - certificatePemBytes := certificatePem.Bytes - parsedCertificate, err := x509.ParseCertificate(certificatePemBytes) - if err != nil { - log.Error("Error parsing "+certificateLocation+" CERTIFICATE", err) - } - - // Create Keystore - pfxBytes, err := pkcs12.Encode(rand.Reader, parsedPrivateKey, parsedCertificate, []*x509.Certificate{}, password) - if err != nil { - log.Error("Error creating the "+keystoreLocation, err) - } - - // Write out the Keystore 600 (rw- --- ---) - err = ioutil.WriteFile(keystoreLocation, pfxBytes, 0600) - if err != nil { - log.Error(err) - } -} - -var patchHTTPSConnector = localPatchHTTPSConnector - -func localPatchHTTPSConnector(uds string) { - // curl -GET --unix-socket /home/aceuser/ace-server/config/IntegrationServer.uds http://localhost/apiv2/resource-managers/https-connector - // curl -d "" -POST --unix-socket /home/aceuser/ace-server/config/IntegrationServer.uds http://localhost/apiv2/resource-managers/https-connector/refresh-tls-config -i - // HTTP/1.1 200 OK - // Content-Length: 0 - // Content-Type: application/json - - // use unix domain socket - httpc := http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", uds) - }, - }, - } - - var err error - // can be any path root but use localhost to match curl - _, err = httpc.Post("http://localhost/apiv2/resource-managers/https-connector/refresh-tls-config", "application/octet-stream", strings.NewReader("")) - if err != nil { - log.Println("error during call to restart HTTPSConnector for Force Flows HTTPS", err) - } else { - log.Println("Call made to restart HTTPSConnector for Force Flows HTTPS") - } - -} - -func deployIntegrationFlowResources() error { - log.Println("Deploying IntegrationFlow resources") - err := packageBarFile() - if err != nil { - log.Println(err) - return err - } - - err = deployBarFile() - if err != nil { - log.Println(err) - return err - } - - return nil -} - -func packageBarFileLocal() error { - cmd := exec.Command("/opt/ibm/ace-12/common/node/bin/node", "/opt/ibm/acecc-bar-gen/acecc-bar-gen.js", "2>&1") - out, _, err := commandRunCmd(cmd) - if err != nil { - log.Println("Error packaging BAR file :", err) - return err - } - log.Print(out) - - return nil -} - -func deployBarFileLocal() error { - mqsiCommand := exec.Command("/bin/sh", "-c", "source /opt/ibm/ace-12/server/bin/mqsiprofile; /opt/ibm/ace-12/server/bin/ibmint deploy --input-bar-file /home/aceuser/initial-config/generated-bar/integrationFlowResources.bar --output-work-directory /home/aceuser/ace-server") - out, _, err := commandRunCmd(mqsiCommand) - if err != nil { - fmt.Println("Error deploying BAR file :", err) - return err - } - log.Print(out) - return nil -} - -func deployCSAPIFlows() error { - csAPIProxy := os.Getenv("CONNECTOR_SERVICE") - if csAPIProxy == "true" || csAPIProxy == "1" { - log.Println("Deploying Connector Service API Proxy Flow") - cpCommand := exec.Command("cp", "--recursive", "/home/aceuser/deps/CSAPI", "/home/aceuser/ace-server/run/CSAPI") - out, _, err := commandRunCmd(cpCommand) - if err != nil { - log.Println("Error deploying copy CS API flow files :", err) - return err - } - log.Print(out) - log.Println("Connector Service API flow deployed") - } - return nil -} - -// This function updates the server.conf.yaml with the fields required to -// force basic auth on all flows -func forceflowbasicauthServerConfUpdate() error { - - log.Println("Enabling basic auth on all flows in server.conf.yaml") - - serverconfContent, readError := readServerConfFile() - if readError != nil { - if !os.IsNotExist(readError) { - // Error is different from file not existing (if the file does not exist we will create it ourselves) - log.Errorf("Error reading server.conf.yaml: %v", readError) - return readError - } - } - - serverconfMap := make(map[interface{}]interface{}) - unmarshallError := yamlUnmarshal([]byte(serverconfContent), &serverconfMap) - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return unmarshallError - } - - if serverconfMap["forceServerHTTPSecurityProfile"] == nil { - serverconfMap["forceServerHTTPSecurityProfile"] = "{DefaultPolicies}:SecProfLocal" - } else { - log.Println("WARNING: You have asked to force basic auth on all flows but already have forceServerHTTPSecurityProfile set") - log.Println("WARNING: We will not override this existing value which may prevent the basic working. ") - } - - serverconfYaml, marshallError := yamlMarshal(&serverconfMap) - if marshallError != nil { - log.Errorf("Error marshalling server.conf.yaml: %v", marshallError) - return marshallError - } - - writeError := writeServerConfFile(serverconfYaml) - if writeError != nil { - return writeError - } - - log.Println("forceServerHTTPSecurityProfile enabled in server.conf.yaml") - - return nil -} \ No newline at end of file diff --git a/cmd/runaceserver/integrationserver_internal_test.go b/cmd/runaceserver/integrationserver_internal_test.go deleted file mode 100644 index 1d02e01..0000000 --- a/cmd/runaceserver/integrationserver_internal_test.go +++ /dev/null @@ -1,1240 +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 ( - "errors" - "io" - "io/ioutil" - "net" - "net/http" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/ot4i/ace-docker/common/logger" - "github.com/stretchr/testify/assert" -) - -var yamlTests = []struct { - in string - out string -}{ - { // User's yaml has Statistics - it should keep accountingOrigin in Statics and any other main sections - `Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS' -Statistics: - Snapshot: - accountingOrigin: 'test' - Archive: - test1: 'blah' - test2: 'blah2'`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -Statistics: - Archive: - test1: blah - test2: blah2 - Resource: - reportingOn: true - Snapshot: - accountingOrigin: test - nodeDataLevel: basic - outputFormat: json - publicationOn: active - threadDataLevel: none -`}, - { // User's yaml does not have a Statistics section, it adds the default metrics info - ` -Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS'`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -Statistics: - Resource: - reportingOn: true - Snapshot: - accountingOrigin: none - nodeDataLevel: basic - outputFormat: json - publicationOn: active - threadDataLevel: none -`}, - { // User's yaml has accountingOrigin in Statistics.Snapshot. It keeps this value. - ` -Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS' -Statistics: - Snapshot: - accountingOrigin: 'test'`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -Statistics: - Resource: - reportingOn: true - Snapshot: - accountingOrigin: test - nodeDataLevel: basic - outputFormat: json - publicationOn: active - threadDataLevel: none -`}, -{ // User's yaml has accountingOrigin in Statistics.Snapshot. It keeps this value. -` -Statistics: - Snapshot: - accountingOrigin: mockValue1 - nodeDataLevel: mockValue2 - outputFormat: csv - publicationOn: mockValue3 - threadDataLevel: mockValue4`, -`Statistics: - Resource: - reportingOn: true - Snapshot: - accountingOrigin: mockValue1 - nodeDataLevel: mockValue2 - outputFormat: csv,json - publicationOn: mockValue3 - threadDataLevel: mockValue4 -`}, -{ // User's yaml has accountingOrigin in Statistics.Snapshot and overriden outputFormat. It keeps these values. -` -Statistics: - Resource: - outputFormat: file`, -`Statistics: - Resource: - outputFormat: file - reportingOn: true - Snapshot: - accountingOrigin: none - nodeDataLevel: basic - outputFormat: json - publicationOn: active - threadDataLevel: none -`}, -{ // User's yaml has bare minimum. Our defaults added. -` -Statistics:`, -`Statistics: - Resource: - reportingOn: true - Snapshot: - accountingOrigin: none - nodeDataLevel: basic - outputFormat: json - publicationOn: active - threadDataLevel: none -`}, -} - -func TestAddMetricsToServerConf(t *testing.T) { - for _, table := range yamlTests { - out, err := addMetricsToServerConf([]byte(table.in)) - if err != nil { - t.Error(err) - } - stringOut := string(out) - if stringOut != table.out { - t.Errorf("addMetricsToServerConf expected %v, got %v", table.out, stringOut) - } - } -} - -func TestCheckLogs(t *testing.T) { - err := checkLogs() - if err != nil { - t.Error(err) - } -} - - -var yamlAdminTests = []struct { - in string - out string -}{ - { // User's yaml does not have a ResourceAdminListener section, so it is added -`Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS'`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -RestAdminListener: - caPath: /home/aceuser/adminssl - requireClientCert: true - sslCertificate: /home/aceuser/adminssl/tls.crt.pem - sslPassword: /home/aceuser/adminssl/tls.key.pem -`}, - { // User's yaml has RestAdminListener in don't alter. -`Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS' -RestAdminListener: - caPath: "test" - requireClientCert: false - sslCertificate: "test" - sslPassword: "test"`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -RestAdminListener: - caPath: test - requireClientCert: false - sslCertificate: test - sslPassword: test -`}, - { // User's yaml has a ResourceAdminListener section, so ours is merged with users taking precedence -`Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS' -RestAdminListener: - authorizationEnabled: true - requireClientCert: false - authorizationMode: file - sslPassword: "test" -`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -RestAdminListener: - authorizationEnabled: true - authorizationMode: file - caPath: /home/aceuser/adminssl - requireClientCert: false - sslCertificate: /home/aceuser/adminssl/tls.crt.pem - sslPassword: test -`}, -} - - -func TestAddAdminsslToServerConf(t *testing.T) { - for _, table := range yamlAdminTests { - out, err := addAdminsslToServerConf([]byte(table.in)) - if err != nil { - t.Error(err) - } - stringOut := string(out) - if stringOut != table.out { - t.Errorf("addAdminsslToServerConf expected \n%v, got \n%v", table.out, stringOut) - } - } -} - -var yamlForceFlowsHttpsTests = []struct { - in string - out string -}{ - { // User's yaml does not have a ResourceManagers section, so it is added -`Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS'`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -ResourceManagers: - HTTPSConnector: - KeystoreFile: /home/aceuser/ace-server/https-keystore.p12 - KeystorePassword: brokerHTTPSKeystore::password - KeystoreType: PKCS12 -forceServerHTTPS: true -`}, - { // User's yaml has ResourceManagers in don't alter. -`Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS' -ResourceManagers: - HTTPSConnector: - ListenerPort: 7843 - KeystoreFile: '/home/aceuser/keystores/des-01-quickstart-ma-designer-ks' - KeystorePassword: 'changeit' - KeystoreType: 'PKCS12' - CORSEnabled: true`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -ResourceManagers: - HTTPSConnector: - CORSEnabled: true - KeystoreFile: /home/aceuser/keystores/des-01-quickstart-ma-designer-ks - KeystorePassword: changeit - KeystoreType: PKCS12 - ListenerPort: 7843 -forceServerHTTPS: true -`}, - { // User's yaml has a ResourceManagers HTTPSConnector section, so ours is merged with users taking precedence -`Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS' -ResourceManagers: - HTTPSConnector: - ListenerPort: 7843 - KeystorePassword: 'changeit' - CORSEnabled: true -`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -ResourceManagers: - HTTPSConnector: - CORSEnabled: true - KeystoreFile: /home/aceuser/ace-server/https-keystore.p12 - KeystorePassword: changeit - KeystoreType: PKCS12 - ListenerPort: 7843 -forceServerHTTPS: true -`}, -{ // User's yaml has a ResourceManagers but no HTTPSConnector section, so ours is merged with users taking precedence -`Defaults: - defaultApplication: '' - policyProject: 'DefaultPolicies' - Policies: - HTTPSConnector: 'HTTPS' -ResourceManagers: - SOMETHINGELSE: - ListenerPort: 9999 - KeystorePassword: 'otherbit' - CORSEnabled: false -`, -`Defaults: - Policies: - HTTPSConnector: HTTPS - defaultApplication: "" - policyProject: DefaultPolicies -ResourceManagers: - HTTPSConnector: - KeystoreFile: /home/aceuser/ace-server/https-keystore.p12 - KeystorePassword: brokerHTTPSKeystore::password - KeystoreType: PKCS12 - SOMETHINGELSE: - CORSEnabled: false - KeystorePassword: otherbit - ListenerPort: 9999 -forceServerHTTPS: true -`}, -} - -func TestAddforceFlowsHttpsToServerConf(t *testing.T) { - for _, table := range yamlForceFlowsHttpsTests { - out, err := addforceFlowsHttpsToServerConf([]byte(table.in)) - if err != nil { - t.Error(err) - } - stringOut := string(out) - if stringOut != table.out { - t.Errorf("addforceFlowsHttpsToServerConf expected \n%v, got \n%v", table.out, stringOut) - } - } -} - -func TestCheckGlobalCacheConfigurations(t *testing.T) { - t.Run("When global cache is not configured, no warning message is issued.", func(t *testing.T) { - serverConfigYaml := `ResourceManagers:` - - readServerConfFile = func() ([]byte, error) { - return []byte(serverConfigYaml), nil - } - - isEmbeddedCacheEnabled, err := checkGlobalCacheConfigurations() - assert.NoError(t, err) - assert.Equal(t, isEmbeddedCacheEnabled, false, "The WXS server should be disabled.") - }) - - t.Run("When global cache is disabled, no warning message is issued.", func(t *testing.T) { - serverConfigYaml := -`ResourceManagers: - GlobalCache: - cacheOn: false - enableCatalogService: false - enableContainerService: false` - - readServerConfFile = func() ([]byte, error) { - return []byte(serverConfigYaml), nil - } - - isEmbeddedCacheEnabled, err := checkGlobalCacheConfigurations() - assert.NoError(t, err) - assert.Equal(t, isEmbeddedCacheEnabled, false, "The WXS server should be disabled.") - }) - - t.Run("When global cache is enabled without the WXS server, no warning message is issued.", func(t *testing.T) { - serverConfigYaml := -`ResourceManagers: - GlobalCache: - cacheOn: true - enableCatalogService: false - enableContainerService: false` - - readServerConfFile = func() ([]byte, error) { - return []byte(serverConfigYaml), nil - } - - isEmbeddedCacheEnabled, err := checkGlobalCacheConfigurations() - assert.NoError(t, err) - assert.Equal(t, isEmbeddedCacheEnabled, false, "The WXS server should be disabled.") - }) - - t.Run("When both global cache and the WXS server are enabled, a warning message is issued.", func(t *testing.T) { - serverConfigYaml := -`ResourceManagers: - GlobalCache: - cacheOn: true - enableCatalogService: true - enableContainerService: true` - - readServerConfFile = func() ([]byte, error) { - return []byte(serverConfigYaml), nil - } - - isEmbeddedCacheEnabled, err := checkGlobalCacheConfigurations() - assert.NoError(t, err) - assert.Equal(t, isEmbeddedCacheEnabled, true, "The WXS server should be enabled.") - }) -} - -func TestGetConfigurationFromContentServer(t *testing.T) { - - barDirPath := "/home/aceuser/initial-config/bars" - var osMkdirRestore = osMkdir - var osCreateRestore = osCreate - var osStatRestore = osStat - var ioutilReadFileRestore = ioutilReadFile - var ioCopyRestore = ioCopy - var contentserverGetBARRestore = contentserverGetBAR - - var dummyCert = `-----BEGIN CERTIFICATE----- - MIIC1TCCAb2gAwIBAgIJANoE+RGRB8c6MA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV - BAMTD3d3dy5leGFtcGxlLmNvbTAeFw0yMTA1MjUwMjE3NDlaFw0zMTA1MjMwMjE3 - NDlaMBoxGDAWBgNVBAMTD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB - BQADggEPADCCAQoCggEBAOOh3VRmp/NfWbzXONxeLK/JeNvtC+TnWCz6HgtRzlhe - 7qe55dbm51Z6+l9y3C4KYH/K+a8Wgb9pKfeGCtfhRybVW3lYFtfudW7LrvgTyRIr - r/D9UPK9J+4p/ucClGERixSY8a2F4L3Bt3o1eKDeRnz5rUlmO2mJOw41p8sSgWtp - 9MOaw5OdUrqgXh3qWkctJ8gWS2eMddh0T5ZTYE2VAOW8mTtSwAFYeBSzB+/mcl8Y - BE7pOd71a3ka2xxLwm9KfSGLQTw0K7PxeZvdEcAq+Ffb+f/eOw0TwkNPGjnmQxLa - MSEDDOw0AzYPibRAZIBfhLeBOHifxTd0XbCYOUAD5zkCAwEAAaMeMBwwGgYDVR0R - BBMwEYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQAuTsah7W7H - HRvsuzEnPNXKKuy/aTvI3nnr6P9n5QCsdFirPyAS3/H7iNbvHVfSTfFa+b2qDaGU - tbLJkT84m/R6gIIzRMbA0WUwQ7GRJE3KwKIytSZcTY0AuQnXy7450COmka9ztBuI - rPYRV01LzLPJxO9A07tThSFMzhUiKrkeB5pBIjzgcYgQZNCfNtqpITmXKbO84oWA - rbxwlF1RCvmAvzIqQx21IX16i/vH/cQ3VvCIQJt1X47KCKmWaft9AkCdjyWrFh5M - ZhCApdQ3e/+TGkBlX32kHaRmn4Ascib7aQI2ugvowqLYFg/2LSeA0nexL+hA2GJB - GKhFuYZvggen - -----END CERTIFICATE-----` - - osMkdirError := errors.New("osMkdir failed") - - var reset = func() { - osMkdir = func(path string, mode os.FileMode) (error) { - panic("should be mocked") - } - osCreate = func(file string) (*os.File, error) { - panic("should be mocked") - } - osStat = func(file string) (os.FileInfo, error) { - panic("should be mocked") - } - ioCopy = func(target io.Writer, source io.Reader) (int64, error) { - panic("should be mocked") - } - ioutilReadFile = func(cafile string) ([]byte, error) { - panic("should be mocked") - } - contentserverGetBAR = func(url string, serverName string, token string, contentServerCACert []byte, contentServerCert string, contentServerKey string, log logger.LoggerInterface) (io.ReadCloser, error) { - panic("should be mocked") - } - } - - var restore = func() { - osMkdir = osMkdirRestore - osCreate = osCreateRestore - osStat = osStatRestore - ioutilReadFile = ioutilReadFileRestore - ioCopy = ioCopyRestore - contentserverGetBAR = contentserverGetBARRestore - } - - reset() - defer restore() - - t.Run("No error when ACE_CONTENT_SERVER_URL not set", func(t *testing.T) { - - os.Unsetenv("ACE_CONTENT_SERVER_URL") - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Nil(t, errorReturned) - }) - - t.Run("Fails when mkDir fails", func(t *testing.T) { - - var contentServerName = "domsdash-ibm-ace-dashboard-prod" - var barAuth = "userid=fsdjfhksdjfhsd" - var barUrl = "https://"+contentServerName+":3443/v1/directories/CustomerDatabaseV1" - - os.Unsetenv("DEFAULT_CONTENT_SERVER") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Setenv("ACE_CONTENT_SERVER_NAME", contentServerName) - os.Setenv("ACE_CONTENT_SERVER_TOKEN", barAuth) - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return osMkdirError - } - - errorReturned := getConfigurationFromContentServer() - - assert.Equal(t, errorReturned, osMkdirError) - }) - - - t.Run("Creates barfile.bar when ACE_CONTENT_SERVER_URL is SINGLE url and ACE_CONTENT_SERVER_NAME + ACE_CONTENT_SERVER_TOKEN - backward compatibility for MQ connector", func(t *testing.T) { - - var contentServerName = "domsdash-ibm-ace-dashboard-prod" - var barAuth = "userid=fsdjfhksdjfhsd" - var barUrl = "https://"+contentServerName+":3443/v1/directories/CustomerDatabaseV1" - - testReadCloser := ioutil.NopCloser(strings.NewReader("test")) - - os.Unsetenv("DEFAULT_CONTENT_SERVER") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Setenv("ACE_CONTENT_SERVER_NAME", contentServerName) - os.Setenv("ACE_CONTENT_SERVER_TOKEN", barAuth) - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - osCreate = func(file string) (*os.File, error) { - assert.Equal(t, "/home/aceuser/initial-config/bars/barfile.bar", file) - return nil, nil - } - - - osStat = func(file string) (os.FileInfo, error) { - // Should not be called - t.Errorf("Should not check if file exist when only single bar URL") - return nil, nil - } - - ioutilReadFile = func(cafile string) ([]byte, error) { - assert.Equal(t, "/home/aceuser/ssl/cacert.pem", cafile) - return []byte(dummyCert), nil - } - - contentserverGetBAR = func(url string, serverName string, token string, contentServerCACert []byte, contentServerCert string, contentServerKey string, log logger.LoggerInterface) (io.ReadCloser, error) { - assert.Equal(t, barUrl + "?archive=true", url) - assert.Equal(t, contentServerName, serverName) - assert.Equal(t, barAuth, token) - assert.Equal(t, []byte(dummyCert), contentServerCACert) - assert.Equal(t, "cacert", contentServerCert) - assert.Equal(t, "cakey", contentServerKey) - return testReadCloser, nil - } - - ioCopy = func(target io.Writer, source io.Reader) (int64, error) { - return 0, nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Nil(t, errorReturned) - - }) - - t.Run("Error when DEFAULT_CONTENT_SERVER is true but no token found", func(t *testing.T) { - - var contentServerName = "domsdash-ibm-ace-dashboard-prod" - var barUrl = "https://"+contentServerName+":3443/v1/directories/CustomerDatabaseV1" - - os.Setenv("DEFAULT_CONTENT_SERVER", "true") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Unsetenv("ACE_CONTENT_SERVER_NAME") - os.Unsetenv("ACE_CONTENT_SERVER_TOKEN") - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - ioutilReadFile = func(cafile string) ([]byte, error) { - assert.Equal(t, "/home/aceuser/ssl/cacert.pem", cafile) - return []byte(dummyCert), nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Equal(t, errors.New("No content server token available but a url is defined"), errorReturned) - - }) - - t.Run("No error when DEFAULT_CONTENT_SERVER is false and token found", func(t *testing.T) { - - var contentServerName = "domsdash-ibm-ace-dashboard-prod" - var barUrl = "https://"+contentServerName+":3443/v1/directories/CustomerDatabaseV1?user=default" - - testReadCloser := ioutil.NopCloser(strings.NewReader("test")) - - os.Setenv("DEFAULT_CONTENT_SERVER", "false") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Unsetenv("ACE_CONTENT_SERVER_NAME") - os.Unsetenv("ACE_CONTENT_SERVER_TOKEN") - os.Setenv("CONTENT_SERVER_CA", "/home/aceuser/ssl/mycustom.pem") - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - osCreate = func(file string) (*os.File, error) { - assert.Equal(t, "/home/aceuser/initial-config/bars/barfile.bar", file) - return nil, nil - } - - osStat = func(file string) (os.FileInfo, error) { - // Should not be called - t.Errorf("Should not check if file exist when only single bar URL") - return nil, nil - } - - ioutilReadFile = func(cafile string) ([]byte, error) { - assert.Equal(t, "/home/aceuser/ssl/mycustom.pem", cafile) - return []byte(dummyCert), nil - } - - contentserverGetBAR = func(url string, serverName string, token string, contentServerCACert []byte, contentServerCert string, contentServerKey string, log logger.LoggerInterface) (io.ReadCloser, error) { - assert.Equal(t, barUrl, url) - assert.Equal(t, contentServerName, serverName) - assert.Equal(t, "user=default", token) - assert.Equal(t, []byte(dummyCert), contentServerCACert) - assert.Equal(t, "cacert", contentServerCert) - assert.Equal(t, "cakey", contentServerKey) - return testReadCloser, nil - } - - ioCopy = func(target io.Writer, source io.Reader) (int64, error) { - return 0, nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Nil(t, errorReturned) - - }) - - - t.Run("No error when DEFAULT_CONTENT_SERVER is false and no token found", func(t *testing.T) { - - var contentServerName = "domsdash-ibm-ace-dashboard-prod" - var barUrl = "https://"+contentServerName+":3443/v1/directories/CustomerDatabaseV1" - - testReadCloser := ioutil.NopCloser(strings.NewReader("test")) - - os.Setenv("DEFAULT_CONTENT_SERVER", "false") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Unsetenv("ACE_CONTENT_SERVER_NAME") - os.Unsetenv("ACE_CONTENT_SERVER_TOKEN") - os.Setenv("CONTENT_SERVER_CA", "/home/aceuser/ssl/mycustom.pem") - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - osCreate = func(file string) (*os.File, error) { - assert.Equal(t, "/home/aceuser/initial-config/bars/barfile.bar", file) - return nil, nil - } - - osStat = func(file string) (os.FileInfo, error) { - // Should not be called - t.Errorf("Should not check if file exist when only single bar URL") - return nil, nil - } - - ioutilReadFile = func(cafile string) ([]byte, error) { - assert.Equal(t, "/home/aceuser/ssl/mycustom.pem", cafile) - return []byte(dummyCert), nil - } - - contentserverGetBAR = func(url string, serverName string, token string, contentServerCACert []byte, contentServerCert string, contentServerKey string, log logger.LoggerInterface) (io.ReadCloser, error) { - assert.Equal(t, barUrl, url) - assert.Equal(t, contentServerName, serverName) - assert.Equal(t, "", token) - assert.Equal(t, []byte(dummyCert), contentServerCACert) - assert.Equal(t, "cacert", contentServerCert) - assert.Equal(t, "cakey", contentServerKey) - return testReadCloser, nil - } - - ioCopy = func(target io.Writer, source io.Reader) (int64, error) { - return 0, nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Nil(t, errorReturned) - - }) - - - t.Run("Error when DEFAULT_CONTENT_SERVER is false but no CONTENT_SERVER_CA found", func(t *testing.T) { - - var contentServerName = "domsdash-ibm-ace-dashboard-prod" - var barUrl = "https://"+contentServerName+":3443/v1/directories/CustomerDatabaseV1" - - os.Setenv("DEFAULT_CONTENT_SERVER", "false") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Unsetenv("ACE_CONTENT_SERVER_NAME") - os.Unsetenv("ACE_CONTENT_SERVER_TOKEN") - os.Unsetenv("CONTENT_SERVER_CA") - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Equal(t, errors.New("CONTENT_SERVER_CA not defined"), errorReturned) - - }) - - t.Run("Creates multiple files when ACE_CONTENT_SERVER_URL is array url and extracts server name and auth from urls - multi bar support", func(t *testing.T) { - - //https://alexdash-ibm-ace-dashboard-prod:3443/v1/directories/CustomerDatabaseV1?userid=fsdjfhksdjfhsd - var barName1 = "CustomerDatabaseV1" - var contentServerName1 = "alexdash-ibm-ace-dashboard-prod" - var barAuth1 = "userid=fsdjfhksdjfhsd" - var barUrl1 = "https://"+contentServerName1+":3443/v1/directories/" + barName1 - - //https://test-acecontentserver-ace-alex.svc:3443/v1/directories/testdir?e31d23f6-e3ba-467d-ab3b-ceb0ab12eead - var barName2 = "testdir" - var contentServerName2 = "test-acecontentserver-ace-alex.svc" - var barAuth2 = "e31d23f6-e3ba-467d-ab3b-ceb0ab12eead" - var barUrl2 = "https://"+contentServerName2+":3443/v1/directories/" + barName2 - - var barUrl = barUrl1 + "?" + barAuth1 + "," + barUrl2 + "?" + barAuth2 - - testReadCloser := ioutil.NopCloser(strings.NewReader("test")) - - os.Unsetenv("DEFAULT_CONTENT_SERVER") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Unsetenv("ACE_CONTENT_SERVER_NAME") - os.Unsetenv("ACE_CONTENT_SERVER_TOKEN") - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - osMkdir = func(dirPath string, mode os.FileMode) (error) { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - osCreateCall := 1 - osCreate = func(file string) (*os.File, error) { - if osCreateCall == 1 { - assert.Equal(t, "/home/aceuser/initial-config/bars/" + barName1 + ".bar", file) - } else if osCreateCall == 2 { - assert.Equal(t, "/home/aceuser/initial-config/bars/" + barName2 + ".bar", file) - } - osCreateCall = osCreateCall + 1 - return nil, nil - } - - osStat = func(file string) (os.FileInfo, error) { - return nil, os.ErrNotExist - } - - ioutilReadFile = func(cafile string) ([]byte, error) { - assert.Equal(t, "/home/aceuser/ssl/cacert.pem", cafile) - return []byte(dummyCert), nil - } - - getBarCall := 1 - contentserverGetBAR = func(url string, serverName string, token string, contentServerCACert []byte, contentServerCert string, contentServerKey string, log logger.LoggerInterface) (io.ReadCloser, error) { - if getBarCall == 1 { - assert.Equal(t, barUrl1 + "?archive=true", url) - assert.Equal(t, contentServerName1, serverName) - assert.Equal(t, barAuth1, token) - assert.Equal(t, []byte(dummyCert), contentServerCACert) - assert.Equal(t, "cacert", contentServerCert) - assert.Equal(t, "cakey", contentServerKey) - } else if getBarCall == 2 { - assert.Equal(t, barUrl2 + "?archive=true", url) - assert.Equal(t, contentServerName2, serverName) - assert.Equal(t, barAuth2, token) - assert.Equal(t, []byte(dummyCert), contentServerCACert) - assert.Equal(t, "cacert", contentServerCert) - assert.Equal(t, "cakey", contentServerKey) - } - getBarCall = getBarCall + 1 - return testReadCloser, nil - } - - ioCopy = func(target io.Writer, source io.Reader) (int64, error) { - return 0, nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Nil(t, errorReturned) - }) - - t.Run("Creates multiple files with different names when using multi bar support and the bar file names are all the same", func(t *testing.T) { - - //https://alexdash-ibm-ace-dashboard-prod:3443/v1/directories/CustomerDatabaseV1?userid=fsdjfhksdjfhsd - var barName = "CustomerDatabaseV1" - var contentServerName = "alexdash-ibm-ace-dashboard-prod" - var barAuth = "userid=fsdjfhksdjfhsd" - var barUrlBase = "https://" + contentServerName + ":3443/v1/directories/" + barName - var barUrlFull = barUrlBase + "?" + barAuth - - var barUrl = barUrlFull + "," + barUrlFull + "," + barUrlFull - - testReadCloser := ioutil.NopCloser(strings.NewReader("test")) - - os.Unsetenv("DEFAULT_CONTENT_SERVER") - os.Setenv("ACE_CONTENT_SERVER_URL", barUrl) - os.Unsetenv("ACE_CONTENT_SERVER_NAME") - os.Unsetenv("ACE_CONTENT_SERVER_TOKEN") - os.Setenv("CONTENT_SERVER_CERT", "cacert") - os.Setenv("CONTENT_SERVER_KEY", "cakey") - - osMkdir = func(dirPath string, mode os.FileMode) error { - assert.Equal(t, barDirPath, dirPath) - assert.Equal(t, os.ModePerm, mode) - return nil - } - - createdFiles := map[string]bool{} - osCreateCall := 1 - osCreate = func(file string) (*os.File, error) { - createdFiles[file] = true - if osCreateCall == 1 { - assert.Equal(t, "/home/aceuser/initial-config/bars/"+barName+".bar", file) - } else if osCreateCall == 2 { - assert.Equal(t, "/home/aceuser/initial-config/bars/"+barName+"-1.bar", file) - } else if osCreateCall == 3 { - assert.Equal(t, "/home/aceuser/initial-config/bars/"+barName+"-2.bar", file) - } - osCreateCall = osCreateCall + 1 - return nil, nil - } - - osStat = func(file string) (os.FileInfo, error) { - if createdFiles[file] { - return nil, os.ErrExist - } else { - return nil, os.ErrNotExist - } - } - - ioutilReadFile = func(cafile string) ([]byte, error) { - assert.Equal(t, "/home/aceuser/ssl/cacert.pem", cafile) - return []byte(dummyCert), nil - } - - getBarCall := 1 - contentserverGetBAR = func(url string, serverName string, token string, contentServerCACert []byte, contentServerCert string, contentServerKey string, log logger.LoggerInterface) (io.ReadCloser, error) { - assert.Equal(t, barUrlBase+"?archive=true", url) - assert.Equal(t, contentServerName, serverName) - assert.Equal(t, barAuth, token) - assert.Equal(t, []byte(dummyCert), contentServerCACert) - assert.Equal(t, "cacert", contentServerCert) - assert.Equal(t, "cakey", contentServerKey) - getBarCall = getBarCall + 1 - return testReadCloser, nil - } - - ioCopy = func(target io.Writer, source io.Reader) (int64, error) { - return 0, nil - } - - errorReturned := getConfigurationFromContentServer() - - assert.Nil(t, errorReturned) - }) -} - -func TestWatchForceFlowsHTTPSSecret(t *testing.T) { - - patchHTTPSConnectorCalled := 0 - oldpatchHTTPSConnector := patchHTTPSConnector - defer func() { patchHTTPSConnector = oldpatchHTTPSConnector }() - patchHTTPSConnector = func(string) { - patchHTTPSConnectorCalled++ - } - - oldgenerateHTTPSKeystore := generateHTTPSKeystore - defer func() { generateHTTPSKeystore = oldgenerateHTTPSKeystore }() - generateHTTPSKeystore = func(string, string, string, string) { - } - - // Tidy up any /tmp files - files, err := filepath.Glob("/tmp/forceflowtest*") - if err != nil { - log.Errorf("Error finding tmp files: %v", err) - } - for _, f := range files { - if err := os.Remove(f); err != nil { - log.Errorf("Error removing tmp files: %v", err) - } - } - - // Create new test file - extension := generatePassword(5) - f, err := os.Create("/tmp/forceflowtest"+ extension) - helloFile := []byte{115, 111, 109, 101, 10} // hello - _, err = f.Write(helloFile) - - // Now create the watcher and watch the file created above - watcher := watchForceFlowsHTTPSSecret("TESTPASS") - err = watcher.Add("/tmp/forceflowtest"+ extension) - if err != nil { - log.Errorf("Error watching /home/aceuser/httpsNodeCerts for Force Flows to be HTTPS: %v", err) - } - - // Now write to the file to check we are called 2 times - // waits are required as if you update too quickly you only get called once - _, err = f.Write(helloFile) - time.Sleep(2* time.Second) - _, err = f.Write(helloFile) - time.Sleep(2* time.Second) - - // clean up watcher and temporary file - os.Remove("/tmp/forceflowtest"+ extension) - watcher.Close() - - // expected patchHTTPSConnector to be called twice - assert.Equal(t, patchHTTPSConnectorCalled, 2) - - log.Println("done") -} - -func TestUDSCall(t *testing.T) { - // curl --unix-socket /tmp/47EBPflowtest.uds http:/apiv2/resource-managers/https-connector/refresh-tls-config - - // Tidy up any /tmp files - files, err := filepath.Glob("/tmp/*flowtest.uds") - if err != nil { - log.Errorf("Error finding tmp files: %v", err) - } - for _, f := range files { - if err := os.Remove(f); err != nil { - log.Errorf("Error removing tmp files: %v", err) - } - } - - extension := generatePassword(5) - uds := "/tmp/"+extension+"flowtest.uds" - - timesServerCalled := 0 - - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if (r.Method == "POST") && (r.RequestURI== "/apiv2/resource-managers/https-connector/refresh-tls-config") { - timesServerCalled++ - } else { - log.Print("Error: TestUDSCall called with the following but expected POST and /apiv2/resource-managers/https-connector/refresh-tls-config") - log.Printf(r.Method) - log.Printf(r.RequestURI) - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.RequestURI, "/apiv2/resource-managers/https-connector/refresh-tls-config") - } - }) - - unixListener, err := net.Listen("unix", uds) - if err != nil { - log.Errorf("Error creating UDS listener: %v", err) - } - handler := http.DefaultServeMux - // Kick off server in the background - go func() { - err = http.Serve(unixListener, handler) - if err != nil { - log.Errorf("Error starting up http UDS server: %v", err) - } - }() - - // test we can call the server above twice - localPatchHTTPSConnector(uds) - assert.Equal(t, timesServerCalled, 1) - localPatchHTTPSConnector(uds) - assert.Equal(t, timesServerCalled, 2) - - // tidy up uds file - os.Remove(uds) -} - - - -func TestGenerateHTTPSKeystore(t *testing.T) { - password := "anypassword" - - testPrivateKey := `-----BEGIN RSA PRIVATE KEY----- -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 ------END RSA PRIVATE KEY----- -` - - testCertificate := `-----BEGIN CERTIFICATE----- -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 -iipaBg== ------END CERTIFICATE----- -` - - // Tidy up any /tmp files - files, err := filepath.Glob("/tmp/*generatekeystore*") - if err != nil { - log.Errorf("Error finding tmp files: %v", err) - } - for _, f := range files { - if err := os.Remove(f); err != nil { - log.Errorf("Error removing tmp files: %v", err) - } - } - - extension := generatePassword(5) - privateKeyFileLocation := "/tmp/"+extension+"generatekeystore.tls.key" - certificateLocation := "/tmp/"+extension+"generatekeystore.tls.cert" - keystoreLocation := "/tmp/"+extension+"generatekeystore.https-keystore.p12" - - keyFile, err := os.Create(privateKeyFileLocation) - if (err != nil) { - log.Error("Could not create " + privateKeyFileLocation) - assert.Nil(t, err) - } - keyFile.WriteString(testPrivateKey) - - certificateFile, err := os.Create(certificateLocation) - if (err != nil) { - log.Error("Could not create " + certificateLocation) - assert.Nil(t, err) - } - certificateFile.WriteString(testCertificate) - - generateHTTPSKeystore(privateKeyFileLocation, certificateLocation, keystoreLocation, password) - - // Check created keystore - fileInfo, err := os.Stat(keystoreLocation) - if (err != nil) { - log.Error("Could not stat " + keystoreLocation) - assert.Nil(t, err) - } - - // Check keystore size - assert.Equal(t, fileInfo.Size(), int64(4239)) - assert.Equal(t, fileInfo.IsDir(), false) - - // clean up - os.Remove(privateKeyFileLocation) - os.Remove(certificateLocation) - os.Remove(keystoreLocation) - -} - - - -var yamlOpenTracingTests = []struct { - in string - out string -}{ - { // User's blank yaml -``, -`UserExits: - activeUserExitList: ACEOpenTracingUserExit - userExitPath: /opt/ACEOpenTracing -`}, - { // Do not alter as values provided -`UserExits: - activeUserExitList: ACEOpenTracingUserExit - userExitPath: /opt/ACEOpenTracing`, -`UserExits: - activeUserExitList: ACEOpenTracingUserExit - userExitPath: /opt/ACEOpenTracing -`}, - { // Check all values -`UserExits: - userExitPath: /opt/ACEOpenTracing`, -`UserExits: - activeUserExitList: ACEOpenTracingUserExit - userExitPath: /opt/ACEOpenTracing -`}, -} - -func TestAddOpenTracingToServerConf(t *testing.T) { - for _, table := range yamlOpenTracingTests { - out, err := addOpenTracingToServerConf([]byte(table.in)) - if err != nil { - t.Error(err) - } - stringOut := string(out) - if stringOut != table.out { - t.Errorf("addOpenTracingToServerConf expected \n%v, got \n%v", table.out, stringOut) - } - } -} \ No newline at end of file diff --git a/cmd/runaceserver/integrationserver_test.go b/cmd/runaceserver/integrationserver_test.go deleted file mode 100644 index 98b4fe5..0000000 --- a/cmd/runaceserver/integrationserver_test.go +++ /dev/null @@ -1,438 +0,0 @@ -package main - -import ( - "errors" - "os" - "os/exec" - "testing" - - "github.com/ot4i/ace-docker/common/logger" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v2" -) - -func Test_initialIntegrationServerConfig(t *testing.T) { - oldGetConfigurationFromContentServer := getConfigurationFromContentServer - getConfigurationFromContentServer = func() error { - return nil - } - t.Run("Golden path - When initial-config/webusers dir exists we call into ConfigureWebAdminUsers to process users", func(t *testing.T) { - oldCreateSHAServerConfYaml := createSHAServerConfYaml - createSHAServerConfYaml = func() error { - return nil - } - oldConfigureWebAdminUsers := ConfigureWebAdminUsers - ConfigureWebAdminUsers = func(log logger.LoggerInterface) error { - return nil - } - - homedir = "../../internal/webadmin/testdata" - initialConfigDir = "../../internal/webadmin/testdata/initial-config" - err := initialIntegrationServerConfig() - assert.NoError(t, err) - - createSHAServerConfYaml = oldCreateSHAServerConfYaml - ConfigureWebAdminUsers = oldConfigureWebAdminUsers - - }) - - t.Run("When we fail to properly configure WebAdmin users we fail and return error", func(t *testing.T) { - oldCreateSHAServerConfYaml := createSHAServerConfYaml - createSHAServerConfYaml = func() error { - return nil - } - oldConfigureWebAdminUsers := ConfigureWebAdminUsers - ConfigureWebAdminUsers = func(log logger.LoggerInterface) error { - return errors.New("Error processing WebAdmin users") - } - homedir = "../../internal/webadmin/testdata" - initialConfigDir = "../../internal/webadmin/testdata/initial-config" - err := initialIntegrationServerConfig() - assert.Error(t, err) - assert.Equal(t, "Error processing WebAdmin users", err.Error()) - - createSHAServerConfYaml = oldCreateSHAServerConfYaml - ConfigureWebAdminUsers = oldConfigureWebAdminUsers - }) - - getConfigurationFromContentServer = oldGetConfigurationFromContentServer -} - -func Test_createSHAServerConfYamlLocal(t *testing.T) { - t.Run("Golden path - Empty file gets populated with the right values", func(t *testing.T) { - - oldReadServerConfFile := readServerConfFile - readServerConfFile = func() ([]byte, error) { - return []byte{}, nil - } - oldWriteServerConfFileLocal := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return nil - } - - err := createSHAServerConfYaml() - assert.NoError(t, err) - readServerConfFile = oldReadServerConfFile - writeServerConfFile = oldWriteServerConfFileLocal - }) - t.Run("Golden path 2 - Populated file gets handled and no errors", func(t *testing.T) { - - oldReadServerConfFile := readServerConfFile - readServerConfFile = func() ([]byte, error) { - file, err := os.ReadFile("../../internal/webadmin/testdata/initial-config/webusers/server.conf.yaml") - if err != nil { - t.Log(err) - t.Fail() - } - return file, nil - } - oldWriteServerConfFileLocal := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return nil - } - oldYamlMarshal := yamlMarshal - yamlMarshal = func(in interface{}) (out []byte, err error) { - return nil, nil - } - - err := createSHAServerConfYaml() - assert.NoError(t, err) - readServerConfFile = oldReadServerConfFile - writeServerConfFile = oldWriteServerConfFileLocal - yamlMarshal = oldYamlMarshal - - }) - t.Run("Error reading server.conf.yaml file", func(t *testing.T) { - oldReadServerConfFile := readServerConfFile - readServerConfFile = func() ([]byte, error) { - return nil, errors.New("Error reading server.conf.yaml") - } - oldWriteServerConfFileLocal := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return nil - } - - err := createSHAServerConfYaml() - assert.Error(t, err) - readServerConfFile = oldReadServerConfFile - writeServerConfFile = oldWriteServerConfFileLocal - }) - t.Run("yaml.Marshal fails to execute properly", func(t *testing.T) { - oldYamlUnmarshal := yamlUnmarshal - yamlUnmarshal = func(in []byte, out interface{}) (err error) { - return errors.New("Error unmarshalling yaml") - } - oldYamlMarshal := yamlMarshal - yamlMarshal = func(in interface{}) (out []byte, err error) { - return nil, nil - } - - err := createSHAServerConfYaml() - assert.Error(t, err) - assert.Equal(t, "Error unmarshalling yaml", err.Error()) - - yamlUnmarshal = oldYamlUnmarshal - yamlMarshal = oldYamlMarshal - }) - t.Run("yaml.Marshal fails to execute properly", func(t *testing.T) { - oldYamlUnmarshal := yamlUnmarshal - yamlUnmarshal = func(in []byte, out interface{}) (err error) { - return nil - } - oldYamlMarshal := yamlMarshal - yamlMarshal = func(in interface{}) (out []byte, err error) { - return nil, errors.New("Error marshalling yaml") - } - - err := createSHAServerConfYaml() - assert.Error(t, err) - assert.Equal(t, "Error marshalling yaml", err.Error()) - - yamlUnmarshal = oldYamlUnmarshal - yamlMarshal = oldYamlMarshal - }) - t.Run("yaml.Marshal fails to execute properly", func(t *testing.T) { - oldYamlUnmarshal := yamlUnmarshal - yamlUnmarshal = func(in []byte, out interface{}) (err error) { - return nil - } - oldYamlMarshal := yamlMarshal - yamlMarshal = func(in interface{}) (out []byte, err error) { - return nil, nil - } - oldWriteServerConfFile := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return errors.New("Error writing server.conf.yaml") - } - err := createSHAServerConfYaml() - assert.Error(t, err) - assert.Equal(t, "Error writing server.conf.yaml", err.Error()) - - yamlUnmarshal = oldYamlUnmarshal - yamlMarshal = oldYamlMarshal - writeServerConfFile = oldWriteServerConfFile - }) -} - -func Test_deployIntegrationFlowResources(t *testing.T) { - t.Run("deployIntegrationFlowResources returns nil as both commands returned without error", func(t *testing.T) { - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - return "success", 0, nil - } - err := deployIntegrationFlowResources() - assert.NoError(t, err) - }) - - t.Run("deployIntegrationFlowResources error running acecc-bar-gen.js", func(t *testing.T) { - oldpackageBarFile := packageBarFile - defer func() { packageBarFile = oldpackageBarFile }() - packageBarFile = func() error { - err := errors.New("Error running acecc-bar-gen.js") - return err - } - - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - err := errors.New("Error running acecc-bar-gen.js") - return "Error running acecc-bar-gen.js", 1, err - } - - err := deployIntegrationFlowResources() - assert.Error(t, err) - }) - - t.Run("deployIntegrationFlowResources error deploying BAR file - non zero return code", func(t *testing.T) { - oldpackageBarFile := packageBarFile - defer func() { packageBarFile = oldpackageBarFile }() - packageBarFile = func() error { - return nil - } - - olddeployBarFile := deployBarFile - defer func() { deployBarFile = olddeployBarFile }() - deployBarFile = func() error { - err := errors.New("Error deploying BAR file") - return err - } - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - err := errors.New("Error deploying BAR file") - return "Error deploying BAR file", 1, err - } - - err := deployIntegrationFlowResources() - assert.Error(t, err) - }) -} - -func Test_PackageBarFile(t *testing.T) { - t.Run("Success running acecc-bar-gen.js", func(t *testing.T) { - - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - return "Success", 0, nil - } - err := packageBarFile() - assert.NoError(t, err) - - }) - t.Run("Error running acecc-bar-gen.js", func(t *testing.T) { - - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - err := errors.New("Error running acecc-bar-gen.js") - return "Error running acecc-bar-gen.js", 1, err - } - err := packageBarFile() - assert.Error(t, err) - }) -} - -func Test_DeployBarFile(t *testing.T) { - t.Run("Success deploying BAR file", func(t *testing.T) { - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - return "Success", 0, nil - } - err := deployBarFile() - assert.NoError(t, err) - - }) - t.Run("Error deploying BAR file", func(t *testing.T) { - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - err := errors.New("Error running deploying BAR file") - return "Error running deploying BAR file", 1, err - } - err := deployBarFile() - assert.Error(t, err) - }) - -} - -func Test_deployCSAPIFlows(t *testing.T) { - - t.Run("Env var not set", func(t *testing.T) { - called := 0 - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - called++ - return "Success", 0, nil - } - err := deployCSAPIFlows() - assert.NoError(t, err) - assert.Equal(t, 0, called) - }) - - t.Run("Env var not set", func(t *testing.T) { - os.Setenv("CONNECTOR_SERVICE", "true") - - t.Run("Success deploying copyied files", func(t *testing.T) { - var command []string - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - command = cmd.Args - return "Success", 0, nil - } - err := deployCSAPIFlows() - assert.NoError(t, err) - assert.Contains(t, command, "/home/aceuser/deps/CSAPI") - assert.Contains(t, command, "/home/aceuser/ace-server/run/CSAPI") - - }) - t.Run("Error copying files", func(t *testing.T) { - oldcommandRunCmd := commandRunCmd - defer func() { commandRunCmd = oldcommandRunCmd }() - commandRunCmd = func(cmd *exec.Cmd) (string, int, error) { - err := errors.New("Error running deploying BAR file") - return "Error running deploying BAR file", 1, err - } - err := deployCSAPIFlows() - assert.Error(t, err) - }) - os.Unsetenv("CONNECTOR_SERVICE") - }) - -} - - -func Test_forceflowbasicauthServerConfUpdate(t *testing.T) { - t.Run("Golden path - Empty file gets populated with the right values", func(t *testing.T) { - - oldReadServerConfFile := readServerConfFile - readServerConfFile = func() ([]byte, error) { - return []byte{}, nil - } - oldWriteServerConfFileLocal := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return nil - } - - err := forceflowbasicauthServerConfUpdate() - assert.NoError(t, err) - readServerConfFile = oldReadServerConfFile - writeServerConfFile = oldWriteServerConfFileLocal - }) - - t.Run("Existing value in server.conf.yaml", func(t *testing.T) { - - serverconfMap := make(map[interface{}]interface{}) - serverconfMap["forceServerHTTPSecurityProfile"] = "{DefaultPolicies}:SecProfLocal" - serverconfYaml, _ := yaml.Marshal(&serverconfMap) - - oldReadServerConfFile := readServerConfFile - readServerConfFile = func() ([]byte, error) { - return serverconfYaml, nil - } - oldWriteServerConfFileLocal := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return nil - } - - err := forceflowbasicauthServerConfUpdate() - assert.NoError(t, err) - readServerConfFile = oldReadServerConfFile - writeServerConfFile = oldWriteServerConfFileLocal - }) - - t.Run("Error reading server.conf.yaml file", func(t *testing.T) { - oldReadServerConfFile := readServerConfFile - readServerConfFile = func() ([]byte, error) { - return nil, errors.New("Error reading server.conf.yaml") - } - oldWriteServerConfFileLocal := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return nil - } - - err := forceflowbasicauthServerConfUpdate() - assert.Error(t, err) - readServerConfFile = oldReadServerConfFile - writeServerConfFile = oldWriteServerConfFileLocal - }) - t.Run("yaml.Marshal fails to execute properly", func(t *testing.T) { - oldYamlUnmarshal := yamlUnmarshal - yamlUnmarshal = func(in []byte, out interface{}) (err error) { - return errors.New("Error unmarshalling yaml") - } - oldYamlMarshal := yamlMarshal - yamlMarshal = func(in interface{}) (out []byte, err error) { - return nil, nil - } - - err := forceflowbasicauthServerConfUpdate() - assert.Error(t, err) - assert.Equal(t, "Error unmarshalling yaml", err.Error()) - - yamlUnmarshal = oldYamlUnmarshal - yamlMarshal = oldYamlMarshal - }) - t.Run("yaml.Marshal fails to execute properly", func(t *testing.T) { - oldYamlUnmarshal := yamlUnmarshal - yamlUnmarshal = func(in []byte, out interface{}) (err error) { - return nil - } - oldYamlMarshal := yamlMarshal - yamlMarshal = func(in interface{}) (out []byte, err error) { - return nil, errors.New("Error marshalling yaml") - } - - err := forceflowbasicauthServerConfUpdate() - assert.Error(t, err) - assert.Equal(t, "Error marshalling yaml", err.Error()) - - yamlUnmarshal = oldYamlUnmarshal - yamlMarshal = oldYamlMarshal - }) - t.Run("yaml.Marshal fails to execute properly", func(t *testing.T) { - oldYamlUnmarshal := yamlUnmarshal - yamlUnmarshal = func(in []byte, out interface{}) (err error) { - return nil - } - oldYamlMarshal := yamlMarshal - yamlMarshal = func(in interface{}) (out []byte, err error) { - return nil, nil - } - oldWriteServerConfFile := writeServerConfFile - writeServerConfFile = func(content []byte) error { - return errors.New("Error writing server.conf.yaml") - } - err := forceflowbasicauthServerConfUpdate() - assert.Error(t, err) - assert.Equal(t, "Error writing server.conf.yaml", err.Error()) - - yamlUnmarshal = oldYamlUnmarshal - yamlMarshal = oldYamlMarshal - writeServerConfFile = oldWriteServerConfFile - }) -} diff --git a/cmd/runaceserver/license.go b/cmd/runaceserver/license.go deleted file mode 100644 index 0d7eb26..0000000 --- a/cmd/runaceserver/license.go +++ /dev/null @@ -1,92 +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 ( - "errors" - "io/ioutil" - "os" - "path/filepath" - "strings" -) - -// resolveLicenseFile returns the file name of the MQ license file, taking into -// account the language set by the LANG environment variable -func resolveLicenseFile() string { - lang, ok := os.LookupEnv("LANG") - if !ok { - return "English.txt" - } - switch { - case strings.HasPrefix(lang, "zh_TW"): - return "Chinese_TW.txt" - case strings.HasPrefix(lang, "zh"): - return "Chinese.txt" - // Differentiate Czech (cs) and Kashubian (csb) - case strings.HasPrefix(lang, "cs") && !strings.HasPrefix(lang, "csb"): - return "Czech.txt" - case strings.HasPrefix(lang, "fr"): - return "French.txt" - case strings.HasPrefix(lang, "de"): - return "German.txt" - case strings.HasPrefix(lang, "el"): - return "Greek.txt" - case strings.HasPrefix(lang, "id"): - return "Indonesian.txt" - case strings.HasPrefix(lang, "it"): - return "Italian.txt" - case strings.HasPrefix(lang, "ja"): - return "Japanese.txt" - // Differentiate Korean (ko) from Konkani (kok) - case strings.HasPrefix(lang, "ko") && !strings.HasPrefix(lang, "kok"): - return "Korean.txt" - case strings.HasPrefix(lang, "lt"): - return "Lithuanian.txt" - case strings.HasPrefix(lang, "pl"): - return "Polish.txt" - case strings.HasPrefix(lang, "pt"): - return "Portugese.txt" - case strings.HasPrefix(lang, "ru"): - return "Russian.txt" - case strings.HasPrefix(lang, "sl"): - return "Slovenian.txt" - case strings.HasPrefix(lang, "es"): - return "Spanish.txt" - case strings.HasPrefix(lang, "tr"): - return "Turkish.txt" - } - return "English.txt" -} - -func checkLicense() (bool, error) { - lic, ok := os.LookupEnv("LICENSE") - switch { - case ok && lic == "accept": - return true, nil - case ok && lic == "view": - file := filepath.Join("/opt/ibm/ace-12/license", resolveLicenseFile()) - buf, err := ioutil.ReadFile(file) - if err != nil { - log.Println(err) - return false, err - } - log.Println(string(buf)) - return false, nil - } - log.Println("Error: Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions.") - log.Println("License agreements and information can be viewed by setting the environment variable LICENSE=view. You can also set the LANG environment variable to view the license in a different language.") - return false, errors.New("Set environment variable LICENSE=accept to indicate acceptance of license terms and conditions") -} diff --git a/cmd/runaceserver/license_internal_test.go b/cmd/runaceserver/license_internal_test.go deleted file mode 100644 index 3495aa8..0000000 --- a/cmd/runaceserver/license_internal_test.go +++ /dev/null @@ -1,282 +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 ( - "os" - "testing" -) - -var licenseTests = []struct { - in string - out string -}{ - {"en_US.UTF_8", "English.txt"}, - {"en_US.ISO-8859-15", "English.txt"}, - {"es_GB", "Spanish.txt"}, - {"el_ES.UTF_8", "Greek.txt"}, - // Cover a wide variety of valid values - {"af", "English.txt"}, - {"af_ZA", "English.txt"}, - {"ar", "English.txt"}, - {"ar_AE", "English.txt"}, - {"ar_BH", "English.txt"}, - {"ar_DZ", "English.txt"}, - {"ar_EG", "English.txt"}, - {"ar_IQ", "English.txt"}, - {"ar_JO", "English.txt"}, - {"ar_KW", "English.txt"}, - {"ar_LB", "English.txt"}, - {"ar_LY", "English.txt"}, - {"ar_MA", "English.txt"}, - {"ar_OM", "English.txt"}, - {"ar_QA", "English.txt"}, - {"ar_SA", "English.txt"}, - {"ar_SY", "English.txt"}, - {"ar_TN", "English.txt"}, - {"ar_YE", "English.txt"}, - {"az", "English.txt"}, - {"az_AZ", "English.txt"}, - {"az_AZ", "English.txt"}, - {"be", "English.txt"}, - {"be_BY", "English.txt"}, - {"bg", "English.txt"}, - {"bg_BG", "English.txt"}, - {"bs_BA", "English.txt"}, - {"ca", "English.txt"}, - {"ca_ES", "English.txt"}, - {"cs", "Czech.txt"}, - {"cs_CZ", "Czech.txt"}, - {"csb_PL", "English.txt"}, - {"cy", "English.txt"}, - {"cy_GB", "English.txt"}, - {"da", "English.txt"}, - {"da_DK", "English.txt"}, - {"de", "German.txt"}, - {"de_AT", "German.txt"}, - {"de_CH", "German.txt"}, - {"de_DE", "German.txt"}, - {"de_LI", "German.txt"}, - {"de_LU", "German.txt"}, - {"dv", "English.txt"}, - {"dv_MV", "English.txt"}, - {"el", "Greek.txt"}, - {"el_GR", "Greek.txt"}, - {"en", "English.txt"}, - {"en_AU", "English.txt"}, - {"en_BZ", "English.txt"}, - {"en_CA", "English.txt"}, - {"en_CB", "English.txt"}, - {"en_GB", "English.txt"}, - {"en_IE", "English.txt"}, - {"en_JM", "English.txt"}, - {"en_NZ", "English.txt"}, - {"en_PH", "English.txt"}, - {"en_TT", "English.txt"}, - {"en_US", "English.txt"}, - {"en_ZA", "English.txt"}, - {"en_ZW", "English.txt"}, - {"eo", "English.txt"}, - {"es", "Spanish.txt"}, - {"es_AR", "Spanish.txt"}, - {"es_BO", "Spanish.txt"}, - {"es_CL", "Spanish.txt"}, - {"es_CO", "Spanish.txt"}, - {"es_CR", "Spanish.txt"}, - {"es_DO", "Spanish.txt"}, - {"es_EC", "Spanish.txt"}, - {"es_ES", "Spanish.txt"}, - {"es_ES", "Spanish.txt"}, - {"es_GT", "Spanish.txt"}, - {"es_HN", "Spanish.txt"}, - {"es_MX", "Spanish.txt"}, - {"es_NI", "Spanish.txt"}, - {"es_PA", "Spanish.txt"}, - {"es_PE", "Spanish.txt"}, - {"es_PR", "Spanish.txt"}, - {"es_PY", "Spanish.txt"}, - {"es_SV", "Spanish.txt"}, - {"es_UY", "Spanish.txt"}, - {"es_VE", "Spanish.txt"}, - {"et", "English.txt"}, - {"et_EE", "English.txt"}, - {"eu", "English.txt"}, - {"eu_ES", "English.txt"}, - {"fa", "English.txt"}, - {"fa_IR", "English.txt"}, - {"fi", "English.txt"}, - {"fi_FI", "English.txt"}, - {"fo", "English.txt"}, - {"fo_FO", "English.txt"}, - {"fr", "French.txt"}, - {"fr_BE", "French.txt"}, - {"fr_CA", "French.txt"}, - {"fr_CH", "French.txt"}, - {"fr_FR", "French.txt"}, - {"fr_LU", "French.txt"}, - {"fr_MC", "French.txt"}, - {"gl", "English.txt"}, - {"gl_ES", "English.txt"}, - {"gu", "English.txt"}, - {"gu_IN", "English.txt"}, - {"he", "English.txt"}, - {"he_IL", "English.txt"}, - {"hi", "English.txt"}, - {"hi_IN", "English.txt"}, - {"hr", "English.txt"}, - {"hr_BA", "English.txt"}, - {"hr_HR", "English.txt"}, - {"hu", "English.txt"}, - {"hu_HU", "English.txt"}, - {"hy", "English.txt"}, - {"hy_AM", "English.txt"}, - {"id", "Indonesian.txt"}, - {"id_ID", "Indonesian.txt"}, - {"is", "English.txt"}, - {"is_IS", "English.txt"}, - {"it", "Italian.txt"}, - {"it_CH", "Italian.txt"}, - {"it_IT", "Italian.txt"}, - {"ja", "Japanese.txt"}, - {"ja_JP", "Japanese.txt"}, - {"ka", "English.txt"}, - {"ka_GE", "English.txt"}, - {"kk", "English.txt"}, - {"kk_KZ", "English.txt"}, - {"kn", "English.txt"}, - {"kn_IN", "English.txt"}, - {"ko", "Korean.txt"}, - {"ko_KR", "Korean.txt"}, - {"kok", "English.txt"}, - {"kok_IN", "English.txt"}, - {"ky", "English.txt"}, - {"ky_KG", "English.txt"}, - {"lt", "Lithuanian.txt"}, - {"lt_LT", "Lithuanian.txt"}, - {"lv", "English.txt"}, - {"lv_LV", "English.txt"}, - {"mi", "English.txt"}, - {"mi_NZ", "English.txt"}, - {"mk", "English.txt"}, - {"mk_MK", "English.txt"}, - {"mn", "English.txt"}, - {"mn_MN", "English.txt"}, - {"mr", "English.txt"}, - {"mr_IN", "English.txt"}, - {"ms", "English.txt"}, - {"ms_BN", "English.txt"}, - {"ms_MY", "English.txt"}, - {"mt", "English.txt"}, - {"mt_MT", "English.txt"}, - {"nb", "English.txt"}, - {"nb_NO", "English.txt"}, - {"nl", "English.txt"}, - {"nl_BE", "English.txt"}, - {"nl_NL", "English.txt"}, - {"nn_NO", "English.txt"}, - {"ns", "English.txt"}, - {"ns_ZA", "English.txt"}, - {"pa", "English.txt"}, - {"pa_IN", "English.txt"}, - {"pl", "Polish.txt"}, - {"pl_PL", "Polish.txt"}, - {"ps", "English.txt"}, - {"ps_AR", "English.txt"}, - {"pt", "Portugese.txt"}, - {"pt_BR", "Portugese.txt"}, - {"pt_PT", "Portugese.txt"}, - {"qu", "English.txt"}, - {"qu_BO", "English.txt"}, - {"qu_EC", "English.txt"}, - {"qu_PE", "English.txt"}, - {"ro", "English.txt"}, - {"ro_RO", "English.txt"}, - {"ru", "Russian.txt"}, - {"ru_RU", "Russian.txt"}, - {"sa", "English.txt"}, - {"sa_IN", "English.txt"}, - {"se", "English.txt"}, - {"se_FI", "English.txt"}, - {"se_FI", "English.txt"}, - {"se_FI", "English.txt"}, - {"se_NO", "English.txt"}, - {"se_NO", "English.txt"}, - {"se_NO", "English.txt"}, - {"se_SE", "English.txt"}, - {"se_SE", "English.txt"}, - {"se_SE", "English.txt"}, - {"sk", "English.txt"}, - {"sk_SK", "English.txt"}, - {"sl", "Slovenian.txt"}, - {"sl_SI", "Slovenian.txt"}, - {"sq", "English.txt"}, - {"sq_AL", "English.txt"}, - {"sr_BA", "English.txt"}, - {"sr_BA", "English.txt"}, - {"sr_SP", "English.txt"}, - {"sr_SP", "English.txt"}, - {"sv", "English.txt"}, - {"sv_FI", "English.txt"}, - {"sv_SE", "English.txt"}, - {"sw", "English.txt"}, - {"sw_KE", "English.txt"}, - {"syr", "English.txt"}, - {"syr_SY", "English.txt"}, - {"ta", "English.txt"}, - {"ta_IN", "English.txt"}, - {"te", "English.txt"}, - {"te_IN", "English.txt"}, - {"th", "English.txt"}, - {"th_TH", "English.txt"}, - {"tl", "English.txt"}, - {"tl_PH", "English.txt"}, - {"tn", "English.txt"}, - {"tn_ZA", "English.txt"}, - {"tr", "Turkish.txt"}, - {"tr_TR", "Turkish.txt"}, - {"tt", "English.txt"}, - {"tt_RU", "English.txt"}, - {"ts", "English.txt"}, - {"uk", "English.txt"}, - {"uk_UA", "English.txt"}, - {"ur", "English.txt"}, - {"ur_PK", "English.txt"}, - {"uz", "English.txt"}, - {"uz_UZ", "English.txt"}, - {"uz_UZ", "English.txt"}, - {"vi", "English.txt"}, - {"vi_VN", "English.txt"}, - {"xh", "English.txt"}, - {"xh_ZA", "English.txt"}, - {"zh", "Chinese.txt"}, - {"zh_CN", "Chinese.txt"}, - {"zh_HK", "Chinese.txt"}, - {"zh_MO", "Chinese.txt"}, - {"zh_SG", "Chinese.txt"}, - {"zh_TW", "Chinese_TW.txt"}, - {"zu", "English.txt"}, - {"zu_ZA", "English.txt"}, -} - -func TestResolveLicenseFile(t *testing.T) { - for _, table := range licenseTests { - os.Setenv("LANG", table.in) - f := resolveLicenseFile() - if f != table.out { - t.Errorf("resolveLicenseFile() with LANG=%v - expected %v, got %v", table.in, table.out, f) - } - } -} diff --git a/cmd/runaceserver/logging.go b/cmd/runaceserver/logging.go deleted file mode 100644 index 86284a3..0000000 --- a/cmd/runaceserver/logging.go +++ /dev/null @@ -1,95 +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" - "io/ioutil" - "os" - - "github.com/ot4i/ace-docker/common/logger" -) - -var log logger.LoggerInterface - -func logTerminationf(format string, args ...interface{}) { - 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 - // that Kubernetes will look for termination information. - log.Debugf("Writing termination message: %v", msg) - err := ioutil.WriteFile("/dev/termination-log", []byte(msg), 0660) - if err != nil { - log.Debug(err) - } - log.Error(msg) -} - -func getLogFormat() string { - return os.Getenv("LOG_FORMAT") -} - -func getLogOutputFormat() string { - switch getLogFormat() { - case "json": - return "ibmjson" - case "basic": - return "idText" - default: - return "ibmjson" - } -} - -func formatSimple(datetime string, message string) string { - return fmt.Sprintf("%v %v\n", datetime, message) -} - -func getDebug() bool { - debug := os.Getenv("DEBUG") - if debug == "true" || debug == "1" { - return true - } - return false -} - -func configureLogger(name string) error { - var err error - f := getLogFormat() - d := getDebug() - switch f { - case "json": - log, err = logger.NewLogger(os.Stderr, d, true, name) - if err != nil { - return err - } - return nil - case "basic": - log, err = logger.NewLogger(os.Stderr, d, false, name) - if err != nil { - return err - } - return nil - default: - log, err = logger.NewLogger(os.Stdout, d, false, name) - if err != nil { - return err - } - return fmt.Errorf("invalid value for LOG_FORMAT: %v", f) - } -} diff --git a/cmd/runaceserver/main.go b/cmd/runaceserver/main.go deleted file mode 100644 index add15c8..0000000 --- a/cmd/runaceserver/main.go +++ /dev/null @@ -1,274 +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. -*/ - -// runaceserver initializes, creates and starts a queue manager, as PID 1 in a container -package main - -import ( - "errors" - "io/ioutil" - "os" - - "github.com/ot4i/ace-docker/common/designer" - "github.com/ot4i/ace-docker/internal/command" - "github.com/ot4i/ace-docker/internal/configuration" - iscommandsapi "github.com/ot4i/ace-docker/internal/isCommandsApi" - "github.com/ot4i/ace-docker/internal/metrics" - "github.com/ot4i/ace-docker/internal/name" - "github.com/ot4i/ace-docker/internal/trace" -) - -func doMain() error { - var integrationServerProcess command.BackgroundCmd - - name, nameErr := name.GetIntegrationServerName() - err := configureLogger(name) - if err != nil { - logTermination(err) - return err - } - if nameErr != nil { - logTermination(err) - return err - } - - accepted, err := checkLicense() - if err != nil { - logTerminationf("Error checking license acceptance: %v", err) - return err - } - if !accepted { - err = errors.New("License not accepted") - logTermination(err) - return err - } - - performShutdown := func() { - - metrics.StopMetricsGathering() - - // Stop watching the Force Flows Secret if the watcher has been created - if watcher != nil { - log.Print("Stopping watching the Force Flows HTTPS secret") - watcher.Close() - } - - log.Print("Stopping Integration Server") - stopIntegrationServer(integrationServerProcess) - log.Print("Integration Server stopped") - - checkLogs() - - iscommandsapi.StopCommandsAPIServer() - log.Print("Shutdown complete") - } - - restartIntegrationServer := func() error { - err := ioutil.WriteFile("/tmp/integration_server_restart.timestamp", []byte(""), 0755) - - if err != nil { - log.Print("RestartIntegrationServer - Creating restart file failed") - return err - } - - log.Print("RestartIntegrationServer - Stopping integration server") - stopIntegrationServer(integrationServerProcess) - log.Println("RestartIntegrationServer - Starting integration server") - - integrationServerProcess = startIntegrationServer() - err = integrationServerProcess.ReturnError - - if integrationServerProcess.ReturnError == nil { - log.Println("RestartIntegrationServer - Waiting for integration server") - err = waitForIntegrationServer() - } - - if err != nil { - logTermination(err) - performShutdown() - return err - } - - log.Println("RestartIntegrationServer - Integration server is ready") - - return nil - } - - // Start signal handler - signalControl := signalHandler(performShutdown) - - // Print out versioning information - logVersionInfo() - - runOnly := os.Getenv("ACE_RUN_ONLY") - if runOnly == "true" || runOnly == "1" { - log.Println("Run selected so skipping setup") - } else { - log.Println("Checking for valid working directory") - err = createWorkDir() - if err != nil { - logTermination(err) - performShutdown() - return err - } - - workdirShared := os.Getenv("WORKDIR-SHARED") - if workdirShared == "true" || workdirShared == "1" { - log.Println("Add symlink into shared workdir") - err = createWorkDirSymLink() - if err != nil { - logTermination(err) - performShutdown() - return err - } - } - - // Note: this will do nothing if there are no crs set in the environment - err = configuration.SetupConfigurationsFiles(log, "/home/aceuser") - if err != nil { - logTermination(err) - performShutdown() - return err - } - - err = initialIntegrationServerConfig() - if err != nil { - logTermination(err) - performShutdown() - return err - } - - log.Println("Validating flows deployed to the integration server before starting") - licenseToggles, err := designer.GetLicenseTogglesFromEnvironmentVariables() - if err != nil { - logTermination(err) - performShutdown() - return err - } - designer.InitialiseLicenseToggles(licenseToggles) - - err = designer.ValidateFlows(log, "/home/aceuser") - if err != nil { - logTermination(err) - performShutdown() - return err - } - - // Apply any WorkdirOverrides provided - err = applyWorkdirOverrides() - if err != nil { - logTermination(err) - performShutdown() - return err - } - - // Deploy Connector Service API - err = deployCSAPIFlows() - if err != nil { - logTermination(err) - performShutdown() - return err - } - - designerIntegrationFlows := os.Getenv("DESIGNER_INTEGRATION_FLOWS") - if designerIntegrationFlows == "true" { - err = deployIntegrationFlowResources() - if err != nil { - logTermination(err) - performShutdown() - return err - } - } - - forceflowbasicauth := os.Getenv("FORCEFLOWBASICAUTH") - if forceflowbasicauth == "true" || forceflowbasicauth == "1" { - err = forceflowbasicauthServerConfUpdate() - if err != nil { - logTermination(err) - performShutdown() - return err - } - } - - } - - setupOnly := os.Getenv("ACE_SETUP_ONLY") - if setupOnly == "true" || setupOnly == "1" { - log.Println("Setup only enabled so exiting now") - osExit(0) - } - - log.Println("Starting integration server") - integrationServerProcess = startIntegrationServer() - if integrationServerProcess.ReturnError != nil { - logTermination(integrationServerProcess.ReturnError) - return integrationServerProcess.ReturnError - } - - log.Println("Waiting for integration server to be ready") - err = waitForIntegrationServer() - if err != nil { - logTermination(err) - performShutdown() - return err - } - log.Println("Integration server is ready") - - enableMetrics := os.Getenv("ACE_ENABLE_METRICS") - if enableMetrics == "true" || enableMetrics == "1" { - go metrics.GatherMetrics(name, log) - } else { - log.Println("Metrics are disabled") - } - - log.Println("Starting integration server commands API server") - err = iscommandsapi.StartCommandsAPIServer(log, 7980, restartIntegrationServer) - - if err != nil { - log.Println("Failed to start isapi server " + err.Error()) - } else { - log.Println("Integration API started") - } - - log.Println("Tracing: Starting trace API server") - err = trace.StartServer(log, 7981) - if err != nil { - log.Println("Failed to start trace API server, you will not be able to retrieve trace through the ACE dashboard " + err.Error()) - } else { - log.Println("Tracing: Trace API server started") - } - - // Start reaping zombies from now on. - // Start this here, so that we don't reap any sub-processes created - // by this process (e.g. for crtmqm or strmqm) - signalControl <- startReaping - // Reap zombies now, just in case we've already got some - signalControl <- reapNow - // Wait for terminate signal - <-signalControl - - return nil -} - -var osExit = os.Exit - -func main() { - - err := doMain() - if err != nil { - osExit(1) - } -} diff --git a/cmd/runaceserver/main_internal_test.go b/cmd/runaceserver/main_internal_test.go deleted file mode 100644 index f3f6b79..0000000 --- a/cmd/runaceserver/main_internal_test.go +++ /dev/null @@ -1,61 +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 ( - "flag" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "testing" - - "github.com/ot4i/ace-docker/common/logger" -) - -var test *bool - -func init() { - test = flag.Bool("test", false, "Set to true when running tests for coverage") - log, _ = logger.NewLogger(os.Stdout, true, false, "test") -} - -// Test started when the test binary is started. Only calls main. -func TestSystem(t *testing.T) { - if *test { - var oldExit = osExit - defer func() { - osExit = oldExit - }() - - filename, ok := os.LookupEnv("EXIT_CODE_FILE") - if !ok { - filename = "/var/coverage/exitCode" - } else { - filename = filepath.Join("/var/coverage/", filename) - } - - osExit = func(rc int) { - // Write the exit code to a file instead - log.Printf("Writing exit code %v to file %v", strconv.Itoa(rc), filename) - err := ioutil.WriteFile(filename, []byte(strconv.Itoa(rc)), 0644) - if err != nil { - log.Print(err) - } - } - main() - } -} diff --git a/cmd/runaceserver/signals.go b/cmd/runaceserver/signals.go deleted file mode 100644 index 1383213..0000000 --- a/cmd/runaceserver/signals.go +++ /dev/null @@ -1,81 +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 ( - "os" - "os/signal" - "syscall" - - "golang.org/x/sys/unix" -) - -const ( - startReaping = iota - reapNow = iota -) - -func signalHandler(shutdownFunc func()) chan int { - control := make(chan int) - // Use separate channels for the signals, to avoid SIGCHLD signals swamping - // the buffer, and preventing other signals. - stopSignals := make(chan os.Signal, 2) - reapSignals := make(chan os.Signal, 2) - signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT) - go func() { - for { - select { - case sig := <-stopSignals: - log.Printf("Signal received: %v", sig) - signal.Stop(reapSignals) - signal.Stop(stopSignals) - shutdownFunc() - // One final reap - reapZombies() - close(control) - // End the goroutine - return - case <-reapSignals: - log.Debug("Received SIGCHLD signal") - reapZombies() - case job := <-control: - switch { - case job == startReaping: - // Add SIGCHLD to the list of signals we're listening to - log.Debug("Listening for SIGCHLD signals") - signal.Notify(reapSignals, syscall.SIGCHLD) - case job == reapNow: - reapZombies() - } - } - } - }() - return control -} - -// reapZombies reaps any zombie (terminated) processes now. -// This function should be called before exiting. -func reapZombies() { - for { - var ws unix.WaitStatus - pid, err := unix.Wait4(-1, &ws, unix.WNOHANG, nil) - // If err or pid indicate "no child processes" - if pid == 0 || err == unix.ECHILD { - return - } - log.Debugf("Reaped PID %v", pid) - } -} diff --git a/cmd/runaceserver/version.go b/cmd/runaceserver/version.go deleted file mode 100644 index 08d92fd..0000000 --- a/cmd/runaceserver/version.go +++ /dev/null @@ -1,100 +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 ( - "errors" - "regexp" - "strings" - "runtime" - - "github.com/ot4i/ace-docker/internal/command" -) - -var ( - // ImageCreated is the date the image was built - ImageCreated = "Not specified" -) - -func logDateStamp() { - log.Printf("Image created: %v", ImageCreated) -} - -func extractVersion(mqsiversion string) (string, error) { - versionRegex := regexp.MustCompile("(?sm).+BIP8996I: Version:\\s+(.*?)\\s?[\\r\\n].*") - version := versionRegex.FindStringSubmatch(mqsiversion) - if len(version) < 2 { - return "", errors.New("Failed to find version") - } - return version[1], nil -} - -func extractLevel(mqsiversion string) (string, error) { - levelRegex := regexp.MustCompile("(?sm).+BIP8998I: Code Level:\\s+(.*?)\\s?[\\r\\n].*") - level := levelRegex.FindStringSubmatch(mqsiversion) - if len(level) < 2 { - return "", errors.New("Failed to find level") - } - return level[1], nil -} - -func extractBuildType(mqsiversion string) (string, error) { - buildTypeRegex := regexp.MustCompile("(?sm).+BIP8999I: Build Type:\\s+(.*?)\\s[\\r\\n].*") - buildType := buildTypeRegex.FindStringSubmatch(mqsiversion) - if len(buildType) < 2 { - return "", errors.New("Failed to find build type") - } - return buildType[1], nil -} - -func logACEVersion() { - out, _, err := command.Run("ace_mqsicommand.sh", "service", "-v") - if err != nil { - log.Printf("Error calling mqsiservice. Output: %v Error: %v", strings.TrimSuffix(string(out), "\n"), err) - } - - version, err := extractVersion(out) - if err != nil { - log.Printf("Error getting ACE version: Output: %v Error: %v", strings.TrimSuffix(string(out), "\n"), err) - } - - level, err := extractLevel(out) - if err != nil { - log.Printf("Error getting ACE level: Output: %v Error: %v", strings.TrimSuffix(string(out), "\n"), err) - } - - buildType, err := extractBuildType(out) - if err != nil { - log.Printf("Error getting ACE build type: Output: %v Error: %v", strings.TrimSuffix(string(out), "\n"), err) - } - - log.Printf("ACE version: %v", version) - log.Printf("ACE level: %v", level) - log.Printf("ACE build type: %v", buildType) -} - -func logGoVersion() { - log.Printf("Go Version: %s", runtime.Version()) - log.Printf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH) -} - - -func logVersionInfo() { - logDateStamp() - logACEVersion() - logGoVersion() -} diff --git a/cmd/runaceserver/version_internal_test.go b/cmd/runaceserver/version_internal_test.go deleted file mode 100644 index d7fdd75..0000000 --- a/cmd/runaceserver/version_internal_test.go +++ /dev/null @@ -1,90 +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 ( - "testing" -) - -var versionTests = []struct { - in string - out string -}{ - {"BIPmsgs en_US\n Console CCSID=1208, ICU CCSID=1208\n Default codepage=UTF-8, in ascii=UTF-8\n JAVA console codepage name=UTF-8\n\nBIP8996I: Version: 11001 \nBIP8997I: Product: IBM App Connect Enterprise \nBIP8998I: Code Level: S000-L180810.12854 \nBIP8999I: Build Type: Production, 64 bit, amd64_linux_2 \n\nBIP8974I: Component: DFDL-C, Build ID: 20180710-2330, Version: 1.1.2.0 (1.1.2.0), Platform: linux_x86 64-bit, Type: production \n\nBIP8071I: Successful command completion.", "11001"}, -} - -var levelTests = []struct { - in string - out string -}{ - {"BIPmsgs en_US\n Console CCSID=1208, ICU CCSID=1208\n Default codepage=UTF-8, in ascii=UTF-8\n JAVA console codepage name=UTF-8\n\nBIP8996I: Version: 11001 \nBIP8997I: Product: IBM App Connect Enterprise \nBIP8998I: Code Level: S000-L180810.12854 \nBIP8999I: Build Type: Production, 64 bit, amd64_linux_2 \n\nBIP8974I: Component: DFDL-C, Build ID: 20180710-2330, Version: 1.1.2.0 (1.1.2.0), Platform: linux_x86 64-bit, Type: production \n\nBIP8071I: Successful command completion.", "S000-L180810.12854"}, - {"BIPmsgs en_US\n Console CCSID=1208, ICU CCSID=1208\n Default codepage=UTF-8, in ascii=UTF-8\n JAVA console codepage name=UTF-8\n\nBIP8996I: Version: 11001 \nBIP8997I: Product: IBM App Connect Enterprise \nBIP8998I: Code Level: S000-L180810.12854 Suffix \nBIP8999I: Build Type: Production, 64 bit, amd64_linux_2 \n\nBIP8974I: Component: DFDL-C, Build ID: 20180710-2330, Version: 1.1.2.0 (1.1.2.0), Platform: linux_x86 64-bit, Type: production \n\nBIP8071I: Successful command completion.", "S000-L180810.12854 Suffix"}, -} - -var buildTypeTests = []struct { - in string - out string -}{ - {"BIPmsgs en_US\n Console CCSID=1208, ICU CCSID=1208\n Default codepage=UTF-8, in ascii=UTF-8\n JAVA console codepage name=UTF-8\n\nBIP8996I: Version: 11001 \nBIP8997I: Product: IBM App Connect Enterprise \nBIP8998I: Code Level: S000-L180810.12854 \nBIP8999I: Build Type: Production, 64 bit, amd64_linux_2 \n\nBIP8974I: Component: DFDL-C, Build ID: 20180710-2330, Version: 1.1.2.0 (1.1.2.0), Platform: linux_x86 64-bit, Type: production \n\nBIP8071I: Successful command completion.", "Production, 64 bit, amd64_linux_2"}, -} - -func TestExtractVersion(t *testing.T) { - for _, table := range versionTests { - out, err := extractVersion(table.in) - if err != nil { - t.Errorf("extractVersion(%v) - unexpected error %v", table.in, err) - } - if out != table.out { - t.Errorf("extractVersion(%v) - expected %v, got %v", table.in, table.out, out) - } - } - _, err := extractVersion("xxx") - if err == nil { - t.Error("extractVersion(xxx) - expected an error but didn't get one") - } -} - -func TestExtractLevel(t *testing.T) { - for _, table := range levelTests { - out, err := extractLevel(table.in) - if err != nil { - t.Errorf("extractLevel(%v) - unexpected error %v", table.in, err) - } - if out != table.out { - t.Errorf("extractLevel(%v) - expected %v, got %v", table.in, table.out, out) - } - } - _, err := extractLevel("xxx") - if err == nil { - t.Error("extractLevel(xxx) - expected an error but didn't get one") - } -} - -func TestExtractBuildType(t *testing.T) { - for _, table := range buildTypeTests { - out, err := extractBuildType(table.in) - if err != nil { - t.Errorf("extractBuildType(%v) - unexpected error %v", table.in, err) - } - if out != table.out { - t.Errorf("extractBuildType(%v) - expected %v, got %v", table.in, table.out, out) - } - } - _, err := extractBuildType("xxx") - if err == nil { - t.Error("extractBuildType(xxx) - expected an error but didn't get one") - } -} diff --git a/common/contentserver/bar.go b/common/contentserver/bar.go deleted file mode 100644 index 95ae542..0000000 --- a/common/contentserver/bar.go +++ /dev/null @@ -1,78 +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 contentserver - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "io" - "net/http" - - "github.com/ot4i/ace-docker/common/logger" -) - -var loadX509KeyPair = tls.LoadX509KeyPair -var newRequest = http.NewRequest - -var newHTTPClient = func(rootCAs *x509.CertPool, cert tls.Certificate, serverName string) Client { - return &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: rootCAs, - Certificates: []tls.Certificate{cert}, - ServerName: serverName, - }, - }, - } -} - -func GetBAR(url string, serverName string, token string, contentServerCACert []byte, contentServerCert string, contentServerKey string, log logger.LoggerInterface) (io.ReadCloser, error) { - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(contentServerCACert) - - // If provided read the key pair to create certificate - cert, err := loadX509KeyPair(contentServerCert, contentServerKey) - if err != nil { - if contentServerCert != "" && contentServerKey != "" { - log.Errorf("Error reading Certificates: %s", err) - return nil, errors.New("Error reading Certificates") - } - } else { - log.Printf("Using certs for mutual auth") - } - - client := newHTTPClient(caCertPool, cert, serverName) - - request, err := newRequest("GET", url, nil) - if err != nil { - return nil, err - } - - request.Header.Set("x-ibm-ace-directory-token", token) - response, err := client.Do(request) - if err != nil { - return nil, err - } - - if response.StatusCode != 200 { - log.Printf("Call to retrieve BAR file from content server failed with response code: %v", response.StatusCode) - return nil, errors.New("HTTP status : " + fmt.Sprint(response.StatusCode) + ". Unable to get BAR file from content server.") - } - - return response.Body, nil -} diff --git a/common/contentserver/bar_test.go b/common/contentserver/bar_test.go deleted file mode 100644 index 2cdc629..0000000 --- a/common/contentserver/bar_test.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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 contentserver - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "errors" - "io" - "io/ioutil" - "net/http" - "os" - "strings" - - "github.com/ot4i/ace-docker/common/logger" - - "testing" - - "github.com/stretchr/testify/assert" -) - -var testLogger, _ = logger.NewLogger(os.Stdout, true, true, "test") - -func TestGetBAR(t *testing.T) { - url := "" - serverName := "" - token := "" - contentServerCACert := []byte{} - - oldNewHTTPClient := newHTTPClient - newHTTPClient = func(*x509.CertPool, tls.Certificate, string) Client { - return &MockClient{Client: &http.Client{Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }}} - } - - t.Run("When not able to load the content server cert and key pair, it returns an error", func(t *testing.T) { - oldLoadX509KeyPair := loadX509KeyPair - loadX509KeyPair = func(string, string) (tls.Certificate, error) { - return tls.Certificate{}, errors.New("Fail to load key pair") - } - - t.Run("When the server cert and key are not empty strings, it fails", func(t *testing.T) { - _, err := GetBAR(url, serverName, token, contentServerCACert, "contentServerCert", "contentServerKey", testLogger) - assert.Error(t, err) - }) - - loadX509KeyPair = oldLoadX509KeyPair - }) - - t.Run("When failing to create the http requestv", func(t *testing.T) { - oldNewRequest := newRequest - newRequest = func(string, string, io.Reader) (*http.Request, error) { - return nil, errors.New("Fail to create new request") - } - - _, err := GetBAR(url, serverName, token, contentServerCACert, "", "", testLogger) - assert.Error(t, err) - - newRequest = oldNewRequest - }) - - t.Run("When failing to make the client call, it returns an error", func(t *testing.T) { - setDoResponse(nil, errors.New("Fail to create new request")) - _, err := GetBAR(url, serverName, token, contentServerCACert, "", "", testLogger) - assert.Error(t, err) - resetDoResponse() - }) - - // TODO: should this return an error? - t.Run("When the client call reponds with a non 200, it does return an error", func(t *testing.T) { - setDoResponse(&http.Response{StatusCode: 500}, nil) - _, err := GetBAR(url, serverName, token, contentServerCACert, "", "", testLogger) - assert.Error(t, err) - resetDoResponse() - }) - - t.Run("When the client call reponds with a 200, it returns the body", func(t *testing.T) { - testReadCloser := ioutil.NopCloser(strings.NewReader("test")) - setDoResponse(&http.Response{StatusCode: 200, Body: testReadCloser}, nil) - body, err := GetBAR(url, serverName, token, contentServerCACert, "", "", testLogger) - assert.NoError(t, err) - buf := new(bytes.Buffer) - buf.ReadFrom(body) - assert.Equal(t, "test", buf.String()) - resetDoResponse() - }) - newHTTPClient = oldNewHTTPClient -} diff --git a/common/contentserver/mock_client.go b/common/contentserver/mock_client.go deleted file mode 100644 index 8d513f2..0000000 --- a/common/contentserver/mock_client.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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 contentserver - -import ( - "net/http" -) - -type Client interface { - Do(*http.Request) (*http.Response, error) -} - -type MockClient struct { - Client *http.Client -} - -var response *http.Response = nil -var err error = nil -func (m *MockClient) Do(request *http.Request) (*http.Response, error) { - if response != nil || err != nil{ - return response, err - } - return m.Client.Do(request) -} - -func setDoResponse(mockResponse *http.Response, mockError error) { - response = mockResponse - err = mockError -} - -func resetDoResponse() { - response = nil - err = nil -} \ No newline at end of file diff --git a/common/designer/flow_validation.go b/common/designer/flow_validation.go deleted file mode 100644 index df2f0d4..0000000 --- a/common/designer/flow_validation.go +++ /dev/null @@ -1,250 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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 designer contains code for the designer specific logic -package designer - -import ( - "os" - "io" - "io/ioutil" - "strings" - "gopkg.in/yaml.v2" - - "github.com/ot4i/ace-docker/internal/command" - "github.com/ot4i/ace-docker/common/logger" -) - -var runAsUser = command.RunAsUser -var osOpen = os.Open -var osCreate = os.Create -var ioCopy = io.Copy -var rename = os.Rename -var removeAll = os.RemoveAll -var remove = os.Remove -var mkdir = os.Mkdir -var readDir = ioutil.ReadDir - -type flowDocument struct { - Integration integration `yaml:"integration"` -} - -type integration struct { - TriggerInterfaces map[string]flowInterface `yaml:"trigger-interfaces"` - ActionInterfaces map[string]flowInterface `yaml:"action-interfaces"` -} - -type flowInterface struct { - ConnectorType string `yaml:"connector-type"` -} - -// replaceFlow replaces the resources associated with a deployed designer flow -// with the unpacked generic invalid BAR file -var replaceFlow = func (flow string, log logger.LoggerInterface, basedir string) error { - runFolder := basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" - - // Delete the /run/PolicyProject folder - err := removeAll(runFolder + string(os.PathSeparator) + flow + "PolicyProject") - if err != nil { - log.Errorf("Error checking for the flow's policy project folder: %v", err) - return err - } - - // Rename the flow folder _invalid_license - newFlowName := flow + "_invalid_license" - err = rename(runFolder + string(os.PathSeparator) + flow, runFolder + string(os.PathSeparator) + newFlowName) - if err != nil { - log.Errorf("Error renaming the flow's folder: %v", err) - return err - } - flow = newFlowName - - // Delete the /run//gen folder - err = removeAll(runFolder + string(os.PathSeparator) + flow + string(os.PathSeparator) + "gen") - if err != nil { - log.Errorf("Error deleting the flow's /gen folder: %v", err) - return err - } - - // Create a new /run//gen folder - err = mkdir(runFolder + string(os.PathSeparator) + flow + string(os.PathSeparator) + "gen", 0777) - if err != nil { - log.Errorf("Error creating the flow's new /gen folder: %v", err) - return err - } - - tempFolder := basedir + string(os.PathSeparator) + "temp" - // Copy the contents of /temp/gen into /run//gen - invalidFlowResourcesList, err := readDir(tempFolder + string(os.PathSeparator) + "gen") - if err != nil { - log.Errorf("Error checking for the invalid flow's /gen folder: %v", err) - return err - } - for _, invalidFlowResource := range invalidFlowResourcesList { - err = copy(tempFolder + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + invalidFlowResource.Name(), runFolder + string(os.PathSeparator) + flow + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + invalidFlowResource.Name(), log) - if err != nil { - log.Errorf("Error copying resource %v from the flow's /gen folder: %v", invalidFlowResource.Name(), err) - return err - } - } - - // Remove the .msgflow, .subflow and, restapi.descriptor from /run/ - flowResourcesList, err := readDir(runFolder + string(os.PathSeparator) + flow) - if err != nil { - log.Errorf("Error checking for the flow's folder: %v", err) - return err - } - for _, flowResource := range flowResourcesList { - if strings.Contains(flowResource.Name(), ".msgflow") || strings.Contains(flowResource.Name(), ".subflow") || flowResource.Name() == "restapi.descriptor" { - err = remove(runFolder+ string(os.PathSeparator) + flow + string(os.PathSeparator) + flowResource.Name()) - if err != nil { - log.Errorf("Error deleting resource %v from the flow's folder: %v", flowResource.Name(), err) - return err - } - } - } - - // Replace restapi.descriptor with application.descriptor - err = copy(tempFolder + string(os.PathSeparator) + "application.descriptor", runFolder + string(os.PathSeparator) + flow + string(os.PathSeparator) + "application.descriptor", log) - if err != nil { - log.Errorf("Error copying resource restapi.descriptor to application.descriptor: %v", err) - return err - } - return nil -} - -// cleanupInvalidBarResources deletes the unpacked generic invalid BAR file -// to ensure there's no unknown flows deployed to a user's instnace -func cleanupInvalidBarResources(basedir string) error { - return os.RemoveAll(basedir + string(os.PathSeparator) + "temp") -} - -// getConnectorLicenseToggle computes the license toggle name from the connector -func getConnectorLicenseToggleName(name string) string { - return "connector-" + name -} - -// findDisabledConnectorInFlow returns the first disabled connector it finds -// if it doesn't find a disabled connector, it returns an empty string -var findDisabledConnectorInFlow = func (flowDocument flowDocument, log logger.LoggerInterface) string { - disabledConnectors := make([]string, 0) - - // read the connector-type field under each interface - // and check if the license toggle for that connector is enabled - findDisabledConnector := func(interfaces map[string]flowInterface) { - for _, i := range interfaces { - connector := i.ConnectorType - if connector != "" { - log.Printf("Checking if connector %v is supported under the current license.", connector) - if !isLicenseToggleEnabled(getConnectorLicenseToggleName(connector)) { - disabledConnectors = append(disabledConnectors, connector) - } - } - } - } - - findDisabledConnector(flowDocument.Integration.TriggerInterfaces) - findDisabledConnector(flowDocument.Integration.ActionInterfaces) - - return strings.Join(disabledConnectors[:], ", ") -} - -// IsFlowValid checks if a single flow is valid -var IsFlowValid = func(log logger.LoggerInterface, flow string, flowFile []byte) (bool, error) { - var flowDocument flowDocument - err := yaml.Unmarshal(flowFile, &flowDocument) - if err != nil { - log.Errorf("Error processing running flow in folder %v: %v", flow, err) - return false, err - } - - disabledConnectors := findDisabledConnectorInFlow(flowDocument, log) - if disabledConnectors != "" { - log.Errorf("Flow %v contains one or more connectors that aren't supported under the current license. Please update your license to enable this flow to run. The unsupported connectors are: %v.", flow, disabledConnectors) - } - return disabledConnectors == "", nil -} - -// ValidateFlows checks if the flows in the /run directory are valid -// by making sure all the connectors used by a flow are supported under the license -// if invalid, then it replaces a flow with a generic invalid one, which fails at startup -// if valid, it doesn't do anything -func ValidateFlows(log logger.LoggerInterface, basedir string) error { - // at this point the /run directory should have been created - log.Println("Processing running flows in folder /home/aceuser/ace-server/run") - runFileList, err := ioutil.ReadDir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run") - if err != nil { - log.Errorf("Error checking for the /run folder: %v", err) - return err - } - - for _, file := range runFileList { - // inside the /run directory there are folders corresponding to running toolkit and designer flows - // designer flows will have two folders: /run/ and /run/PolicyProject - // the yaml of the designer flow is the /run/ folder - flow := file.Name() - if file.IsDir() && !strings.Contains(flow, "PolicyProject") && dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + "PolicyProject") { - log.Printf("Processing running flow with name %v", flow) - flowFile, err := ioutil.ReadFile(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + flow + ".yaml") - if err != nil { - log.Errorf("Error processing running flow in folder %v: %v", flow, err) - return err - } - - valid, err := IsFlowValid(log, flow, flowFile) - if err != nil { - return err - } - if !valid { - err = replaceFlow(flow, log, basedir) - if err != nil { - log.Errorf("Error replacing running flow in folder %v: %v", flow, err) - return err - } - } - } - } - - return cleanupInvalidBarResources(basedir) -} - -// copy copies from src to dest -var copy = func (src string, dest string, log logger.LoggerInterface) error { - source, err := osOpen(src) - if err != nil { - log.Errorf("Error opening the source file %v: %v", src, err) - return err - } - defer source.Close() - - destination, err := osCreate(dest) - if err != nil { - log.Errorf("Error creating the destination file %v: %v", dest, err) - return err - } - defer destination.Close() - _, err = ioCopy(destination, source) - return err -} - -// dirExist cheks if a directory exists at the given path -var dirExists = func (path string) bool { - _, err := os.Stat(path) - if err == nil { - return true - } - return !os.IsNotExist(err) -} diff --git a/common/designer/flow_validation_test.go b/common/designer/flow_validation_test.go deleted file mode 100644 index 78159ee..0000000 --- a/common/designer/flow_validation_test.go +++ /dev/null @@ -1,679 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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 designer - -import ( - "os" - "errors" - "io" - "io/ioutil" - - "github.com/ot4i/ace-docker/common/logger" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "testing" -) - -var testLogger, _ = logger.NewLogger(os.Stdout, true, true, "test") - -func TestGetConnectorLicenseToggleName(t *testing.T) { - require.Equal(t, "connector-test", getConnectorLicenseToggleName("test")) -} - -func TestFindDisabledConnectorInFlow(t *testing.T) { - oldIsLicenseToggleEnabled := isLicenseToggleEnabled - isLicenseToggleEnabled = func(toggle string) bool { - if toggle == "connector-test" { - return true - } else { - return false - } - } - t.Run("When there are no unsupported connectors", func(t *testing.T) { - testFlowDocument := flowDocument { - integration { - map[string]flowInterface { - "trigger-interface1": { - ConnectorType: "test", - }, - }, - map[string]flowInterface { - "action-interface1": { - ConnectorType: "test", - }, - }, - }, - } - require.Equal(t, "", findDisabledConnectorInFlow(testFlowDocument, testLogger)) - }) - - t.Run("When there are unsupported connectors in the trigger interface", func(t *testing.T) { - testFlowDocument := flowDocument { - integration { - map[string]flowInterface { - "trigger-interface1": { - ConnectorType: "test", - }, - "trigger-interface2": { - ConnectorType: "foo", - }, - }, - map[string]flowInterface { - "action-interface2": { - ConnectorType: "test", - }, - }, - }, - } - disabledConnectors := findDisabledConnectorInFlow(testFlowDocument, testLogger) - require.Equal(t, "foo", disabledConnectors) - }) - - t.Run("When there are unsupported connectors in the action interface", func(t *testing.T) { - testFlowDocument := flowDocument { - integration { - map[string]flowInterface { - "trigger-interface1": { - ConnectorType: "test", - }, - }, - map[string]flowInterface { - "action-interface1": { - ConnectorType: "test", - }, - "action-interface2": { - ConnectorType: "bar", - }, - }, - }, - } - disabledConnectors := findDisabledConnectorInFlow(testFlowDocument, testLogger) - require.Equal(t, "bar", disabledConnectors) - }) - - t.Run("When there are unsupported connectors in both the trigger interface and the action interface", func(t *testing.T) { - testFlowDocument := flowDocument { - integration { - map[string]flowInterface { - "trigger-interface1": { - ConnectorType: "test", - }, - "trigger-interface2": { - ConnectorType: "foo", - }, - }, - map[string]flowInterface { - "action-interface1": { - ConnectorType: "test", - }, - "action-interface2": { - ConnectorType: "bar", - }, - }, - }, - } - disabledConnectors := findDisabledConnectorInFlow(testFlowDocument, testLogger) - require.Equal(t, "foo, bar", disabledConnectors) - }) - - isLicenseToggleEnabled = oldIsLicenseToggleEnabled -} - -func TestCopy(t *testing.T) { - basedir, err := os.Getwd() - require.NoError(t, err) - - src := basedir + string(os.PathSeparator) + "src.txt" - dst := basedir + string(os.PathSeparator) + "dst.txt" - - t.Run("When failing to open the src file", func (t *testing.T) { - oldOsOpen := osOpen - osOpen = func (string) (*os.File, error) { - return nil, errors.New("Test") - } - err = copy(src, dst, testLogger) - require.Error(t, err) - osOpen = oldOsOpen - }) - - t.Run("When succeeding to open the src file", func (t *testing.T) { - file, err := os.Create(src) - assert.NoError(t, err) - _, err = file.WriteString("src string") - assert.NoError(t, err) - - t.Run("When failing to create the dst file", func (t *testing.T) { - oldOsCreate := osCreate - osCreate = func (string) (*os.File, error) { - return nil, errors.New("Test") - } - - err = copy(src, dst, testLogger) - assert.Error(t, err) - osCreate = oldOsCreate - }) - - t.Run("When succeeding to create the dst file", func (t *testing.T) { - t.Run("When the copying fails", func (t *testing.T) { - oldIoCopy := ioCopy - ioCopy = func (io.Writer, io.Reader) (int64, error) { - return 0, errors.New("Test") - } - - err = copy(src, dst, testLogger) - assert.Error(t, err) - ioCopy = oldIoCopy - }) - - t.Run("When the copying succeeds", func (t *testing.T) { - err = copy(src, dst, testLogger) - assert.NoError(t, err) - - contents, err := ioutil.ReadFile(dst) - assert.NoError(t, err) - assert.Equal(t, "src string", string(contents)) - }) - }) - }) - - err = os.Remove(src) - assert.NoError(t, err) - err = os.Remove(dst) - assert.NoError(t, err) -} - -func TestReplaceFlow(t *testing.T) { - basedir, err := os.Getwd() - require.NoError(t, err) - flow := "Test" - - // setup folders - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server", 0777) - assert.NoError(t, err) - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run", 0777) - assert.NoError(t, err) - - t.Run("When deleting the /run/PolicyProject folder fails", func (t *testing.T) { - oldRemoveAll := removeAll - removeAll = func (string) error { - return errors.New("Fail delete /run/PolicyProject") - } - - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - - removeAll = oldRemoveAll - }) - - t.Run("When deleting the /run/PolicyProject folder does not fail", func (t *testing.T) { - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + "PolicyProject", 0777) - assert.NoError(t, err) - - t.Run("When renaming the /run/ folder fails", func (t *testing.T) { - oldRename := rename - rename = func (string, string) error { - return errors.New("Fail rename /run/") - } - - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - - rename = oldRename - }) - - t.Run("When renaming the /run/ folder succeeds", func (t *testing.T) { - t.Run("When deleting the /run//gen folder fails", func (t *testing.T) { - oldRemoveAll := removeAll - count := 0 - removeAll = func (string) error { - if count == 0 { - return nil - } - count ++; - return errors.New("Fail delete /run//gen") - } - - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - - removeAll = oldRemoveAll - }) - - t.Run("When deleting the /run//gen folder succeeds", func (t *testing.T) { - var setupFlow = func () { - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow, 0777) - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "test.msgflow") - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "test.subflow") - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "restapi.descriptor") - assert.NoError(t, err) - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "gen", 0777) - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + "valid.msgflow") - assert.NoError(t, err) - } - - var cleanupFlow = func () { - err = os.RemoveAll(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + "_invalid_license") - assert.NoError(t, err) - } - - t.Run("When recreating the /run//gen folder fails", func (t *testing.T) { - oldMkdir:= mkdir - mkdir = func (string, os.FileMode) error { - return errors.New("Fail recreate /run//gen") - } - - setupFlow() - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - cleanupFlow() - - mkdir = oldMkdir - }) - - t.Run("When recreating the /run//gen folder succeeds", func (t *testing.T) { - t.Run("When reading the /temp/gen folder fails", func (t *testing.T) { - oldReadDir := readDir - readDir = func(string) ([]os.FileInfo, error) { - return nil, errors.New("Fail read /temp/gen") - } - setupFlow() - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - cleanupFlow() - readDir = oldReadDir - }) - - t.Run("When reading the /temp/gen folder succeeds", func (t *testing.T) { - err = os.Mkdir(basedir + string(os.PathSeparator) + "temp", 0777) - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "temp" + string(os.PathSeparator) + "application.descriptor") - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "temp" + string(os.PathSeparator) + "invalid.flow") - assert.NoError(t, err) - err = os.Mkdir(basedir + string(os.PathSeparator) + "temp" + string(os.PathSeparator) + "gen", 0777) - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "temp" + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + "invalid.msgflow") - assert.NoError(t, err) - - oldCopy := copy - t.Run("When copying the files under /temp/gen to /run//gen fails", func(t *testing.T) { - setupFlow() - copy = func (string, string, logger.LoggerInterface) error { - return errors.New("Fail copy /temp/gen to /run//gen") - } - - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - - cleanupFlow() - copy = oldCopy - }) - - t.Run("When copying the files under /temp/gen to /run//gen succeeds", func(t *testing.T) { - t.Run("When reading the / folder fails", func (t *testing.T) { - oldReadDir := readDir - count := 0 - readDir = func(string) ([]os.FileInfo, error) { - if count == 0 { - return nil, nil - } - count++; - return nil, errors.New("Fail read /") - } - - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - - readDir = oldReadDir - }) - - t.Run("When reading the / folder succeeds", func (t *testing.T) { - t.Run("When removing .msgflow, .subflow and, restapi.descriptor from /run/ fails", func(t *testing.T) { - oldRemove := remove - remove = func (string) error { - return errors.New("Fail removing .msgflow, .subflow and, restapi.descriptor") - } - - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - - remove = oldRemove - }) - - t.Run("When removing .msgflow, .subflow and, restapi.descriptor from /run/ succeeds", func(t *testing.T) { - t.Run("When replacing restapi.descriptor with application.descriptor fails", func(t *testing.T) { - copy = func (src string, dst string, logger logger.LoggerInterface) error { - return errors.New("Fail copying .msgflow, .subflow and, restapi.descriptor") - } - setupFlow() - - err := replaceFlow(flow, testLogger, basedir) - require.Error(t, err) - - cleanupFlow() - copy = oldCopy - }) - - t.Run("When replacing restapi.descriptor with application.descriptor succeeds", func(t *testing.T) { - setupFlow() - assert.True(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + "valid.msgflow")) - assert.False(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + "invalid.msgflow")) - - assert.True(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "restapi.descriptor")) - assert.False(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + string(os.PathSeparator) + "application.descriptor")) - - err := replaceFlow(flow, testLogger, basedir) - require.NoError(t, err) - - assert.False(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + "_invalid_license" + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + "valid.msgflow")) - assert.True(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + "_invalid_license" + string(os.PathSeparator) + "gen" + string(os.PathSeparator) + "invalid.msgflow")) - - assert.False(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + "_invalid_license" + string(os.PathSeparator) + "restapi.descriptor")) - assert.True(t, dirExists(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + flow + "_invalid_license" + string(os.PathSeparator) + "application.descriptor")) - - cleanupFlow() - }) - }) - }) - }) - copy = oldCopy - }) - }) - }) - - // rename = oldRename - }) - - err = os.RemoveAll(basedir + string(os.PathSeparator) + "ace-server") - assert.NoError(t, err) - err = os.RemoveAll(basedir + string(os.PathSeparator) + "temp") - assert.NoError(t, err) - }) -} - -func TestCleanupInvalidBarResources(t *testing.T) { - basedir, err := os.Getwd() - require.NoError(t, err) - - t.Run("When the directory exists", func(t *testing.T) { - // setup folder - err = os.Mkdir(basedir + string(os.PathSeparator) + "temp", 0777) - assert.NoError(t, err) - _, err = os.Create(basedir + string(os.PathSeparator) + "temp" + string(os.PathSeparator) + "test.bar") - assert.NoError(t, err) - - // test function - assert.True(t, dirExists(basedir + string(os.PathSeparator) + "temp" )) - err = cleanupInvalidBarResources(basedir) - assert.NoError(t, err) - assert.False(t, dirExists(basedir + string(os.PathSeparator) + "temp")) - }) - - t.Run("When the directory does not exist", func(t *testing.T) { - assert.False(t, dirExists(basedir + string(os.PathSeparator) + "temp")) - err := cleanupInvalidBarResources(basedir) - assert.NoError(t, err) - assert.False(t, dirExists(basedir + string(os.PathSeparator) + "temp")) - }) -} - -func TestIsFlowValid(t *testing.T) { - t.Run("When failing to parse flow", func (t *testing.T) { - findDisabledConnectorInFlowCalls := 0 - findDisabledConnectorInFlow = func (flowDocument, logger.LoggerInterface) string { - findDisabledConnectorInFlowCalls++; - return "" - } - - _, err := IsFlowValid(testLogger, "Test", []byte("foo")) - assert.Error(t, err) - - assert.Equal(t, 0, findDisabledConnectorInFlowCalls) - }) - - t.Run("When findDisabledConnectorInFlow does not return a connector", func (t *testing.T) { - findDisabledConnectorInFlowCalls := 0 - findDisabledConnectorInFlow = func (flowDocument, logger.LoggerInterface) string { - findDisabledConnectorInFlowCalls++; - return "" - } - - valid, err := IsFlowValid(testLogger, "Test", []byte(flowDocumentYAML)) - assert.NoError(t, err) - assert.True(t, valid) - - assert.Equal(t, 1, findDisabledConnectorInFlowCalls) - }) - - t.Run("When findDisabledConnectorInFlow does return a connector", func (t *testing.T) { - findDisabledConnectorInFlowCalls := 0 - findDisabledConnectorInFlow = func (flowDocument, logger.LoggerInterface) string { - findDisabledConnectorInFlowCalls++; - return "test" - } - - valid, err := IsFlowValid(testLogger, "Test", []byte(flowDocumentYAML)) - assert.NoError(t, err) - assert.False(t, valid) - - assert.Equal(t, 1, findDisabledConnectorInFlowCalls) - }) -} - -func TestValidateFlow(t *testing.T) { - basedir, err := os.Getwd() - require.NoError(t, err) - - t.Run("When the folders are not setup", func (t *testing.T) { - err := ValidateFlows(testLogger, basedir) - require.Error(t, err) - }) - - t.Run("When the folders are setup", func (t *testing.T) { - // setup folders - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server", 0777) - assert.NoError(t, err) - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run", 0777) - assert.NoError(t, err) - // designer flow - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + "Test", 0777) - assert.NoError(t, err) - err = os.Mkdir(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + "TestPolicyProject", 0777) - assert.NoError(t, err) - - file, err := os.Create(basedir + string(os.PathSeparator) + "ace-server" + string(os.PathSeparator) + "run" + string(os.PathSeparator) + "Test" + string(os.PathSeparator) + "Test.yaml") - assert.NoError(t, err) - _, err = file.WriteString(flowDocumentYAML) - assert.NoError(t, err) - file.Close() - - oldIsFlowValid := IsFlowValid - - t.Run("When IsFlowValid returns an error", func (t *testing.T) { - IsFlowValidCalls := 0 - IsFlowValid = func (logger.LoggerInterface, string, []byte) (bool, error) { - IsFlowValidCalls++; - return true, errors.New("IsFlowValid fails") - } - - oldreplaceFlowNumCalls := 0 - oldreplaceFlow := replaceFlow - replaceFlow = func (string, logger.LoggerInterface, string) error { - oldreplaceFlowNumCalls++ - return nil - } - - err := ValidateFlows(testLogger, basedir) - assert.Error(t, err) - - assert.Equal(t, 1, IsFlowValidCalls) - assert.Equal(t, 0, oldreplaceFlowNumCalls) - - replaceFlow = oldreplaceFlow - }) - - t.Run("When IsFlowValid returns true", func (t *testing.T) { - // setup the toolkit flow every time, since it gets deleted at the end of the function - err = os.Mkdir(basedir + string(os.PathSeparator) + "temp", 0777) - assert.NoError(t, err) - - IsFlowValidCalls := 0 - IsFlowValid = func (logger.LoggerInterface, string, []byte) (bool, error) { - IsFlowValidCalls++; - return true, nil - } - - oldreplaceFlowNumCalls := 0 - oldreplaceFlow := replaceFlow - replaceFlow = func (string, logger.LoggerInterface, string) error { - oldreplaceFlowNumCalls++ - return nil - } - - err := ValidateFlows(testLogger, basedir) - assert.NoError(t, err) - - assert.Equal(t, 1, IsFlowValidCalls) - assert.Equal(t, 0, oldreplaceFlowNumCalls) - - replaceFlow = oldreplaceFlow - }) - - t.Run("When IsFlowValid returns false", func (t *testing.T) { - // setup the toolkit flow every time, since it gets deleted at the end of the function - err = os.Mkdir(basedir + string(os.PathSeparator) + "temp", 0777) - assert.NoError(t, err) - - IsFlowValidCalls := 0 - IsFlowValid = func (logger.LoggerInterface, string, []byte) (bool, error) { - IsFlowValidCalls++; - return false, nil - } - - t.Run("When replaceFlow fails", func (t *testing.T) { - IsFlowValidCalls = 0 - oldreplaceFlowNumCalls := 0 - oldreplaceFlow := replaceFlow - replaceFlow = func (string, logger.LoggerInterface, string) error { - oldreplaceFlowNumCalls++ - return errors.New("Test") - } - - err := ValidateFlows(testLogger, basedir) - assert.Error(t, err) - - assert.Equal(t, 1, IsFlowValidCalls) - assert.Equal(t, 1, oldreplaceFlowNumCalls) - replaceFlow = oldreplaceFlow - }) - - t.Run("When replaceFlow does not fail", func (t *testing.T) { - IsFlowValidCalls = 0 - oldreplaceFlowNumCalls := 0 - oldreplaceFlow := replaceFlow - replaceFlow = func (string, logger.LoggerInterface, string) error { - oldreplaceFlowNumCalls++ - return nil - } - - err := ValidateFlows(testLogger, basedir) - assert.NoError(t, err) - - assert.Equal(t, 1, IsFlowValidCalls) - assert.Equal(t, 1, oldreplaceFlowNumCalls) - replaceFlow = oldreplaceFlow - }) - }) - - IsFlowValid = oldIsFlowValid - - // cleanup folders - err = os.RemoveAll(basedir + string(os.PathSeparator) + "ace-server") - assert.NoError(t, err) - }) -} - -var flowDocumentYAML string = `$integration: 'http://ibm.com/appconnect/integration/v2/integrationFile' -integration: - type: api - trigger-interfaces: - trigger-interface-1: - triggers: - retrieveTest: - assembly: - $ref: '#/integration/assemblies/assembly-1' - input-context: - data: test - output-context: - data: test - options: - resources: - - business-object: test - model: - $ref: '#/models/test' - triggers: - retrieve: retrieveTest - type: api-trigger - action-interfaces: - action-interface-1: - type: api-action - business-object: mail - connector-type: gmail - account-name: Account 1 - actions: - CREATE: {} - assemblies: - assembly-1: - assembly: - execute: - - create-action: - name: Gmail Create email - target: - $ref: '#/integration/action-interfaces/action-interface-1' - map: - mappings: - - To: - template: test@gmail.com - $map: 'http://ibm.com/appconnect/map/v1' - input: - - variable: api - $ref: '#/trigger/api/parameters' - - response: - name: response-1 - reply-maps: - - title: test successfully retrieved - status-code: '200' - map: - $map: 'http://ibm.com/appconnect/map/v1' - input: - - variable: api - $ref: '#/trigger/api/parameters' - - variable: GmailCreateemail - $ref: '#/node-output/Gmail Create email/response/payload' - mappings: [] - input: - - variable: api - $ref: '#/trigger/api/parameters' - - variable: GmailCreateemail - $ref: '#/node-output/Gmail Create email/response/payload' - name: Untitled API 1 -models: {}` \ No newline at end of file diff --git a/common/designer/license_toggles.go b/common/designer/license_toggles.go deleted file mode 100644 index 54c6b3f..0000000 --- a/common/designer/license_toggles.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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 designer contains code for the designer specific logic -package designer - -import ( - "os" - "encoding/json" -) - -var globalLicenseToggles map[string]bool - -// ConvertAppConnectLicenseToggleEnvironmentVariable converts the string representation of the environment variable -// into a : representation -func ConvertAppConnectLicenseToggleEnvironmentVariable (environmentVariable string, licenseToggles map[string]bool) error { - var licenseTogglesValues map[string]int - if environmentVariable != "" { - err := json.Unmarshal([]byte(environmentVariable), &licenseTogglesValues) - if err != nil { - return err - } - } - - for licenseToggle, value := range licenseTogglesValues { - if value == 1 { - licenseToggles[licenseToggle] = true - } else { - licenseToggles[licenseToggle] = false - } - } - return nil -} -// GetLicenseTogglesFromEnvironmentVariables reads the APP_CONNECT_LICENSE_TOGGLES and APP_CONNECT_LICENSE_TOGGLES_OVERRIDE environment variables -// it sets the license toggles to APP_CONNECT_LICENSE_TOGGLES and overrides the values using APP_CONNECT_LICENSE_TOGGLES_OVERRIDE -func GetLicenseTogglesFromEnvironmentVariables () (map[string]bool, error) { - licenseToggles := map[string]bool{} - - err := ConvertAppConnectLicenseToggleEnvironmentVariable(os.Getenv("APP_CONNECT_LICENSE_TOGGLES"), licenseToggles) - if err != nil { - return nil, err - } - err = ConvertAppConnectLicenseToggleEnvironmentVariable(os.Getenv("APP_CONNECT_LICENSE_TOGGLES_OVERRIDE"), licenseToggles) - if err != nil { - return nil, err - } - return licenseToggles, nil -} - -// isLicenseToggleEnabled checks if a toggle is enabled -// if not defined, then it's considered enabled -var isLicenseToggleEnabled = func (toggle string) bool { - enabled, ok := globalLicenseToggles[toggle] - return !ok || enabled -} - -// InitialiseLicenseToggles initialises the globalLicenseToggles map -func InitialiseLicenseToggles(licenseToggles map[string]bool) { - globalLicenseToggles = licenseToggles -} \ No newline at end of file diff --git a/common/designer/license_toggles_test.go b/common/designer/license_toggles_test.go deleted file mode 100644 index 2c62b30..0000000 --- a/common/designer/license_toggles_test.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -© Copyright IBM Corporation 2020 - -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 designer - -import ( - "os" - - "github.com/stretchr/testify/require" - "testing" -) - -func TestConvertAppConnectLicenseToggleEnvironmentVariable(t *testing.T) { - t.Run("When environment variable is empty", func(t *testing.T) { - licenseToggles := map[string]bool{} - err := ConvertAppConnectLicenseToggleEnvironmentVariable("", licenseToggles) - require.NoError(t, err) - require.Equal(t, 0, len(licenseToggles)) - }) - - t.Run("When environment variable is invalid JSON", func(t *testing.T) { - licenseToggles := map[string]bool{} - err := ConvertAppConnectLicenseToggleEnvironmentVariable("{\"foo\":1,\"bar\":}", licenseToggles) - require.Error(t, err) - require.Equal(t, 0, len(licenseToggles)) - }) - - t.Run("When environment variable is valid JSON", func(t *testing.T) { - licenseToggles := map[string]bool{} - err := ConvertAppConnectLicenseToggleEnvironmentVariable("{\"foo\":1,\"bar\":0}", licenseToggles) - require.NoError(t, err) - require.Equal(t, 2, len(licenseToggles)) - require.True(t, licenseToggles["foo"]) - require.False(t, licenseToggles["bar"]) - }) -} - -func TestGetLicenseTogglesFromEnvironmentVariables(t *testing.T) { - os.Unsetenv("APP_CONNECT_LICENSE_TOGGLES") - os.Unsetenv("APP_CONNECT_LICENSE_TOGGLES_OVERRIDE") - - t.Run("When only APP_CONNECT_LICENSE_TOGGLES is invalid JSON and fails to convert", func(t *testing.T) { - os.Setenv("APP_CONNECT_LICENSE_TOGGLES", "{\"foo\":1,\"bar\":0") - _, err := GetLicenseTogglesFromEnvironmentVariables() - require.Error(t, err) - os.Unsetenv("APP_CONNECT_LICENSE_TOGGLES") - }) - - t.Run("When only APP_CONNECT_LICENSE_TOGGLES_OVERRIDE is invalid JSON and fails to convert", func(t *testing.T) { - os.Setenv("APP_CONNECT_LICENSE_TOGGLES_OVERRIDE", "{\"foo\":0") - _, err := GetLicenseTogglesFromEnvironmentVariables() - require.Error(t, err) - os.Unsetenv("APP_CONNECT_LICENSE_TOGGLES_OVERRIDE") - }) - - t.Run("When neither APP_CONNECT_LICENSE_TOGGLES and APP_CONNECT_LICENSE_TOGGLES_OVERRIDE are empty", func(t *testing.T) { - os.Setenv("APP_CONNECT_LICENSE_TOGGLES", "{\"foo\":1,\"bar\":0}") - os.Setenv("APP_CONNECT_LICENSE_TOGGLES_OVERRIDE", "{\"bar\":1}") - licenseToggles, err := GetLicenseTogglesFromEnvironmentVariables() - require.NoError(t, err) - require.True(t, licenseToggles["foo"]) - require.True(t, licenseToggles["bar"]) - os.Unsetenv("APP_CONNECT_LICENSE_TOGGLES") - os.Unsetenv("APP_CONNECT_LICENSE_TOGGLES_OVERRIDE") - }) -} - -func TestIsLicenseToggleEnabled (t *testing.T) { - oldGlobalLicenseToggles := globalLicenseToggles - globalLicenseToggles = map[string]bool{ - "foo": true, - "bar": false, - } - - require.True(t, isLicenseToggleEnabled("foo")) - require.False(t, isLicenseToggleEnabled("bar")) - require.True(t, isLicenseToggleEnabled("unknown")) - globalLicenseToggles = oldGlobalLicenseToggles -} - -func TestInitialiseLicenseToggles (t *testing.T) { - licenseToggles := map[string]bool{ - "foo": true, - "bar": false, - } - InitialiseLicenseToggles(licenseToggles) - require.True(t, globalLicenseToggles["foo"]) - require.False(t, globalLicenseToggles["bar"]) -} \ No newline at end of file diff --git a/common/logger/logger.go b/common/logger/logger.go deleted file mode 100644 index abcdbc5..0000000 --- a/common/logger/logger.go +++ /dev/null @@ -1,219 +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 logger provides utility functions for logging purposes -package logger - -import ( - "encoding/json" - "fmt" - "io" - "os" - "os/user" - "strconv" - "sync" - "time" - - "github.com/imdario/mergo" -) - -// timestampFormat matches the format used by MQ messages (includes milliseconds) -const timestampFormat string = "2006-01-02T15:04:05.000Z07:00" -const debugLevel string = "DEBUG" -const infoLevel string = "INFO" -const errorLevel string = "ERROR" - -// A Logger is used to log messages to stdout -type Logger struct { - mutex sync.Mutex - writer io.Writer - debug bool - json bool - processName string - pid string - serverName string - host string - user *user.User - jsonElements map[string]interface{} -} - -// Define the interface to keep the internal and external loggers in sync -// Every reference to logger in the code must reference this interface -// Every function used by the logger must be defined in the interface -type LoggerInterface interface { - LogDirect(string) - Debug(...interface{}) - Debugf(string, ...interface{}) - Print(...interface{}) - Println(...interface{}) - Printf(string, ...interface{}) - PrintString(string) - Error(...interface{}) - Errorf(string, ...interface{}) - Fatalf(string, ...interface{}) -} - -// NewLogger creates a new logger -func NewLogger(writer io.Writer, debug bool, json bool, serverName string) (*Logger, error) { - hostname, err := os.Hostname() - if err != nil { - return nil, err - } - user, err := user.Current() - if err != nil { - return nil, err - } - jsonElements, err := getAdditionalJsonElements(json) - if err != nil { - return nil, err - } - - return &Logger{ - mutex: sync.Mutex{}, - writer: writer, - debug: debug, - json: json, - processName: os.Args[0], - pid: strconv.Itoa(os.Getpid()), - serverName: serverName, - host: hostname, - user: user, - jsonElements: jsonElements, - }, nil -} - -func getAdditionalJsonElements(isJson bool) (map[string]interface{}, error) { - if isJson { - jsonElements := os.Getenv("MQSI_LOG_ADDITIONAL_JSON_ELEMENTS") - if jsonElements != "" { - logEntries := make(map[string]interface{}) - unmarshalErr := json.Unmarshal([]byte("{"+jsonElements+"}"), &logEntries) - if unmarshalErr != nil { - return nil, unmarshalErr - } - return logEntries, nil - } - } - - return nil, nil -} - -func (l *Logger) format(entry map[string]interface{}) (string, error) { - if l.json { - if l.jsonElements != nil { - // Merge the value of MQSI_LOG_ADDITIONAL_JSON_ELEMENTS with entry - mergo.Merge(&entry, l.jsonElements) - } - - b, err := json.Marshal(entry) - if err != nil { - return "", err - } - return string(b), err - } - return fmt.Sprintf("%v %v\n", entry["ibm_datetime"], entry["message"]), nil -} - -// log logs a message at the specified level. The message is enriched with -// additional fields. -func (l *Logger) log(level string, msg string) { - t := time.Now() - entry := map[string]interface{}{ - "message": fmt.Sprint(msg), - "ibm_datetime": t.Format(timestampFormat), - "loglevel": level, - "host": l.host, - "ibm_serverName": l.serverName, - "ibm_processName": l.processName, - "ibm_processId": l.pid, - "ibm_userName": l.user.Username, - "type": "ace_containerlog", - "ibm_product": "IBM App Connect Enterprise", - "ibm_recordtype": "log", - "module": "integration_server.container", - } - - s, err := l.format(entry) - l.mutex.Lock() - defer l.mutex.Unlock() - - if err != nil { - // TODO: Fix this - fmt.Println(err) - } - if l.json { - fmt.Fprintln(l.writer, s) - } else { - fmt.Fprint(l.writer, s) - } -} - -// LogDirect logs a message directly to stdout -func (l *Logger) LogDirect(msg string) { - l.mutex.Lock() - defer l.mutex.Unlock() - fmt.Fprint(l.writer, msg) -} - -// Debug logs a line as debug -func (l *Logger) Debug(args ...interface{}) { - if l.debug { - l.log(debugLevel, fmt.Sprint(args...)) - } -} - -// Debugf logs a line as debug using format specifiers -func (l *Logger) Debugf(format string, args ...interface{}) { - if l.debug { - l.log(debugLevel, fmt.Sprintf(format, args...)) - } -} - -// Print logs a message as info -func (l *Logger) Print(args ...interface{}) { - l.log(infoLevel, fmt.Sprint(args...)) -} - -// Println logs a message -func (l *Logger) Println(args ...interface{}) { - l.Print(args...) -} - -// Printf logs a message as info using format specifiers -func (l *Logger) Printf(format string, args ...interface{}) { - l.log(infoLevel, fmt.Sprintf(format, args...)) -} - -// PrintString logs a string as info -func (l *Logger) PrintString(msg string) { - l.log(infoLevel, msg) -} - -// Error logs a message as error -func (l *Logger) Error(args ...interface{}) { - l.log(errorLevel, fmt.Sprint(args...)) -} - -// Errorf logs a message as error using format specifiers -func (l *Logger) Errorf(format string, args ...interface{}) { - l.log(errorLevel, fmt.Sprintf(format, args...)) -} - -// Fatalf logs a message as fatal using format specifiers -// TODO: Remove this -func (l *Logger) Fatalf(format string, args ...interface{}) { - l.log("FATAL", fmt.Sprintf(format, args...)) -} diff --git a/common/logger/logger_test.go b/common/logger/logger_test.go deleted file mode 100644 index 9d5bf2f..0000000 --- a/common/logger/logger_test.go +++ /dev/null @@ -1,80 +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 logger_test - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "strings" - "testing" - - "github.com/ot4i/ace-docker/common/logger" -) - -func TestJSONLogger(t *testing.T) { - const logSourceCRN, saveServiceCopy, newParam = "testCRN", false, "123" - os.Setenv("MQSI_LOG_ADDITIONAL_JSON_ELEMENTS", fmt.Sprintf("\"logSourceCRN\":%q, \"saveServiceCopy\":%t, \"newParam\":%q", logSourceCRN, saveServiceCopy, newParam)) - - buf := new(bytes.Buffer) - l, err := logger.NewLogger(buf, true, true, t.Name()) - if err != nil { - t.Fatal(err) - } - s := "Hello world" - l.Print(s) - var e map[string]interface{} - err = json.Unmarshal([]byte(buf.String()), &e) - if err != nil { - t.Error(err) - } - if s != e["message"] { - t.Errorf("Expected JSON to contain message=%v; got %v", s, buf.String()) - } - - if e["logSourceCRN"] != logSourceCRN { - t.Errorf("Expected JSON to contain logSourceCRN=%v; got %v", e["logSourceCRN"], buf.String()) - } - - if e["saveServiceCopy"] != saveServiceCopy { - t.Errorf("Expected JSON to contain saveServiceCopy=%v; got %v", e["saveServiceCopy"], buf.String()) - } - - if e["newParam"] != newParam { - t.Errorf("Expected JSON to contain newParam=%v; got %v", e["newParam"], buf.String()) - } -} - -func TestSimpleLogger(t *testing.T) { - os.Setenv("MQSI_LOG_ADDITIONAL_JSON_ELEMENTS", "\"logSourceCRN\":\"testCRN\"") - - buf := new(bytes.Buffer) - l, err := logger.NewLogger(buf, true, false, t.Name()) - if err != nil { - t.Fatal(err) - } - s := "Hello world" - l.Print(s) - if !strings.Contains(buf.String(), s) { - t.Errorf("Expected log output to contain %v; got %v", s, buf.String()) - } - - if strings.Contains(buf.String(), "logSourceCRN") { - t.Errorf("Expected log output to without %v; got %v", "logSourceCRN", buf.String()) - } -} diff --git a/deps/.gitignore b/deps/.gitignore deleted file mode 100644 index 4498535..0000000 --- a/deps/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -*.tar.gz -*.tgz -OpenTracing/config/* -OpenTracing/library/* -!OpenTracing/config/README -!OpenTracing/library/README \ No newline at end of file diff --git a/deps/CSAPI/META-INF/bar-refresh.links b/deps/CSAPI/META-INF/bar-refresh.links deleted file mode 100644 index 44e994c..0000000 --- a/deps/CSAPI/META-INF/bar-refresh.links +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/deps/CSAPI/META-INF/broker.xml b/deps/CSAPI/META-INF/broker.xml deleted file mode 100644 index 1526b46..0000000 --- a/deps/CSAPI/META-INF/broker.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/deps/CSAPI/META-INF/manifest.mf b/deps/CSAPI/META-INF/manifest.mf deleted file mode 100644 index 55f313d..0000000 --- a/deps/CSAPI/META-INF/manifest.mf +++ /dev/null @@ -1 +0,0 @@ -srcSelected=false;showSrc=true;esql21VersionSelected=false; \ No newline at end of file diff --git a/deps/CSAPI/META-INF/service.log b/deps/CSAPI/META-INF/service.log deleted file mode 100644 index e6de405..0000000 --- a/deps/CSAPI/META-INF/service.log +++ /dev/null @@ -1,36 +0,0 @@ - -============================================== - -!Wed Mar 23 13:16:28 GMT 2022 -Message flow CSAPI/Proxy.msgflow successfully loaded -Message flow CSAPI/Proxy.msgflow successfully compiled -Message flow CSAPI/Proxy.msgflow successfully added to archive file - -Node Flow Version Reason ----------------------------------------------------------------------------------------------------- -Failure Handler Proxy 5.0 Generate 5.0 compatible ESQL by default. -This compatibility level supports ESQL source code debugging. -Deploying 5.0 compatible ESQL code will cause a syntax error on version 2.1 or previous brokers. -Configure Request Proxy 5.0 Generate 5.0 compatible ESQL by default. -This compatibility level supports ESQL source code debugging. -Deploying 5.0 compatible ESQL code will cause a syntax error on version 2.1 or previous brokers. -Message flow CSAPI/Proxy.msgflow successfully loaded -Message flow CSAPI/Proxy.msgflow successfully compiled -Message flow CSAPI/Proxy.msgflow successfully added to archive file - -Node Flow Version Reason ----------------------------------------------------------------------------------------------------- -Failure Handler Proxy 5.0 Generate 5.0 compatible ESQL by default. -This compatibility level supports ESQL source code debugging. -Deploying 5.0 compatible ESQL code will cause a syntax error on version 2.1 or previous brokers. -Configure Request Proxy 5.0 Generate 5.0 compatible ESQL by default. -This compatibility level supports ESQL source code debugging. -Deploying 5.0 compatible ESQL code will cause a syntax error on version 2.1 or previous brokers. - -Successfully added file CSAPI/application.descriptor to BAR file. - -Successfully added file CSAPI/application.descriptor to BAR file. - -Successfully added file CSAPI/application.descriptor to BAR file. - -Successfully added file CSAPI/application.descriptor to BAR file. diff --git a/deps/CSAPI/META-INF/user.log b/deps/CSAPI/META-INF/user.log deleted file mode 100644 index 1257a13..0000000 --- a/deps/CSAPI/META-INF/user.log +++ /dev/null @@ -1,37 +0,0 @@ - -============================================== - -!Wed Mar 23 13:16:28 GMT 2022 - -Processing file CSAPI/Proxy.msgflow -Successfully added file CSAPI/Proxy.msgflow to archive file -Elapsed time: 0.082 second(s). - -Processing file CSAPI/Proxy.msgflow -Successfully added file CSAPI/Proxy.msgflow to archive file -Elapsed time: 0.082 second(s). - -Processing file CSAPI/Proxy.msgflow -Message flow "Proxy.msgflow" can be deployed as source since it does not include any subflows defined in the MSGFLOW files -or any nodes unsupported by the source deploy. You may select -deployAsSource flag when building the -BAR with MQSICREATEBAR command or do not select "Compile and in-line resources" build option on the BAR editor. - -============================================== - -!Wed Mar 23 13:16:28 GMT 2022 - -Processing file CSAPI/application.descriptor. -Successfully added file CSAPI/application.descriptor to BAR file. -Elapsed time: 0 second(s). - -Successfully added file CSAPI/application.descriptor to BAR file. - -============================================== - -!Wed Mar 23 13:16:28 GMT 2022 - -Processing file CSAPI/application.descriptor. -Successfully added file CSAPI/application.descriptor to BAR file. -Elapsed time: 0 second(s). - -Successfully added file CSAPI/application.descriptor to BAR file. diff --git a/deps/CSAPI/Proxy.cmf b/deps/CSAPI/Proxy.cmf deleted file mode 100644 index 3f91329..0000000 --- a/deps/CSAPI/Proxy.cmf +++ /dev/null @@ -1,75 +0,0 @@ - - -CREATE SCHEMA "" PATH "" - -CREATE COMPUTE MODULE Proxy_Configure_Request - CREATE FUNCTION Main() RETURNS BOOLEAN - BEGIN - - SET OutputRoot = InputRoot; - - /* -LE section when "Set destination list" is checked on the input node: - - (0x01000000:Name):HTTP = ( - (0x01000000:Name):Input = ( - (0x01000000:Name):RequestLine = ( - (0x03000000:NameValue):Method = 'GET' (CHARACTER) - (0x03000000:NameValue):RequestURI = '/csapi/abc/def' (CHARACTER) - (0x03000000:NameValue):HTTPVersion = 'HTTP/1.1' (CHARACTER) - ) - ) - ) -*/ - -- Keep everything after /csapi (include the slash) - IF LENGTH( InputRoot.HTTPInputHeader."X-Query-String" ) > 0 THEN - SET OutputLocalEnvironment.Destination.HTTP.RequestLine.RequestURI = SUBSTRING(InputLocalEnvironment.HTTP.Input.RequestLine.RequestURI FROM 7) || '?' || InputRoot.HTTPInputHeader."X-Query-String"; - ELSE - SET OutputLocalEnvironment.Destination.HTTP.RequestLine.RequestURI = SUBSTRING(InputLocalEnvironment.HTTP.Input.RequestLine.RequestURI FROM 7); - END IF; - SET OutputLocalEnvironment.Destination.HTTP.RequestLine.Method = InputLocalEnvironment.HTTP.Input.RequestLine.Method; - - RETURN TRUE; - END; - -END MODULE; -CREATE SCHEMA "" PATH "" - -CREATE COMPUTE MODULE Proxy_Failure_Handler - CREATE FUNCTION Main() RETURNS BOOLEAN - BEGIN - SET OutputRoot = InputRoot; - - -- Set response code - SET OutputLocalEnvironment.Destination.HTTP.ReplyStatusCode = 500; - - if InputExceptionList IS NOT NULL THEN - -- Check the BIP message and put out an appropriate error message - DECLARE messageNumber INTEGER; - DECLARE messageText char; - CALL getLastExceptionDetail(InputExceptionList, messageNumber, messageText); - - SET OutputRoot.JSON.Data.BIP.Number = messageNumber; - SET OutputRoot.JSON.Data.BIP.Message = messageText; - END IF; - - -- Just forward on the original message - - - END; -END MODULE;CREATE PROCEDURE getLastExceptionDetail(IN InputTree reference, OUT messageNumber integer, OUT messageText char) -BEGIN - -- Create a reference to the first child of the exception list - declare ptrException reference to InputTree.*[1]; - -- keep looping while the moves to the child of exception list work - WHILE lastmove(ptrException) DO - -- store the current values for the error number and text - IF ptrException.Number is not null THEN - SET messageNumber = ptrException.Number; - SET messageText = ptrException.Text; - END IF; - -- now move to the last child which should be the next exceptionlist - move ptrException lastchild; - END WHILE; -END; - \ No newline at end of file diff --git a/deps/CSAPI/application.descriptor b/deps/CSAPI/application.descriptor deleted file mode 100644 index 166bf9e..0000000 --- a/deps/CSAPI/application.descriptor +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/deps/OpenTracing/config/README b/deps/OpenTracing/config/README deleted file mode 100644 index e9d10f0..0000000 --- a/deps/OpenTracing/config/README +++ /dev/null @@ -1 +0,0 @@ -Any configuration files needed for the Loadable Exit Library (LEL) should be placed in this directory \ No newline at end of file diff --git a/deps/OpenTracing/library/README b/deps/OpenTracing/library/README deleted file mode 100644 index 13f5064..0000000 --- a/deps/OpenTracing/library/README +++ /dev/null @@ -1 +0,0 @@ -The Loadable Exit Library (LEL) file to support tracing should be placed in this directory. \ No newline at end of file diff --git a/deps/package-connectors.json b/deps/package-connectors.json deleted file mode 100644 index e056389..0000000 --- a/deps/package-connectors.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "ace", - "version": "1.0.0", - "description": "ace", - "private": true, - "dependencies": { - "loopback-connector-mongodb": "5.4.0", - "loopback-connector-postgresql": "5.1.0" - } -} \ No newline at end of file diff --git a/experimental/ace-basic/Dockerfile.ubuntu b/experimental/ace-basic/Dockerfile.ubuntu index fad5110..7c0d392 100644 --- a/experimental/ace-basic/Dockerfile.ubuntu +++ b/experimental/ace-basic/Dockerfile.ubuntu @@ -12,7 +12,8 @@ MAINTAINER Trevor Dolby (@tdolby) # # This might require a local directory with the right permissions, or changing the userid further down . . . - +ARG USERNAME +ARG PASSWORD ARG DOWNLOAD_URL=http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/integration/12.0.2.0-ACE-LINUX64-DEVELOPER.tar.gz ARG PRODUCT_LABEL=ace-12.0.2.0 @@ -22,7 +23,8 @@ ENV DEBIAN_FRONTEND noninteractive # Install ACE v12.0.2.0 and accept the license RUN apt-get update && apt-get install -y --no-install-recommends curl && \ mkdir /opt/ibm && echo Downloading package ${DOWNLOAD_URL} && \ - curl ${DOWNLOAD_URL} | tar zx --exclude=tools --directory /opt/ibm && \ + if [ -z $USERNAME ]; then curl ${DOWNLOAD_URL}; else curl -u ${USERNAME}:{PASSWORD} ${DOWNLOAD_URL}; fi | \ + tar zx --exclude=tools --exclude server/bin/TADataCollector.sh --exclude server/transformationAdvisor/ta-plugin-ace.jar --directory /opt/ibm && \ mv /opt/ibm/${PRODUCT_LABEL} /opt/ibm/ace-12 && \ /opt/ibm/ace-12/ace make registry global accept license deferred diff --git a/git.commit b/git.commit deleted file mode 100644 index 087e0e0..0000000 --- a/git.commit +++ /dev/null @@ -1 +0,0 @@ -UPDATEME \ No newline at end of file diff --git a/go.mod b/go.mod deleted file mode 100644 index ec8fd03..0000000 --- a/go.mod +++ /dev/null @@ -1,55 +0,0 @@ -module github.com/ot4i/ace-docker - -go 1.17 - -require ( - github.com/Jeffail/gabs v1.4.0 - github.com/aymerick/raymond v2.0.2+incompatible - github.com/fsnotify/fsnotify v1.5.1 - github.com/ghodss/yaml v1.0.0 - github.com/gorilla/websocket v1.4.2 - github.com/imdario/mergo v0.3.12 - github.com/prometheus/client_golang v1.11.1 - github.com/prometheus/client_model v0.2.0 - github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 - golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 - gopkg.in/yaml.v2 v2.4.0 - k8s.io/apimachinery v0.22.3 - k8s.io/client-go v0.22.3 - software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 -) - -require ( - cloud.google.com/go v0.97.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-logr/logr v0.4.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.6 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect - golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.22.3 // indirect - k8s.io/klog/v2 v2.9.0 // indirect - k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 2d1ae38..0000000 --- a/go.sum +++ /dev/null @@ -1,778 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= -github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= -github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI= -golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE= -golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.22.3 h1:wOoES2GoSkUsdped2RB4zYypPqWtvprGoKCENTOOjP4= -k8s.io/api v0.22.3/go.mod h1:azgiXFiXqiWyLCfI62/eYBOu19rj2LKmIhFPP4+33fs= -k8s.io/apimachinery v0.22.3 h1:mrvBG5CZnEfwgpVqWcrRKvdsYECTrhAR6cApAgdsflk= -k8s.io/apimachinery v0.22.3/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/client-go v0.22.3 h1:6onkOSc+YNdwq5zXE0wFXicq64rrym+mXwHu/CPVGO4= -k8s.io/client-go v0.22.3/go.mod h1:ElDjYf8gvZsKDYexmsmnMQ0DYO8W9RwBjfQ1PI53yow= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4= -software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg= diff --git a/internal/command/command.go b/internal/command/command.go deleted file mode 100644 index 5ad664b..0000000 --- a/internal/command/command.go +++ /dev/null @@ -1,252 +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 command contains code to run external commands -package command - -import ( - "bufio" - "errors" - "fmt" - "os/exec" - "os/user" - "runtime" - "strconv" - "strings" - "syscall" - - "github.com/ot4i/ace-docker/common/logger" -) - -// A BackgroundCmd provides a handle to a backgrounded command and its completion state. -type BackgroundCmd struct { - Cmd *exec.Cmd - ReturnCode int - ReturnError error - Started bool - Finished bool - finishChan chan bool -} - -// RunCmd runs an OS command. On Linux it waits for the command to -// complete and returns the exit status (return code). -// Do not use this function to run shell built-ins (like "cd"), because -// the error handling works differently -func RunCmd(cmd *exec.Cmd) (string, int, error) { - // Run the command and wait for completion - out, err := cmd.Output() - if err != nil { - // Assert that this is an ExitError - exiterr, ok := err.(*exec.ExitError) - // If the type assertion was correct, and we're on Linux - if ok && runtime.GOOS == "linux" { - status, ok := exiterr.Sys().(syscall.WaitStatus) - if ok { - return string(out), status.ExitStatus(), fmt.Errorf("%v: %v: %v", cmd.Path, err, string(exiterr.Stderr)) - } - } - return string(out), -1, err - } - return string(out), 0, nil -} - -// Run runs an OS command. On Linux it waits for the command to -// complete and returns the exit status (return code). -// 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) { - return RunCmd(exec.Command(name, arg...)) -} - -// RunCmdBackground runs an OS command. On Linux it runs the command in -// the background, piping stdout/stderr to log.LogDirect. -// It returns a BackgroundCmd, containing the os/exec/cmd, an int channel -// and error channel that will have the return code and error written respectively, -// when the process exits -func RunCmdBackground(cmd *exec.Cmd, log logger.LoggerInterface) BackgroundCmd { - bgCmd := BackgroundCmd{cmd, 0, nil, false, false, make(chan bool)} - - stdoutChildPipe, err := cmd.StdoutPipe() - if err != nil { - bgCmd.ReturnCode = -1 - bgCmd.ReturnError = err - return bgCmd - } - stderrChildPipe, err := cmd.StderrPipe() - if err != nil { - bgCmd.ReturnCode = -1 - bgCmd.ReturnError = err - return bgCmd - } - - // Write both stdout and stderr of the child to our logs - stdoutScanner := bufio.NewScanner(stdoutChildPipe) - stderrScanner := bufio.NewScanner(stderrChildPipe) - - go func() { - for stdoutScanner.Scan() { - log.LogDirect(stdoutScanner.Text() + "\n") - } - }() - go func() { - for stderrScanner.Scan() { - log.LogDirect(stderrScanner.Text() + "\n") - } - }() - - // Start the command in the background - err = cmd.Start() - if err != nil { - bgCmd.ReturnCode = -1 - bgCmd.ReturnError = err - return bgCmd - } - - bgCmd.Started = true - - // Wait on the command and mark as finished when done - go func() { - err := cmd.Wait() - bgCmd.ReturnCode = 0 - bgCmd.ReturnError = nil - if err != nil { - // Assert that this is an ExitError - exiterr, ok := err.(*exec.ExitError) - // If the type assertion was correct, and we're on Linux - if ok && runtime.GOOS == "linux" { - status, ok := exiterr.Sys().(syscall.WaitStatus) - if ok { - bgCmd.ReturnCode = status.ExitStatus() - bgCmd.ReturnError = fmt.Errorf("%v: %v", cmd.Path, err) - } - } - bgCmd.ReturnCode = -1 - bgCmd.ReturnError = err - } - bgCmd.Finished = true - bgCmd.finishChan <- true - close(bgCmd.finishChan) - }() - - return bgCmd -} - -// SigIntBackground sends the signal SIGINT to the backgrounded command wrapped by -// the BackgroundCommand struct -func SigIntBackground(bgCmd BackgroundCmd) { - if bgCmd.Started && !bgCmd.Finished { - bgCmd.Cmd.Process.Signal(syscall.SIGINT) // TODO returns an error - } -} - -// WaitOnBackground will wait until the process is marked as finished. -func WaitOnBackground(bgCmd BackgroundCmd) { - if !bgCmd.Finished { - bgCmd.Finished = <-bgCmd.finishChan - } -} - -// RunBackground runs an OS command. On Linux it runs the command in the background -// and returns a channel to int that will have the return code written when -// the process exits -func RunBackground(name string, log logger.LoggerInterface, arg ...string) BackgroundCmd { - return RunCmdBackground(exec.Command(name, arg...), log) -} - -// RunAsUser runs the specified command as the aceuser user. If the current user -// already is the specified user then this calls through to RunCmd. -func RunAsUser(username string, name string, arg ...string) (string, int, error) { - thisUser, err := user.Current() - if err != nil { - return "", 0, err - } - cmd := exec.Command(name, arg...) - if strings.Compare(username, thisUser.Username) != 0 { - cmd.SysProcAttr = &syscall.SysProcAttr{} - uid, gid, groups, err := LookupUser(username) - if err != nil { - return "", 0, err - } - cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid, Groups: groups} - } - return RunCmd(cmd) -} - -// RunAsUserBackground runs the specified command as the aceuser user in the background. -// It returns a BackgroundCmd, containing the os/exec/cmd, an int channel -// and error channel that will have the return code and error written respectively, -// when the process exits -func RunAsUserBackground(username string, name string, log logger.LoggerInterface, arg ...string) BackgroundCmd { - cmd := exec.Command(name, arg...) - thisUser, err := user.Current() - if err != nil { - bgCmd := BackgroundCmd{cmd, 0, nil, false, false, make(chan bool)} - bgCmd.ReturnCode = -1 - bgCmd.ReturnError = err - return bgCmd - } - if strings.Compare(username, thisUser.Username) != 0 { - cmd.SysProcAttr = &syscall.SysProcAttr{} - uid, gid, groups, err := LookupUser(username) - if err != nil { - bgCmd := BackgroundCmd{cmd, 0, nil, false, false, make(chan bool)} - bgCmd.ReturnCode = -1 - bgCmd.ReturnError = err - return bgCmd - } - cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid, Groups: groups} - } - return RunCmdBackground(cmd, log) -} - -// LookupAceuser looks up the UID, GID and supplementary groups of the aceuser user. This is only -// allowed if the current user is "root"; otherwise this returns an error. -func LookupUser(username string) (uint32, uint32, []uint32, error) { - thisUser, err := user.Current() - if err != nil { - return uint32(0), uint32(0), []uint32{}, err - } - if strings.Compare("root", thisUser.Username) != 0 { - return uint32(0), uint32(0), []uint32{}, errors.New("Not permitted: the current user attempted to look up user but is not permitted.") - } - - user, err := user.Lookup(username) - if err != nil { - return uint32(0), uint32(0), []uint32{}, err - } - userUID, err := strconv.Atoi(user.Uid) - if err != nil { - return uint32(0), uint32(0), []uint32{}, err - } - userGID, err := strconv.Atoi(user.Gid) - if err != nil { - return uint32(0), uint32(0), []uint32{}, err - } - userSupplementaryGIDStrings, err := user.GroupIds() - if err != nil { - return uint32(0), uint32(0), []uint32{}, err - } - var userSupplementaryGIDs []uint32 - for _, idString := range userSupplementaryGIDStrings { - id, err := strconv.Atoi(idString) - if err != nil { - return uint32(0), uint32(0), []uint32{}, err - } - userSupplementaryGIDs = append(userSupplementaryGIDs, uint32(id)) - } - - return uint32(userUID), uint32(userGID), userSupplementaryGIDs, nil -} diff --git a/internal/command/command_test.go b/internal/command/command_test.go deleted file mode 100644 index a4d6464..0000000 --- a/internal/command/command_test.go +++ /dev/null @@ -1,49 +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 command_test - -import ( - "runtime" - "testing" - - "github.com/ot4i/ace-docker/internal/command" -) - -var commandTests = []struct { - name string - arg []string - rc int -}{ - {"ls", []string{}, 0}, - {"ls", []string{"madeup"}, 2}, - {"bash", []string{"-c", "exit 99"}, 99}, -} - -func TestRun(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("Skipping tests for package which only works on Linux") - } - for _, table := range commandTests { - arg := table.arg - _, rc, err := command.Run(table.name, arg...) - if rc != table.rc { - t.Errorf("Run(%v,%v) - expected %v, got %v", table.name, table.arg, table.rc, rc) - } - if rc != 0 && err == nil { - t.Errorf("Run(%v,%v) - expected error for non-zero return code (rc=%v)", table.name, table.arg, rc) - } - } -} diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go deleted file mode 100644 index 693dfb6..0000000 --- a/internal/configuration/configuration.go +++ /dev/null @@ -1,659 +0,0 @@ -package configuration - -import ( - "archive/zip" - "bytes" - "context" - "crypto/tls" - "crypto/x509" - "encoding/base64" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "os/exec" - "path" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/Jeffail/gabs" - - "github.com/ot4i/ace-docker/common/logger" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - "k8s.io/client-go/rest" -) - -const workdirName = "ace-server" -const truststoresName = "truststores" -const keystoresName = "keystores" -const genericName = "generic" -const odbcIniName = "odbc" -const adminsslName = "adminssl" -const aceInstall = "/opt/ibm/ace-12/server/bin" -const initialConfig = "initial-config" -const workdiroverrides = "workdir_overrides" - -var ContentServer = true - -var ( - configurationClassGVR = schema.GroupVersionResource{ - Group: "appconnect.ibm.com", - Version: "v1beta1", - Resource: "configurations", - } - - integrationServerClassGVR = schema.GroupVersionResource{ - Group: "appconnect.ibm.com", - Version: "v1beta1", - Resource: "integrationservers", - } -) - -/** -* START: FUNCTIONS CREATES EXTERNAL REQUESTS - */ - -func getPodNamespace() (string, error) { - if data, err := ioutilReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { - if ns := strings.TrimSpace(string(data)); len(ns) > 0 { - return ns, nil - } - return "default", err - } - return "default", nil -} - -func writeConfigurationFile(dir string, fileName string, contents []byte) error { - makeDirErr := osMkdirAll(dir, 0740) - if makeDirErr != nil { - return makeDirErr - } - return ioutilWriteFile(dir+string(os.PathSeparator)+fileName, contents, 0740) -} - -func unzip(log logger.LoggerInterface, dir string, contents []byte) error { - var filenames []string - zipReader, err := zip.NewReader(bytes.NewReader(contents), int64(len(contents))) - if err != nil { - log.Printf("%s: %#v", "Failed to read zip contents", err) - return err - } - - for _, file := range zipReader.File { - - // Store filename/path for returning and using later on - filePath := filepath.Join(dir, file.Name) - - // Check for ZipSlip. - if !strings.HasPrefix(filePath, filepath.Clean(dir)+string(os.PathSeparator)) { - if err != nil { - log.Printf("%s: %#v", "Illegal file path:"+filePath, err) - return err - } - } - - filenames = append(filenames, filePath) - - if file.FileInfo().IsDir() { - // Make Folder - osMkdirAll(filePath, os.ModePerm) - continue - } - - // Make File - err = osMkdirAll(filepath.Dir(filePath), os.ModePerm) - - if err != nil { - log.Printf("%s: %#v", "Illegal file path:"+filePath, err) - return err - } - - outFile, err := osOpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) - - if err != nil { - log.Printf("%s: %#v", "Cannot create file writer"+filePath, err) - return err - } - - fileReader, err := file.Open() - - if err != nil { - log.Printf("%s: %#v", "Cannot open file"+filePath, err) - return err - } - - _, err = ioCopy(outFile, fileReader) - // Close the file without defer to close before next iteration of loop - outFile.Close() - fileReader.Close() - - if err != nil { - log.Printf("%s: %#v", "Cannot write file"+filePath, err) - return err - } - - } - return nil -} - -/** -* END: FUNCTIONS CREATES EXTERNAL REQUESTS - */ - -type configurationObject struct { - name string - configType string - contents []byte -} - -func getAllConfigurationsImpl(log logger.LoggerInterface, namespace string, configurationsNames []string, dynamicClient dynamic.Interface) ([]*unstructured.Unstructured, error) { - - list := make([]*unstructured.Unstructured, len(configurationsNames)) - for index, configurationName := range configurationsNames { - - res := dynamicClient.Resource(configurationClassGVR).Namespace(namespace) - configuration, err := res.Get(context.TODO(), configurationName, metav1.GetOptions{}) - if err != nil { - log.Printf("%s: %#v", "Failed to get configuration: "+configurationName, err) - return nil, err - } - list[index] = configuration - } - return list, nil -} - -var getAllConfigurations = getAllConfigurationsImpl - -func getSecretImpl(basedir string, secretName string) ([]byte, error) { - content, err := ioutil.ReadFile(basedir + string(os.PathSeparator) + "secrets" + string(os.PathSeparator) + secretName + string(os.PathSeparator) + "configuration") - return content, err -} - -var getSecret = getSecretImpl - -func parseConfigurationList(log logger.LoggerInterface, basedir string, list []*unstructured.Unstructured) ([]configurationObject, error) { - output := make([]configurationObject, len(list)) - for index, item := range list { - name := item.GetName() - configType, exists, err := unstructured.NestedString(item.Object, "spec", "type") - - if !exists || err != nil { - log.Printf("%s: %#v", "A configuration must has a type", errors.New("A configuration must has a type")) - return nil, errors.New("A configuration must has a type") - } - switch configType { - case "policyproject", "odbc", "serverconf": - fld, exists, err := unstructured.NestedString(item.Object, "spec", "contents") - if !exists || err != nil { - log.Printf("%s: %#v", "A configuration with type: "+configType+" must has a contents field", errors.New("A configuration with type: "+configType+" must has a contents field")) - return nil, errors.New("A configuration with type: " + configType + " must has a contents field") - } - contents, err := base64.StdEncoding.DecodeString(fld) - if err != nil { - log.Printf("%s: %#v", "Failed to decode contents", err) - return nil, errors.New("Failed to decode contents") - } - output[index] = configurationObject{name: name, configType: configType, contents: contents} - case "truststorecertificate", "truststore", "keystore", "setdbparms", "generic", "adminssl", "agentx", "agenta", "accounts", "loopbackdatasource", "barauth", "workdiroverride", "resiliencekafkacredentials", "persistencerediscredentials": - secretName, exists, err := unstructured.NestedString(item.Object, "spec", "secretName") - if !exists || err != nil { - log.Printf("%s: %#v", "A configuration with type: "+configType+" must have a secretName field", errors.New("A configuration with type: "+configType+" must have a secretName field")) - return nil, errors.New("A configuration with type: " + configType + " must have a secretName field") - } - secretVal, err := getSecret(basedir, secretName) - if err != nil { - log.Printf("%s: %#v", "Failed to get secret", err) - return nil, err - } - output[index] = configurationObject{name: name, configType: configType, contents: secretVal} - } - } - return output, nil -} - -var dynamicNewForConfig = dynamic.NewForConfig -var kubernetesNewForConfig = kubernetes.NewForConfig - -func setupClientsImpl() (dynamic.Interface, error) { - config, err := rest.InClusterConfig() - if err != nil { - return nil, err - } - dynamicClient, err := dynamicNewForConfig(config) - - if err != nil { - return nil, err - - } - return dynamicClient, nil - -} - -var setupClients = setupClientsImpl - -func SetupConfigurationsFiles(log logger.LoggerInterface, basedir string) error { - configurationNames, ok := os.LookupEnv("ACE_CONFIGURATIONS") - if ok && configurationNames != "" { - log.Printf("Setup configuration files - configuration names: %s", configurationNames) - - return SetupConfigurationsFilesInternal(log, strings.SplitN(configurationNames, ",", -1), basedir) - } else { - return nil - } -} -func SetupConfigurationsFilesInternal(log logger.LoggerInterface, configurationNames []string, basedir string) error { - // set up k8s client - dynamicClient, err := setupClients() - if err != nil { - return err - } - // get pod namespace - namespace, err := getPodNamespace() - if err != nil { - return err - } - // get contents for all configurations - rawConfigurations, err := getAllConfigurations(log, namespace, configurationNames, dynamicClient) - - if err != nil { - return err - } - configurationObjects, err := parseConfigurationList(log, basedir, rawConfigurations) - if err != nil { - return err - } - - for _, configObject := range configurationObjects { - // create files on the system - err := constructConfigurationsOnFileSystem(log, basedir, configObject.name, configObject.configType, configObject.contents) - if err != nil { - return err - } - } - return nil -} - -func constructConfigurationsOnFileSystem(log logger.LoggerInterface, basedir string, configName string, configType string, contents []byte) error { - log.Printf("Construct a configuration on the filesystem - configuration name: %s type: %s", configName, configType) - switch configType { - case "policyproject": - return constructPolicyProjectOnFileSystem(log, basedir, contents) - case "truststore": - return constructTrustStoreOnFileSystem(log, basedir, configName, contents) - case "keystore": - return constructKeyStoreOnFileSystem(log, basedir, configName, contents) - case "odbc": - return constructOdbcIniOnFileSystem(log, basedir, contents) - case "serverconf": - return constructServerConfYamlOnFileSystem(log, basedir, contents) - case "setdbparms": - return executeSetDbParms(log, basedir, contents) - case "generic": - return constructGenericOnFileSystem(log, basedir, contents) - case "loopbackdatasource": - return constructLoopbackDataSourceOnFileSystem(log, basedir, contents) - case "adminssl": - return constructAdminSSLOnFileSystem(log, basedir, contents) - case "accounts": - return SetupTechConnectorsConfigurations(log, basedir, contents) - case "agentx": - return constructAgentxOnFileSystem(log, basedir, contents) - case "agenta": - return constructAgentaOnFileSystem(log, basedir, contents) - case "truststorecertificate": - return addTrustCertificateToCAcerts(log, basedir, configName, contents) - case "barauth": - return downloadBarFiles(log, basedir, contents) - case "workdiroverride": - return constructWorkdirOverrideOnFileSystem(log, basedir, configName, contents) - case "resiliencekafkacredentials": - log.Println("Do nothing for resiliencykafkacredentials") - return nil - case "persistencerediscredentials": - log.Println("Do nothing for persistencerediscredentials") - return nil - default: - return errors.New("Unknown configuration type") - } -} - -func constructPolicyProjectOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct policy project on the filesystem") - return unzip(log, basedir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"overrides", contents) -} - -func constructTrustStoreOnFileSystem(log logger.LoggerInterface, basedir string, name string, contents []byte) error { - log.Printf("Construct truststore on the filesystem - Truststore name: %s", name) - return writeConfigurationFile(basedir+string(os.PathSeparator)+truststoresName, name, contents) -} - -func constructKeyStoreOnFileSystem(log logger.LoggerInterface, basedir string, name string, contents []byte) error { - log.Printf("Construct keystore on the filesystem - Keystore name: %s", name) - return writeConfigurationFile(basedir+string(os.PathSeparator)+keystoresName, name, contents) -} - -func constructOdbcIniOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct odbc.Ini on the filesystem") - return writeConfigurationFile(basedir+string(os.PathSeparator)+workdirName, "odbc.ini", contents) -} - -func constructGenericOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct generic files on the filesystem") - return unzip(log, basedir+string(os.PathSeparator)+genericName, contents) -} - -func constructLoopbackDataSourceOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct loopback connector files on the filesystem") - return unzip(log, basedir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config"+string(os.PathSeparator)+"connectors"+string(os.PathSeparator)+"loopback", contents) -} - -func constructAdminSSLOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct adminssl on the filesystem") - return unzip(log, basedir+string(os.PathSeparator)+adminsslName, contents) -} - -func constructServerConfYamlOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct serverconfyaml on the filesystem") - return writeConfigurationFile(basedir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"overrides", "server.conf.yaml", contents) -} - -func constructAgentxOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct agentx on the filesystem") - return writeConfigurationFile(basedir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config/iibswitch/agentx", "agentx.json", contents) -} - -func constructAgentaOnFileSystem(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Construct agenta on the filesystem") - return writeConfigurationFile(basedir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config/iibswitch/agenta", "agenta.json", contents) -} - -func addTrustCertificateToCAcerts(log logger.LoggerInterface, basedir string, name string, contents []byte) error { - log.Println("Adding trust certificate to CAcerts") - // creating temporary file based on the content - tmpFile := creatingTempFile(log, contents, name) - // cleans up the file afterwards - defer os.Remove(tmpFile.Name()) - // adding this file to CAcerts - commandCreateArgsJKS := []string{"-import", "-file", tmpFile.Name(), "-alias", name, "-keystore", "$MQSI_JREPATH/lib/security/cacerts", "-storepass", "changeit", "-noprompt", "-storetype", "JKS"} - return internalRunKeytoolCommand(log, commandCreateArgsJKS) -} - -func constructWorkdirOverrideOnFileSystem(log logger.LoggerInterface, basedir string, name string, contents []byte) error { - log.Printf("Construct workdiroverride on the filesystem - Workdiroveride name: %s", name) - return writeConfigurationFile(basedir+string(os.PathSeparator)+initialConfig+string(os.PathSeparator)+workdiroverrides, name, contents) -} - -func creatingTempFile(log logger.LoggerInterface, contents []byte, name string) *os.File { - tmpFile, err := ioutil.TempFile(os.TempDir(), name) - if err != nil { - log.Println("Cannot create temporary file", err) - } - - // writing content to the file - if _, err = tmpFile.Write(contents); err != nil { - log.Println("Failed to write to temporary file", err) - } - - // Close the file - if err := tmpFile.Close(); err != nil { - log.Println("Failed to close the file", err) - } - return tmpFile -} - -func executeSetDbParms(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Execute mqsisetdbparms command") - for index, m := range strings.Split(string(contents), "\n") { - // ignore empty lines - if len(strings.TrimSpace(m)) > 0 { - contentsArray := strings.Fields(strings.TrimSpace(m)) - log.Printf("Execute line %d with number of args: %d", index, len(contentsArray)) - var trimmedArray []string - for _, m := range contentsArray { - escapedQuote := strings.Replace(m, "'", "'\\''", -1) - trimmedArray = append(trimmedArray, "'"+strings.TrimSpace(escapedQuote)+"'") - } - if len(trimmedArray) > 2 { - if trimmedArray[0] == "'mqsisetdbparms'" { - if !Contains(trimmedArray, "'-w'") { - trimmedArray = append(trimmedArray, "'-w'") - trimmedArray = append(trimmedArray, "'"+basedir+string(os.PathSeparator)+workdirName+"'") - } - err := internalRunSetdbparmsCommand(log, "mqsisetdbparms", trimmedArray[1:]) - if err != nil { - return err - } - } else if len(trimmedArray) == 3 { - args := []string{"'-n'", trimmedArray[0], "'-u'", trimmedArray[1], "'-p'", trimmedArray[2], "'-w'", "'" + basedir + string(os.PathSeparator) + workdirName + "'"} - err := internalRunSetdbparmsCommand(log, "mqsisetdbparms", args) - if err != nil { - return err - } - } else { - return errors.New("Invalid mqsisetdbparms entry - too many parameters") - } - } else { - return errors.New("Invalid mqsisetdbparms entry - too few parameters") - } - } - } - return nil - -} -func runSetdbparmsCommand(log logger.LoggerInterface, command string, params []string) error { - realCommand := command - return runCommand(log, realCommand, params) -} - -func runCommand(log logger.LoggerInterface, command string, params []string) error { - realCommand := "source " + aceInstall + "/mqsiprofile && " + command + " " + strings.Join(params[:], " ") - cmd := exec.Command("/bin/sh", "-c", realCommand) - cmd.Stdin = strings.NewReader("some input") - var stderr bytes.Buffer - cmd.Stderr = &stderr - var stdout bytes.Buffer - cmd.Stdout = &stdout - err := cmd.Run() - if err != nil { - log.Printf("Error executing command: %s %s", stdout.String(), stderr.String()) - - } else { - log.Printf("Successfully executed command.") - } - return err - -} - -func runKeytoolCommand(log logger.LoggerInterface, params []string) error { - return runCommand(log, "keytool", params) - -} - -var internalRunSetdbparmsCommand = runSetdbparmsCommand -var internalRunKeytoolCommand = runKeytoolCommand - -func Contains(a []string, x string) bool { - for _, n := range a { - if x == n { - return true - } - } - return false -} -func main() { -} - -func downloadBarFiles(log logger.LoggerInterface, basedir string, contents []byte) error { - log.Println("Downloading bar file using supplied credentials") - ContentServer = false - log.Debug("Configuration: " + string(contents)) - barAuthParsed, err := gabs.ParseJSON(contents) - if err != nil { - return errors.New("Unable to parse JSON") - } - authType := barAuthParsed.Path("authType").Data().(string) - switch authType { - case "BASIC_AUTH": - return downloadBASIC_AUTH(log, basedir, barAuthParsed) - default: - return errors.New("Unknown barauth type: " + authType) - } -} - -func downloadBASIC_AUTH(log logger.LoggerInterface, basedir string, barAuthParsed *gabs.Container) error { - log.Println("BasicAuth Credentials") - - // Get the SystemCertPool, continue with an empty pool on error - rootCAs, _ := x509.SystemCertPool() - if rootCAs == nil { - rootCAs = x509.NewCertPool() - } - - // Append optional cert to the system pool - if (barAuthParsed.Path("credentials.caCert").Data() != nil) && (barAuthParsed.Path("credentials.caCert").Data().(string) != "") { - caCert := barAuthParsed.Path("credentials.caCert").Data().(string) - if ok := rootCAs.AppendCertsFromPEM([]byte(caCert)); !ok { - return errors.New("CaCert provided but failed to append, Cert provided: " + caCert) - } else { - log.Println("Appending supplied cert via configuration to system pool") - } - } else if (barAuthParsed.Path("credentials.caCertSecret").Data() != nil) && (barAuthParsed.Path("credentials.caCertSecret").Data().(string) != "") { - // Read in the cert file - caCert, err := ioutil.ReadFile(`/home/aceuser/barurlendpoint/ca.crt`) - if err != nil { - return errors.New("CaCertSecret provided but failed to append, Cert provided: " + string(caCert)) - } - if ok := rootCAs.AppendCertsFromPEM(caCert); !ok { - log.Println("No certs appended, using system certs only") - } else { - log.Println("Appending supplied cert via secret to system pool") - } - } else { - log.Println("No certs provided, using system certs only") - } - - var tr *http.Transport - // Allow insecure if insecureSsl is set - if (barAuthParsed.Path("credentials.insecureSsl").Data() != nil) && (barAuthParsed.Path("credentials.insecureSsl").Data().(string) == "true") { - tr = &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 30 * time.Second, - DisableCompression: true, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - RootCAs: rootCAs, - }, - } - log.Println("insecureSsl set so accepting/ignoring all server SSL certificates ") - } else { - tr = &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 30 * time.Second, - DisableCompression: true, - TLSClientConfig: &tls.Config{ - RootCAs: rootCAs, - }, - } - } - client := &http.Client{Transport: tr} - - urls := os.Getenv("ACE_CONTENT_SERVER_URL") - if urls == "" { - return errors.New("No bar url available") - } - - err := os.Mkdir("/home/aceuser/initial-config/bars", os.ModePerm) - if err != nil { - log.Errorf("Error creating directory /home/aceuser/initial-config/bars: %v", err) - return err - } - - urlArray := strings.Split(urls, ",") - for _, url := range urlArray { - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Errorf("Failed creating request - err:", err) - return err - } - - var filename string - - if len(urlArray) == 1 { - // Temporarily override the bar name with "barfile.bar" if we only have ONE bar file until mq connector is fixed to support any bar name - filename = "/home/aceuser/initial-config/bars/barfile.bar" - } else { - // Case where multiple bars. Need to check what file path is available - filename = determineAvailableFilename(log, "/home/aceuser/initial-config/bars/"+path.Base(req.URL.Path)) - } - - file, err := os.Create(filename) - if err != nil { - log.Errorf("Error creating file %v: %v", file, err) - return err - } - defer file.Close() - - req.SetBasicAuth(barAuthParsed.Path("credentials.username").Data().(string), barAuthParsed.Path("credentials.password").Data().(string)) - resp, err := client.Do(req) - if err != nil { - log.Errorf("HTTP call failed - err:", err) - return err - } - if resp.StatusCode != http.StatusOK { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Errorf("Failed to convert body", err) - return err - } - log.Println("Response: " + string(body)) - return errors.New("Non-OK HTTP status: " + strconv.Itoa(resp.StatusCode)) - } else { - log.Println("Downloaded bar file from: " + url) - } - - _, err = io.Copy(file, resp.Body) - if err != nil { - log.Errorf("Error writing file %v: %v", file, err) - return err - } - log.Printf("Saved bar file to " + filename) - } - return nil -} - -func determineAvailableFilename(log logger.LoggerInterface, basepath string) string { - var filename string - filenameBase := basepath - // Initially strip off the .bar at the end if present - if filenameBase[len(filenameBase)-4:] == ".bar" { - filenameBase = filenameBase[:len(filenameBase)-4] - } - isAvailable := false - count := 0 - for !isAvailable { - if count == 0 { - filename = filenameBase + ".bar" - } else { - filename = filenameBase + "-" + fmt.Sprint(count) + ".bar" - log.Printf("Previous path already in use. Testing filename: " + filename) - } - - if _, err := osStat(filename); os.IsNotExist(err) { - log.Printf("No existing file on that path so continuing") - isAvailable = true - } - count++ - } - return filename -} diff --git a/internal/configuration/configuration_test.go b/internal/configuration/configuration_test.go deleted file mode 100644 index 9f4e1e6..0000000 --- a/internal/configuration/configuration_test.go +++ /dev/null @@ -1,1037 +0,0 @@ -package configuration - -import ( - "encoding/base64" - "errors" - "io" - "io/ioutil" - "os" - "testing" - - "github.com/ot4i/ace-docker/common/logger" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/dynamic" -) - -const testBaseDir = "/tmp/tests" - -var testSecretValue = []byte("test secret") - -const secretName = "setdbparms.txt-wb2dc" - -var testLogger, err = logger.NewLogger(os.Stdout, true, true, "test") - -var osMkdirAllRestore = osMkdirAll -var ioutilWriteFileRestore = ioutilWriteFile -var ioutilReadFileRestore = ioutilReadFile -var getSecretRestore = getSecret -var setupClientsRestore = setupClients -var getAllConfigurationsRestore = getAllConfigurations -var osOpenFileRestore = osOpenFile -var ioCopyRestore = ioCopy -var RunCommandRestore = internalRunSetdbparmsCommand -var RunKeytoolCommandRestore = internalRunKeytoolCommand -var setupMqAccountsKdbFileRestore = setupMqAccountsKdbFile - -const policyProjectContent = "UEsDBAoAAAAAAFelclAAAAAAAAAAAAAAAAAJABwAcHJvamVjdDEvVVQJAAPFh3JeyIdyXnV4CwABBPUBAAAEFAAAAFBLAwQUAAAACABgpHJQn5On0w0AAAARAAAAEQAcAHByb2plY3QxL3Rlc3QueG1sVVQJAAPzhXJeHoZyXnV4CwABBPUBAAAEFAAAALMpSS0usQMRNvpgJgBQSwECHgMKAAAAAABXpXJQAAAAAAAAAAAAAAAACQAYAAAAAAAAABAA7UEAAAAAcHJvamVjdDEvVVQFAAPFh3JedXgLAAEE9QEAAAQUAAAAUEsBAh4DFAAAAAgAYKRyUJ+Tp9MNAAAAEQAAABEAGAAAAAAAAQAAAKSBQwAAAHByb2plY3QxL3Rlc3QueG1sVVQFAAPzhXJedXgLAAEE9QEAAAQUAAAAUEsFBgAAAAACAAIApgAAAJsAAAAAAA==" -const genericContent = "UEsDBAoAAAAAAFelclAAAAAAAAAAAAAAAAAJABwAcHJvamVjdDEvVVQJAAPFh3JeyIdyXnV4CwABBPUBAAAEFAAAAFBLAwQUAAAACABgpHJQn5On0w0AAAARAAAAEQAcAHByb2plY3QxL3Rlc3QueG1sVVQJAAPzhXJeHoZyXnV4CwABBPUBAAAEFAAAALMpSS0usQMRNvpgJgBQSwECHgMKAAAAAABXpXJQAAAAAAAAAAAAAAAACQAYAAAAAAAAABAA7UEAAAAAcHJvamVjdDEvVVQFAAPFh3JedXgLAAEE9QEAAAQUAAAAUEsBAh4DFAAAAAgAYKRyUJ+Tp9MNAAAAEQAAABEAGAAAAAAAAQAAAKSBQwAAAHByb2plY3QxL3Rlc3QueG1sVVQFAAPzhXJedXgLAAEE9QEAAAQUAAAAUEsFBgAAAAACAAIApgAAAJsAAAAAAA==" -const adminsslcontent = "UEsDBAoAAAAAAD1epVBBsFEJCgAAAAoAAAAGABwAY2EuY3J0VVQJAAPWRLFe1kSxXnV4CwABBPUBAAAEFAAAAGZha2UgY2VydApQSwECHgMKAAAAAAA9XqVQQbBRCQoAAAAKAAAABgAYAAAAAAABAAAApIEAAAAAY2EuY3J0VVQFAAPWRLFedXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEATAAAAEoAAAAAAA==" -const loopbackdatasourcecontent = "UEsDBBQAAAAIALhhL1E16fencwAAAKQAAAAQABwAZGF0YXNvdXJjZXMuanNvblVUCQAD7KFgX+yhYF91eAsAAQT1AQAABBQAAACrVsrNz0vPT0lSsqpWykvMTVWyUoAJxaeklinpKCXn5+WlJpfkFyFJAYUz8otLQCKGRuZ6BkBoqKSjoFSQXwQSNDI3MDTXUUpJLElMSiwGmwk0y8UJpKS0OLUIZhFQMBTIBetMLC4uzy9KgQoHwLi1tVwAUEsDBAoAAAAAAH1hL1EAAAAAAAAAAAAAAAAGABwAbW9uZ28vVVQJAAN9oWBffaFgX3V4CwABBPUBAAAEFAAAAFBLAQIeAxQAAAAIALhhL1E16fencwAAAKQAAAAQABgAAAAAAAEAAACkgQAAAABkYXRhc291cmNlcy5qc29uVVQFAAPsoWBfdXgLAAEE9QEAAAQUAAAAUEsBAh4DCgAAAAAAfWEvUQAAAAAAAAAAAAAAAAYAGAAAAAAAAAAQAO1BvQAAAG1vbmdvL1VUBQADfaFgX3V4CwABBPUBAAAEFAAAAFBLBQYAAAAAAgACAKIAAAD9AAAAAAA=" - -func restore() { - osMkdirAll = osMkdirAllRestore - ioutilWriteFile = ioutilWriteFileRestore - ioutilReadFile = ioutilReadFileRestore - getSecret = getSecretRestore - getAllConfigurations = getAllConfigurationsRestore - setupClients = setupClientsRestore - osOpenFile = osOpenFileRestore - ioCopy = ioCopyRestore - internalRunSetdbparmsCommand = RunCommandRestore - internalRunKeytoolCommand = RunKeytoolCommandRestore - setupMqAccountsKdbFile = setupMqAccountsKdbFileRestore -} - -func reset() { - // default error mocks - osMkdirAll = func(path string, perm os.FileMode) error { - panic("Should be mocked") - } - ioutilWriteFile = func(fn string, data []byte, perm os.FileMode) error { - panic("Should be mocked") - } - getSecret = func(basedir string, secretName string) ([]byte, error) { - panic("Should be mocked") - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - - panic("Should be mocked") - } - - osOpenFile = func(name string, flat int, perm os.FileMode) (*os.File, error) { - panic("Should be mocked") - } - - ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { - panic("Should be mocked") - } - - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - panic("Should be mocked") - } - - internalRunKeytoolCommand = func(log logger.LoggerInterface, params []string) error { - panic("Should be mocked") - } - - // common mock - setupClients = func() (dynamic.Interface, error) { - return nil, nil - - } - - ioutilReadFile = func(filename string) ([]byte, error) { - return []byte("ace"), nil - } - - setupMqAccountsKdbFile = func(log logger.LoggerInterface) error { - return nil - } -} - -func TestUnzip(t *testing.T) { - zipContents, _ := base64.StdEncoding.DecodeString(policyProjectContent) - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+"project1", path) - return nil - } - osOpenFile = func(name string, flat int, perm os.FileMode) (*os.File, error) { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+"project1"+string(os.PathSeparator)+"test.xml", name) - return &os.File{}, nil - } - - ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { - b, err := ioutil.ReadAll(src) - assert.Equal(t, "test", string(b)) - return 12, nil - } - // test working unzip file - assert.Nil(t, unzip(testLogger, testBaseDir, zipContents)) - // test failing ioCopy - ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { - return 1, errors.New("failed ioCopy") - } - { - err := unzip(testLogger, testBaseDir, zipContents) - if assert.Error(t, err, "Failed to copy file") { - assert.Equal(t, errors.New("failed ioCopy"), err) - } - } - // test open file fail - osOpenFile = func(name string, flat int, perm os.FileMode) (*os.File, error) { - return &os.File{}, errors.New("failed file open") - } - { - err := unzip(testLogger, testBaseDir, zipContents) - if assert.Error(t, err, "Failed to copy file") { - assert.Equal(t, errors.New("failed file open"), err) - } - } - // fail mkdir - osMkdirAll = func(path string, perm os.FileMode) error { - return errors.New("failed mkdir") - } - { - err := unzip(testLogger, testBaseDir, zipContents) - if assert.Error(t, err, "Failed to copy file") { - assert.Equal(t, errors.New("failed mkdir"), err) - } - } -} -func TestSetupConfigurationsFiles(t *testing.T) { - // Test env ACE_CONFIGURATIONS not being set - os.Unsetenv("ACE_CONFIGURATIONS") - reset() - assert.Nil(t, SetupConfigurationsFiles(testLogger, testBaseDir)) - // Test env ACE_CONFIGURATIONS being set - os.Setenv("ACE_CONFIGURATIONS", "server.conf.yaml,odbc.ini") - reset() - configContents := "ZmFrZUZpbGUK" - // Test env ACE_CONFIGURATIONS set to an array of two configurations - reset() - osMkdirAll = func(path string, perm os.FileMode) error { - if path != testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"overrides" && - path != testBaseDir+string(os.PathSeparator)+workdirName { - t.Errorf("Incorrect path for mkdir: %s", path) - } - return nil - } - ioutilWriteFile = func(fn string, data []byte, perm os.FileMode) error { - if fn != testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"overrides"+string(os.PathSeparator)+"server.conf.yaml" && - fn != testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"odbc.ini" { - t.Errorf("Incorrect path for writing to a file: %s", fn) - } - return nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, "server.conf.yaml", cn[0]) - assert.Equal(t, "odbc.ini", cn[1]) - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "server.conf.yaml", - }, - "spec": map[string]interface{}{ - "type": "serverconf", - "contents": configContents, - }, - }, - }, { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "odbc.ini", - }, - "spec": map[string]interface{}{ - "type": "odbc", - "contents": configContents, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFiles(testLogger, testBaseDir)) -} - -func TestRunCommand(t *testing.T) { - // check get an error when command is rubbish - err := runCommand(testLogger, "fakeCommand", []string{"fake"}) - if err == nil { - assert.Equal(t, errors.New("Command should have failed"), err) - } -} - -func TestSetupConfigurationsFilesInternal(t *testing.T) { - - configContents := "ZmFrZUZpbGUK" - // Test missing type fails - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "bad.type") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "bad.type", - }, - "spec": map[string]interface{}{}, - }, - }, - }, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"bad.type"}, testBaseDir) - if assert.Error(t, err, "A configuration must has a type") { - assert.Equal(t, errors.New("A configuration must has a type"), err) - } - } - // Test missing contents fails - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "bad.type") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "bad.type", - }, - "spec": map[string]interface{}{ - "type": "serverconf", - }, - }, - }, - }, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"bad.type"}, testBaseDir) - if assert.Error(t, err, "A configuration with type: serverconf must has a contents field") { - assert.Equal(t, errors.New("A configuration with type: serverconf must has a contents field"), err) - } - } - // Test missing secret fails - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "bad.type") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "bad.type", - }, - "spec": map[string]interface{}{ - "type": "setdbparms", - }, - }, - }, - }, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"bad.type"}, testBaseDir) - if assert.Error(t, err, "A configuration with type: setdbparms must have a secretName field") { - assert.Equal(t, errors.New("A configuration with type: setdbparms must have a secretName field"), err) - } - } // Test secret file is missing - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return nil, errors.New("missing secret file") - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "bad.type") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "bad.type", - }, - "spec": map[string]interface{}{ - "type": "setdbparms", - "secretName": secretName, - }, - }, - }, - }, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"bad.type"}, testBaseDir) - if assert.Error(t, err, "missing secret file") { - assert.Equal(t, errors.New("missing secret file"), err) - } - } - // Test invalid type fails - reset() - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "bad.type") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "bad.type", - }, - "spec": map[string]interface{}{ - "type": "badtype", - "contents": configContents, - }, - }, - }, - }, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"bad.type"}, testBaseDir) - assert.Equal(t, errors.New("Unknown configuration type"), err) - } - // Test base64 decode fails of content - reset() - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "server.conf.yaml") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "server.conf.yaml", - }, - "spec": map[string]interface{}{ - "type": "serverconf", - "contents": "not base64", - }, - }, - }, - }, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"server.conf.yaml"}, testBaseDir) - if assert.Error(t, err, "Fails to decode") { - assert.Equal(t, errors.New("Failed to decode contents"), err) - } - } - - // Test accounts.yaml - reset() - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "accounts-1") - - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "accounts-1", - }, - "spec": map[string]interface{}{ - "type": "accounts", - "secretName": secretName, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"accounts-1"}, testBaseDir)) - // Test agentx.json - reset() - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config/iibswitch/agentx", path) - return nil - } - ioutilWriteFile = func(fn string, data []byte, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config/iibswitch/agentx"+string(os.PathSeparator)+"agentx.json", fn) - return nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "agentx-1") - - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "agentx-1", - }, - "spec": map[string]interface{}{ - "type": "agentx", - "secretName": secretName, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"agentx-1"}, testBaseDir)) - // Test agenta.json - reset() - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config/iibswitch/agenta", path) - return nil - } - ioutilWriteFile = func(fn string, data []byte, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config/iibswitch/agenta"+string(os.PathSeparator)+"agenta.json", fn) - return nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "agenta-1") - - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "agenta-1", - }, - "spec": map[string]interface{}{ - "type": "agenta", - "secretName": secretName, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"agenta-1"}, testBaseDir)) - // Test odbc.ini using contents field - reset() - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName, path) - return nil - } - ioutilWriteFile = func(fn string, data []byte, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"odbc.ini", fn) - return nil - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "odbc-ini") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "odbc-ini", - }, - "spec": map[string]interface{}{ - "type": "odbc", - "contents": configContents, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"odbc-ini"}, testBaseDir)) - // Test Truststore - reset() - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+"truststores", path) - return nil - } - ioutilWriteFile = func(fn string, data []byte, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+"truststores"+string(os.PathSeparator)+"truststore-1", fn) - return nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "truststore-1") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "truststore-1", - }, - "spec": map[string]interface{}{ - "type": "truststore", - "secretName": secretName}, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"truststore-1"}, testBaseDir)) - // Test Keystore - reset() - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+"keystores", path) - return nil - } - ioutilWriteFile = func(fn string, data []byte, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+"keystores"+string(os.PathSeparator)+"keystore-1", fn) - return nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "keystore-1") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "keystore-1", - }, - "spec": map[string]interface{}{ - "type": "keystore", - "secretName": secretName, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"keystore-1"}, testBaseDir)) - // Test setdbparms.txt - reset() - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "setdbparms.txt") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "setdbparms.txt", - }, - "spec": map[string]interface{}{ - "type": "setdbparms", - "secretName": secretName, - }, - }, - }, - }, nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir) - assert.Equal(t, errors.New("Invalid mqsisetdbparms entry - too few parameters"), err) - } - // Test setdbparms with too many parameters - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("name user pass extra"), nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir) - assert.Equal(t, errors.New("Invalid mqsisetdbparms entry - too many parameters"), err) - } - // Test setdbparms with just name, user and password but command fails - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("name user pass"), nil - } - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, command, "mqsisetdbparms") - testParams := []string{"'-n'", "'name'", "'-u'", "'user'", "'-p'", "'pass'", "'-w'", "'/tmp/tests/ace-server'"} - assert.Equal(t, params, testParams) - return errors.New("command fails") - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir) - assert.Equal(t, errors.New("command fails"), err) - } - // Test setdbparms with full command but command fails - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("mqsisetdbparms -n name -u user -p pass -w /tmp/tests/ace-server"), nil - } - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, command, "mqsisetdbparms") - testParams := []string{"'-n'", "'name'", "'-u'", "'user'", "'-p'", "'pass'", "'-w'", "'/tmp/tests/ace-server'"} - assert.Equal(t, params, testParams) - return errors.New("command fails") - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir) - assert.Equal(t, errors.New("command fails"), err) - } - // Test setdbparms with just name, user and password - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("name user pass"), nil - } - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, command, "mqsisetdbparms") - testParams := []string{"'-n'", "'name'", "'-u'", "'user'", "'-p'", "'pass'", "'-w'", "'/tmp/tests/ace-server'"} - assert.Equal(t, params, testParams) - return nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir)) - - // Test setdbparms with several lines, spaces and single quotes - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("\n name1 user1 pass1 \n name2 user2 pass2' "), nil - - } - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, command, "mqsisetdbparms") - var testParams []string - if params[1] == "'name1'" { - testParams = []string{"'-n'", "'name1'", "'-u'", "'user1'", "'-p'", "'pass1'", "'-w'", "'/tmp/tests/ace-server'"} - } else { - testParams = []string{"'-n'", "'name2'", "'-u'", "'user2'", "'-p'", "'pass2'\\'''", "'-w'", "'/tmp/tests/ace-server'"} - - } - assert.Equal(t, params, testParams) - return nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir)) - // Test setdbparms with full syntax - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return []byte("mqsisetdbparms -n name -u user -p pass"), nil - } - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, command, "mqsisetdbparms") - testParams := []string{"'-n'", "'name'", "'-u'", "'user'", "'-p'", "'pass'", "'-w'", "'/tmp/tests/ace-server'"} - assert.Equal(t, params, testParams) - return nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir)) - - // Test setdbparms with spaces and -w included - getSecret = func(basdir string, name string) ([]byte, error) { - - assert.Equal(t, name, secretName) - return []byte("mqsisetdbparms -n name -u user -p pass -w /tmp/tests/ace-server"), nil - } - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, command, "mqsisetdbparms") - testParams := []string{"'-n'", "'name'", "'-u'", "'user'", "'-p'", "'pass'", "'-w'", "'/tmp/tests/ace-server'"} - assert.Equal(t, params, testParams) - return nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"setdbparms.txt"}, testBaseDir)) - - // policy project with an invalid zip file - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "policy-project") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "policy-project", - }, - "spec": map[string]interface{}{ - "type": "policyproject", - "contents": configContents, - }, - }, - }, - }, nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"policy-project"}, testBaseDir) - assert.Equal(t, errors.New("zip: not a valid zip file"), err) - } - // Test adminssl - reset() - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+adminsslName, path) - return nil - } - osOpenFile = func(name string, flat int, perm os.FileMode) (*os.File, error) { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+adminsslName+string(os.PathSeparator)+"ca.crt", name) - return &os.File{}, nil - } - - ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { - b, err := ioutil.ReadAll(src) - assert.Equal(t, "fake cert\n", string(b)) - return 12, nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return base64.StdEncoding.DecodeString(adminsslcontent) - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "adminssl1") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "adminssl1", - }, - "spec": map[string]interface{}{ - "type": "adminssl", - "secretName": secretName, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"adminssl1"}, testBaseDir)) - // Test adminssl with invalid zip file - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("not a zip"), nil - } - - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"adminssl1"}, testBaseDir) - assert.Equal(t, errors.New("zip: not a valid zip file"), err) - } - reset() - // Test generic - osMkdirAll = func(path string, perm os.FileMode) error { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+genericName+string(os.PathSeparator)+"project1", path) - return nil - } - osOpenFile = func(name string, flat int, perm os.FileMode) (*os.File, error) { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+genericName+string(os.PathSeparator)+"project1"+string(os.PathSeparator)+"test.xml", name) - return &os.File{}, nil - } - - ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { - b, err := ioutil.ReadAll(src) - assert.Equal(t, "test", string(b)) - return 12, nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return base64.StdEncoding.DecodeString(genericContent) - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "generic1") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "generic1", - }, - "spec": map[string]interface{}{ - "type": "generic", - "secretName": secretName, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"generic1"}, testBaseDir)) - // Test generic with invalid zip file - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("not a zip"), nil - } - - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"generic1"}, testBaseDir) - assert.Equal(t, errors.New("zip: not a valid zip file"), err) - } - reset() - // Test loopbackdatasource - countMkDir := 0 - osMkdirAll = func(path string, perm os.FileMode) error { - if countMkDir == 0 { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config"+string(os.PathSeparator)+"connectors"+string(os.PathSeparator)+"loopback", path) - } else { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config"+string(os.PathSeparator)+"connectors"+string(os.PathSeparator)+"loopback"+string(os.PathSeparator)+"mongo", path) - } - countMkDir++ - return nil - } - osOpenFile = func(name string, flat int, perm os.FileMode) (*os.File, error) { - assert.Equal(t, testBaseDir+string(os.PathSeparator)+workdirName+string(os.PathSeparator)+"config"+string(os.PathSeparator)+"connectors"+string(os.PathSeparator)+"loopback"+string(os.PathSeparator)+"datasources.json", name) - return &os.File{}, nil - } - - ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { - b, err := ioutil.ReadAll(src) - assert.Equal(t, "{\"mongodb\":{\"name\": \"mongodb_dev\",\"connector\": \"mongodb\",\"host\": \"127.0.0.1\", \"port\": 27017,\"database\": \"devDB\", \"username\": \"devUser\", \"password\": \"devPassword\"}}\n", string(b)) - return 12, nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return base64.StdEncoding.DecodeString(loopbackdatasourcecontent) - } - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "loopback1") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "loopback1", - }, - "spec": map[string]interface{}{ - "type": "loopbackdatasource", - "secretName": secretName, - }, - }, - }, - }, nil - } - assert.Nil(t, SetupConfigurationsFilesInternal(testLogger, []string{"loopback1"}, testBaseDir)) - // Test loopbackdatasource with invalid zip file - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return []byte("not a zip"), nil - } - - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"loopback1"}, testBaseDir) - - assert.Equal(t, errors.New("zip: not a valid zip file"), err) - - } - - // Test truststore certificates - getAllConfigurations = func(log logger.LoggerInterface, ns string, cn []string, dc dynamic.Interface) ([]*unstructured.Unstructured, error) { - assert.Equal(t, cn[0], "truststorecert") - return []*unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Configurations", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "truststorecert", - }, - "spec": map[string]interface{}{ - "type": "truststorecertificate", - "secretName": secretName, - }, - }, - }, - }, nil - } - getSecret = func(basdir string, name string) ([]byte, error) { - assert.Equal(t, name, secretName) - return testSecretValue, nil - } - - // Test keytool command to be called with the correct params - internalRunKeytoolCommandCallCount := 0 - internalRunKeytoolCommand = func(log logger.LoggerInterface, params []string) error { - testParams := []string{"-import", "-file", "-alias", "truststorecert", "-keystore", "$MQSI_JREPATH/lib/security/cacerts", "-storepass", "changeit", "-noprompt", "-storetype", "JKS"} - for i := range params { - if i < 2 { - assert.Equal(t, params[i], testParams[i]) - } else if i > 2 { - assert.Equal(t, params[i], testParams[i-1]) - } - } - internalRunKeytoolCommandCallCount++ - return nil - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"truststorecert"}, testBaseDir) - assert.Equal(t, 1, internalRunKeytoolCommandCallCount) - assert.Equal(t, nil, err) - } - - // Test keytool command throw an error - internalRunKeytoolCommand = func(log logger.LoggerInterface, params []string) error { - return errors.New("command fails") - } - { - err := SetupConfigurationsFilesInternal(testLogger, []string{"truststorecert"}, testBaseDir) - assert.Equal(t, errors.New("command fails"), err) - } - - // restore - restore() -} - -func TestDetermineAvailableFilename(t *testing.T) { - - var osStatRestore = osStat - createdFiles := map[string]bool{} - - var reset = func() { - osStat = func(file string) (os.FileInfo, error) { - if createdFiles[file] { - return nil, os.ErrExist - } else { - return nil, os.ErrNotExist - } - } - } - - var restore = func() { - osStat = osStatRestore - } - - reset() - defer restore() - - t.Run("No existing files just returns the path.bar when basepath has no .bar", func(t *testing.T) { - createdFiles = map[string]bool{} - filename := determineAvailableFilename(testLogger, "my_server/my_bar") - assert.Equal(t, "my_server/my_bar.bar", filename) - }) - - t.Run("No existing files just returns the path.bar when basepath already has .bar", func(t *testing.T) { - createdFiles = map[string]bool{} - filename := determineAvailableFilename(testLogger, "my_server/my_bar.bar") - assert.Equal(t, "my_server/my_bar.bar", filename) - }) - - t.Run("Multiple files with different base paths all save as normal", func(t *testing.T) { - createdFiles = map[string]bool{} - - filename1 := determineAvailableFilename(testLogger, "my_server/my_bar1") - createdFiles[filename1] = true - - filename2 := determineAvailableFilename(testLogger, "my_server/my_bar2") - createdFiles[filename2] = true - - filename3 := determineAvailableFilename(testLogger, "my_server2/my_bar") - createdFiles[filename3] = true - - assert.Equal(t, "my_server/my_bar1.bar", filename1) - assert.Equal(t, "my_server/my_bar2.bar", filename2) - assert.Equal(t, "my_server2/my_bar.bar", filename3) - }) - - t.Run("Multiple files all the same basepath append an incrementing index with no .bar", func(t *testing.T) { - createdFiles = map[string]bool{} - basePath := "my_server/my_bar" - - filename1 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename1] = true - - filename2 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename2] = true - - filename3 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename3] = true - - assert.Equal(t, "my_server/my_bar.bar", filename1) - assert.Equal(t, "my_server/my_bar-1.bar", filename2) - assert.Equal(t, "my_server/my_bar-2.bar", filename3) - }) - - t.Run("Multiple files all the same basepath append an incrementing index and already have a .bar", func(t *testing.T) { - createdFiles = map[string]bool{} - basePath := "my_server/my_bar.bar" - - filename1 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename1] = true - - filename2 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename2] = true - - filename3 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename3] = true - - assert.Equal(t, "my_server/my_bar.bar", filename1) - assert.Equal(t, "my_server/my_bar-1.bar", filename2) - assert.Equal(t, "my_server/my_bar-2.bar", filename3) - }) - - t.Run("Multiple files all the same basepath append an incrementing index with mixed present and missing .bar", func(t *testing.T) { - createdFiles = map[string]bool{} - basePath := "my_server/my_bar.bar" - basePathBar := "my_server/my_bar" - - filename1 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename1] = true - - filename2 := determineAvailableFilename(testLogger, basePathBar) - createdFiles[filename2] = true - - filename3 := determineAvailableFilename(testLogger, basePath) - createdFiles[filename3] = true - - assert.Equal(t, "my_server/my_bar.bar", filename1) - assert.Equal(t, "my_server/my_bar-1.bar", filename2) - assert.Equal(t, "my_server/my_bar-2.bar", filename3) - }) -} diff --git a/internal/configuration/ioutil.go b/internal/configuration/ioutil.go deleted file mode 100644 index 49af32d..0000000 --- a/internal/configuration/ioutil.go +++ /dev/null @@ -1,41 +0,0 @@ -package configuration - -import ( - "io" - "io/ioutil" - "os" -) - -var ioutilReadFile = ioutil.ReadFile -var osMkdirAll = os.MkdirAll -var osMkdir = os.Mkdir -var osCreate = os.Create -var ioutilWriteFile = ioutil.WriteFile -var osOpenFile = os.OpenFile -var ioCopy = io.Copy -var osStat = os.Stat -var osIsNotExist = os.IsNotExist -var osRemoveAll = os.RemoveAll - -var internalAppendFile = func(fileName string, fileContent []byte, filePerm os.FileMode) error { - - file, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, filePerm) - - if err != nil { - return err - } - - defer file.Close() - - _, err = file.Write(fileContent) - - if err == nil { - err = file.Sync() - } - - if err != nil { - return err - } - - return nil -} diff --git a/internal/configuration/techConnectorsConfiguration.go b/internal/configuration/techConnectorsConfiguration.go deleted file mode 100644 index ae18732..0000000 --- a/internal/configuration/techConnectorsConfiguration.go +++ /dev/null @@ -1,951 +0,0 @@ -package configuration - -import ( - "crypto/sha256" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "log" - "math/rand" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - - "github.com/aymerick/raymond" - "github.com/ghodss/yaml" - "github.com/ot4i/ace-docker/common/logger" - yamlv2 "gopkg.in/yaml.v2" -) - -// JdbcCredentials Credentials structure for jdbc credentials object, can be extended for other tech connectors -type JdbcCredentials struct { - AuthType interface{} `json:"authType"` - DbType interface{} `json:"dbType"` - Hostname interface{} `json:"hostname"` - Port interface{} `json:"port"` - DbName interface{} `json:"dbName"` - Username interface{} `json:"username"` - Password interface{} `json:"password"` - MaxPoolSize interface{} `json:"maxPoolSize"` - AdditonalParams interface{} `json:"additonalParams"` -} - -// MQCredentials Credentials structure for mq account credentials object -type MQCredentials struct { - AuthType string `json:"authType"` - QueueManager string `json:"queueManager"` - Hostname string `json:"hostname"` - Port interface{} `json:"port"` - Username string `json:"username"` - Password string `json:"password"` - ChannelName string `json:"channelName"` - CipherSpec string `json:"sslCipherSpec"` - PeerName string `json:"sslPeerName"` - ServerCertificate string `json:"sslServerCertificate"` - ClientCertificate string `json:"sslClientCertificate"` - ClientCertificatePassword string `json:"sslClientCertificatePassword"` - ClientCertificateLabel string `json:"sslClientCertificateLabel"` -} - -// JdbcAccountInfo structure for jdbc connector accounts -type JdbcAccountInfo struct { - Name string - Credentials JdbcCredentials -} - -// MQAccountInfo Account info structure mq connector accounts -type MQAccountInfo struct { - Name string - Credentials MQCredentials -} - -// AccountInfo structure for individual account -type AccountInfo struct { - Name string `json:"name"` - Credentials json.RawMessage `json:"credentials"` -} - -// Accounts connectos account object -type Accounts struct { - Accounts map[string][]AccountInfo `json:"accounts"` -} - -var processMqConnectorAccounts = processMQConnectorAccountsImpl -var processJdbcConnectorAccounts = processJdbcConnectorAccountsImpl -var runOpenSslCommand = runOpenSslCommandImpl -var runMqakmCommand = runMqakmCommandImpl -var createMqAccountsKdbFile = createMqAccountsKdbFileImpl -var setupMqAccountsKdbFile = setupMqAccountsKdbFileImpl -var convertMqAccountSingleLinePEM = convertMQAccountSingleLinePEMImpl -var importMqAccountCertificates = importMqAccountCertificatesImpl -var raymondParse = raymond.Parse - -func convertToString(unknown interface{}) string { - switch unknown.(type) { - case float64: - return fmt.Sprintf("%v", unknown) - case string: - return unknown.(string) - default: - return "" - } -} - -func convertToNumber(unknown interface{}) float64 { - switch i := unknown.(type) { - case float64: - return i - case string: - f, _ := strconv.ParseFloat(i, 64) - return f - default: - return 0 - } -} - -// SetupTechConnectorsConfigurations entry point for all technology connector configurations -func SetupTechConnectorsConfigurations(log logger.LoggerInterface, basedir string, contents []byte) error { - - kdbError := setupMqAccountsKdbFile(log) - - if kdbError != nil { - log.Printf("#SetupTechConnectorsConfigurations setupMqAccountsKdb failed: %v\n", kdbError) - return kdbError - } - - techConnectors := map[string]func(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error{ - "jdbc": processJdbcConnectorAccounts, - "mq": processMqConnectorAccounts} - - log.Println("#SetupTechConnectorsConfigurations: extracting accounts info") - jsonContents, err := yaml.YAMLToJSON(contents) - if err != nil { - log.Printf("#SetupTechConnectorsConfigurations YAMLToJSON: %v\n", err) - return err - } - - var jsonContentsObjForCredParse Accounts - err = json.Unmarshal(jsonContents, &jsonContentsObjForCredParse) - if err != nil { - log.Fatalf("#SetupTechConnectorsConfigurations Unmarshal: %v", err) - return nil - } - - for connector, connectorFunc := range techConnectors { - connectorAccounts := jsonContentsObjForCredParse.Accounts[connector] - - if len(connectorAccounts) > 0 { - log.Printf("Processing connector %s accounts \n", connector) - err := connectorFunc(log, basedir, connectorAccounts) - - if err != nil { - log.Printf("An error occured while proccessing connector accounts %s %v\n", connector, err) - return err - } else { - log.Printf("Connector %s accounts processed %v", connector, len(connectorAccounts)) - } - } else { - log.Printf("No accounts found for connector %s", connector) - } - } - - return nil -} - -func processJdbcConnectorAccountsImpl(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error { - - designerAuthMode, ok := os.LookupEnv("DEVELOPMENT_MODE") - - if ok && designerAuthMode == "true" { - log.Println("Ignore jdbc accounts in designer authoring integration server") - return nil - } - - jdbcAccounts := unmarshalJdbcAccounts(accounts) - err := setDSNForJDBCApplication(log, basedir, jdbcAccounts) - - if err != nil { - log.Printf("#SetupTechConnectorsConfigurations: encountered an error in setDSNForJDBCApplication: %v\n", err) - return err - } - err = buildJDBCPolicies(log, basedir, jdbcAccounts) - if err != nil { - log.Printf("#SetupTechConnectorsConfigurations: encountered an error in buildJDBCPolicies: %v\n", err) - return err - } - - return nil -} - -var setDSNForJDBCApplication = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - log.Println("#setDSNForJDBCApplication: Execute mqsisetdbparms command ") - - for _, accountContent := range jdbcAccounts { - log.Printf("#setDSNForJDBCApplication: setting up config for account - %v\n", accountContent.Name) - jdbcCurrAccountCredInfo := accountContent.Credentials - - hostName := convertToString(jdbcCurrAccountCredInfo.Hostname) - dbPort := convertToString(jdbcCurrAccountCredInfo.Port) - dbName := convertToString(jdbcCurrAccountCredInfo.DbName) - userName := convertToString(jdbcCurrAccountCredInfo.Username) - password := convertToString(jdbcCurrAccountCredInfo.Password) - - if len(hostName) == 0 || len(dbPort) == 0 || len(dbName) == 0 || len(userName) == 0 || len(password) == 0 { - log.Printf("#setDSNForJDBCApplication: skipping executing mqsisetdbparms for account - %v as one of the required fields found empty\n", accountContent.Name) - continue - } - shaInputRawText := hostName + ":" + dbPort + ":" + dbName + ":" + userName - hash := sha256.New() - hash.Write([]byte(shaInputRawText)) - shaHashEncodedText := hex.EncodeToString(hash.Sum(nil)) - args := []string{"'-n'", "jdbc::" + shaHashEncodedText, "'-u'", userName, "'-p'", password, "'-w'", "'" + basedir + string(os.PathSeparator) + workdirName + "'"} - err := internalRunSetdbparmsCommand(log, "mqsisetdbparms", args) - if err != nil { - return err - } - } - return nil -} - -func unmarshalJdbcAccounts(accounts []AccountInfo) []JdbcAccountInfo { - - jdbcAccountsInfo := make([]JdbcAccountInfo, len(accounts)) - - for i, accountInfo := range accounts { - jdbcAccountsInfo[i].Name = accountInfo.Name - json.Unmarshal(accountInfo.Credentials, &jdbcAccountsInfo[i].Credentials) - - } - - return jdbcAccountsInfo -} - -var buildJDBCPolicies = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - var supportedDBs = map[string]string{ - "IBM Db2 Linux, UNIX, or Windows (LUW) - client managed": "db2luw", - "IBM Db2 Linux, UNIX, or Windows (LUW) - IBM Cloud": "db2cloud", - "IBM Db2 for i": "db2i", - "Oracle": "oracle", - "PostgreSQL": "postgresql", - "Microsoft SQL Server": "sqlserver", - } - - policyDirName := basedir + string(os.PathSeparator) + workdirName + string(os.PathSeparator) + "overrides" + string(os.PathSeparator) + "gen.jdbcConnectorPolicies" - log.Printf("#buildJDBCPolicies: jdbc policy directory %v\n", policyDirName) - - policyNameSuffix := ".policyxml" - - policyxmlDescriptor := ` - - - ` - - policyTemplate := ` - - - {{{dbName}}} - {{{dbType}}} - - {{{jdbcClassName}}} - {{{jdbcType4DataSourceName}}} - {{{jdbcURL}}} - - - - - - {{{hostname}}} - {{{port}}} - - useProvidedSchemaNames - - {{{maxPoolSize}}} - {{{securityIdentity}}} - - false - true - - -` - - if _, err := osStat(policyDirName); osIsNotExist(err) { - log.Printf("#buildJDBCPolicies: %v does not exist, creating afresh..", policyDirName) - err := osMkdirAll(policyDirName, os.ModePerm) - if err != nil { - return err - } - } else { - log.Printf("#buildJDBCPolicies: %v already exists", policyDirName) - } - - for _, accountContent := range jdbcAccounts { - log.Printf("#buildJDBCPolicies: building policy for account : \"%v\"", accountContent.Name) - jdbcAccountInfo := accountContent.Credentials - - dbType := convertToString(jdbcAccountInfo.DbType) - hostName := convertToString(jdbcAccountInfo.Hostname) - dbPort := convertToString(jdbcAccountInfo.Port) - dbName := convertToString(jdbcAccountInfo.DbName) - userName := convertToString(jdbcAccountInfo.Username) - password := convertToString(jdbcAccountInfo.Password) - additionalParams := convertToString(jdbcAccountInfo.AdditonalParams) - - if len(hostName) == 0 || len(dbPort) == 0 || len(dbName) == 0 || len(userName) == 0 || len(password) == 0 { - log.Printf("#buildJDBCPolicies: skipping building policy for account - %v as one of the required fields found empty\n", accountContent.Name) - continue - } - - rawText := hostName + ":" + dbPort + ":" + dbName + ":" + userName - hash := sha256.New() - hash.Write([]byte(rawText)) - uuid := hex.EncodeToString(hash.Sum(nil)) - - databaseType := supportedDBs[dbType] - - policyAttributes, err := getJDBCPolicyAttributes(log, databaseType, hostName, dbPort, dbName, additionalParams) - if err != nil { - log.Printf("#buildJDBCPolicies: getJDBCPolicyAttributes returned an error - %v", err) - return err - } - - policyName := databaseType + "-" + uuid - context := map[string]interface{}{ - "policyName": policyName, - "dbName": dbName, - "dbType": databaseType, - "jdbcClassName": policyAttributes["jdbcClassName"], - "jdbcType4DataSourceName": policyAttributes["jdbcType4DataSourceName"], - "jdbcURL": policyAttributes["jdbcURL"], - "hostname": hostName, - "port": convertToNumber(jdbcAccountInfo.Port), - "maxPoolSize": convertToNumber(jdbcAccountInfo.MaxPoolSize), - "securityIdentity": uuid, - } - - result, err := transformXMLTemplate(string(policyTemplate), context) - if err != nil { - log.Printf("#buildJDBCPolicies: failed to transform policy xml - %v", err) - return err - } - - policyFileName := policyName + policyNameSuffix - - err = ioutilWriteFile(policyDirName+string(os.PathSeparator)+policyFileName, []byte(result), os.ModePerm) - if err != nil { - log.Printf("#buildJDBCPolicies: failed to write to the policy file %v - %v", policyFileName, err) - return err - } - } - - err := ioutilWriteFile(policyDirName+string(os.PathSeparator)+"policy.descriptor", []byte(policyxmlDescriptor), os.ModePerm) - if err != nil { - log.Printf("#buildJDBCPolicies: failed to write to the policy.descriptor - %v", err) - return err - } - return nil -} - -func getJDBCPolicyAttributes(log logger.LoggerInterface, dbType, hostname, port, dbName, additonalParams string) (map[string]string, error) { - - var policyAttributes = make(map[string]string) - var classNames = map[string]string{ - "DB2NativeDriverClassName": "com.ibm.db2.jcc.DB2Driver", - "DB2NativeDataSourceClassName": "com.ibm.db2.jcc.DB2XADataSource", - "DB2DriverClassName": "com.ibm.appconnect.jdbc.db2.DB2Driver", - "DB2DataSourceClassName": "com.ibm.appconnect.jdbcx.db2.DB2DataSource", - "OracleDriverClassName": "com.ibm.appconnect.jdbc.oracle.OracleDriver", - "OracleDataSourceClassName": "com.ibm.appconnect.jdbcx.oracle.OracleDataSource", - "MySQLDriverClassName": "com.ibm.appconnect.jdbc.mysql.MySQLDriver", - "MySQLDataSourceClassName": "com.ibm.appconnect.jdbcx.mysql.MySQLDataSource", - "SqlServerDriverClassName": "com.ibm.appconnect.jdbc.sqlserver.SQLServerDriver", - "SqlServerDataSourceClassName": "com.ibm.appconnect.jdbcx.sqlserver.SQLServerDataSource", - "PostgresDriveClassName": "com.ibm.appconnect.jdbc.postgresql.PostgreSQLDriver", - "PostgresDataSourceClassName": "com.ibm.appconnect.jdbcx.postgresql.PostgreSQLDataSource", - "HiveDriverClassName": "com.ibm.appconnect.jdbc.hive.HiveDriver", - "HiveDataSourceClassName": "com.ibm.appconnect.jdbcx.hive.HiveDataSource", - } - - var jdbcURL, jdbcClassName, jdbcType4DataSourceName string - var endDemiliter = "" - - var err error - switch dbType { - case "db2luw", "db2cloud": - jdbcURL = "jdbc:db2://" + hostname + ":" + port + "/" + dbName + ":user=[user];password=[password]" - jdbcClassName = classNames["DB2NativeDriverClassName"] - jdbcType4DataSourceName = classNames["DB2NativeDataSourceClassName"] - endDemiliter = ";" - case "db2i": - jdbcURL = "jdbc:ibmappconnect:db2://" + hostname + ":" + port + ";DatabaseName=" + dbName + ";user=[user];password=[password]" - jdbcClassName = classNames["DB2DriverClassName"] - jdbcType4DataSourceName = classNames["DB2DataSourceClassName"] - case "oracle": - jdbcURL = "jdbc:ibmappconnect:oracle://" + hostname + ":" + port + ";user=[user];password=[password]" - - if !strings.Contains(strings.ToLower(additonalParams), "servicename=") && !strings.Contains(strings.ToLower(additonalParams), "sid=") { - jdbcURL = jdbcURL + ";DatabaseName=" + dbName - } - - if !strings.Contains(strings.ToLower(additonalParams), "fetchdateastimestamp=") { - jdbcURL = jdbcURL + ";FetchDateAsTimestamp=false" - } - - jdbcClassName = classNames["OracleDriverClassName"] - jdbcType4DataSourceName = classNames["OracleDataSourceClassName"] - case "sqlserver": - jdbcURL = "jdbc:ibmappconnect:sqlserver://" + hostname + ":" + port + ";DatabaseName=" + dbName + ";user=[user];password=[password]" - - if !strings.Contains(strings.ToLower(additonalParams), "authenticationmethod=") { - jdbcURL = jdbcURL + ";AuthenticationMethod=userIdPassword" - } - - jdbcClassName = classNames["SqlServerDriverClassName"] - jdbcType4DataSourceName = classNames["SqlServerDataSourceClassName"] - case "postgresql": - jdbcURL = "jdbc:ibmappconnect:postgresql://" + hostname + ":" + port + ";DatabaseName=" + dbName + ";user=[user];password=[password]" - jdbcClassName = classNames["PostgresDriveClassName"] - jdbcType4DataSourceName = classNames["PostgresDataSourceClassName"] - default: - err = errors.New("Unsupported database type: " + dbType) - return nil, err - } - - // default timeout - if !strings.Contains(strings.ToLower(additonalParams), "logintimeout=") { - jdbcURL = jdbcURL + ";loginTimeout=40" - } - - if additonalParams != "" { - jdbcURL = jdbcURL + ";" + additonalParams - } - - if endDemiliter != "" { - jdbcURL += endDemiliter - } - - policyAttributes["jdbcURL"] = jdbcURL - policyAttributes["jdbcClassName"] = jdbcClassName - policyAttributes["jdbcType4DataSourceName"] = jdbcType4DataSourceName - return policyAttributes, err -} - -var processMQConnectorAccountsImpl = func(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error { - - mqAccounts := unmarshalMQAccounts(accounts) - - designerAuthMode, ok := os.LookupEnv("DEVELOPMENT_MODE") - - isDesignerAuthoringMode := false - if ok && designerAuthMode == "true" { - isDesignerAuthoringMode = true - } - - for _, mqAccount := range mqAccounts { - log.Printf("MQ account %v Q Manager %v", mqAccount.Name, mqAccount.Credentials.QueueManager) - err := processMqAccount(log, basedir, mqAccount, isDesignerAuthoringMode) - if err != nil { - log.Printf("#SetupTechConnectorsConfigurations encountered an error while processing mq account %v\n", err) - return err - } - } - - return nil -} - -var unmarshalMQAccounts = func(accounts []AccountInfo) []MQAccountInfo { - - mqAccountsInfo := make([]MQAccountInfo, len(accounts)) - - for i, accountInfo := range accounts { - mqAccountsInfo[i].Name = accountInfo.Name - json.Unmarshal(accountInfo.Credentials, &mqAccountsInfo[i].Credentials) - - } - - return mqAccountsInfo -} - -var processMqAccount = func(log logger.LoggerInterface, baseDir string, mqAccount MQAccountInfo, isDesignerAuthoringMode bool) error { - err := createMqAccountDbParams(log, baseDir, mqAccount) - - if err != nil { - log.Println("#processMQAccounts create db params failed") - return err - } - - err = importMqAccountCertificates(log, mqAccount) - - if err != nil { - log.Printf("Importing of certificates failed for %v", mqAccount.Name) - return err - } - - if isDesignerAuthoringMode { - return nil - } - - err = createMQPolicy(log, baseDir, mqAccount) - if err != nil { - log.Println("#processMQAccounts build mq policies failed") - return err - } - - err = createMQFlowBarOverridesProperties(log, baseDir, mqAccount) - - if err != nil { - log.Println("#processMQAccounts create mq flow bar overrides failed") - return err - } - - return nil -} - -func getMQAccountSHA(mqAccountInfo *MQAccountInfo) string { - mqCredentials := mqAccountInfo.Credentials - shaInputRawText := mqCredentials.Hostname + ":" + convertToString(mqCredentials.Port) + ":" + mqCredentials.QueueManager + ":" + mqCredentials.Username + ":" + mqCredentials.ChannelName - hash := sha256.New() - hash.Write([]byte(shaInputRawText)) - uuid := hex.EncodeToString(hash.Sum(nil)) - return uuid -} - -var createMQPolicy = func(log logger.LoggerInterface, basedir string, mqAccount MQAccountInfo) error { - - policyDirName := basedir + string(os.PathSeparator) + workdirName + string(os.PathSeparator) + "overrides" + string(os.PathSeparator) + "gen.MQPolicies" - log.Printf("#buildMQPolicyies: mq policy directory %v\n", policyDirName) - - policyNameSuffix := ".policyxml" - - policyxmlDescriptor := ` - - - ` - - policyxmlTemplate := ` - - - CLIENT - {{{queueManager}}} - {{{hostName}}} - {{{port}}} - {{{channelName}}} - {{{securityIdentity}}} - {{useSSL}} - {{sslPeerName}} - {{sslCipherSpec}} - {{sslCertificateLabel}} - - -` - - if _, err := osStat(policyDirName); osIsNotExist(err) { - log.Printf("#createMQPolicy: %v does not exist, creating afresh..", policyDirName) - err := osMkdirAll(policyDirName, os.ModePerm) - if err != nil { - return err - } - } else { - log.Printf("#createMQPolicy: %v already exists", policyDirName) - } - - log.Printf("#createMQPolicy: building policy for account : \"%v\"", mqAccount.Name) - - specialCharsRegEx, err := regexp.Compile("[^a-zA-Z0-9]") - mqAccountName := convertToString(mqAccount.Name) - - if err != nil { - log.Printf("#createMQPolicy: Failed to compile regex") - return err - } - - policyName := specialCharsRegEx.ReplaceAllString(mqAccountName, "_") - - securityIdentity := "" - - if mqAccount.Credentials.Username == "" && mqAccount.Credentials.Password == "" { - log.Println("#createMQPolicy - setting security identity empty") - } else { - securityIdentity = "gen_" + getMQAccountSHA(&mqAccount) - } - - useSSL := mqAccount.Credentials.CipherSpec != "" - - context := map[string]interface{}{ - "policyName": policyName, - "queueManager": mqAccount.Credentials.QueueManager, - "hostName": mqAccount.Credentials.Hostname, - "port": mqAccount.Credentials.Port, - "channelName": mqAccount.Credentials.ChannelName, - "securityIdentity": securityIdentity, - "useSSL": useSSL, - "sslPeerName": mqAccount.Credentials.PeerName, - "sslCipherSpec": mqAccount.Credentials.CipherSpec, - "sslCertificateLabel": mqAccount.Credentials.ClientCertificateLabel, - } - - result, err := transformXMLTemplate(string(policyxmlTemplate), context) - if err != nil { - log.Printf("#createMQPolicy: transformXmlTemplate failed with an error - %v", err) - return err - } - - policyFileName := policyName + policyNameSuffix - - err = ioutilWriteFile(policyDirName+string(os.PathSeparator)+policyFileName, []byte(result), os.ModePerm) - if err != nil { - log.Printf("#createMQPolicy: failed to write to the policy file %v - %v", policyFileName, err) - return err - } - - err = ioutilWriteFile(policyDirName+string(os.PathSeparator)+"policy.descriptor", []byte(policyxmlDescriptor), os.ModePerm) - if err != nil { - log.Printf("#createMQPolicy: failed to write to the policy.descriptor - %v", err) - return err - } - return nil -} - -var createMqAccountDbParams = func(log logger.LoggerInterface, basedir string, mqAccount MQAccountInfo) error { - - if mqAccount.Credentials.Username == "" && mqAccount.Credentials.Password == "" { - log.Println("#createMqAccountDbParams - skipping setdbparams empty credentials") - return nil - } - - log.Printf("#createMqAccountDbParams: setdbparams for account - %v\n", mqAccount.Name) - - securityIdentityName := "gen_" + getMQAccountSHA(&mqAccount) - args := []string{"'-n'", "mq::" + securityIdentityName, "'-u'", "'" + mqAccount.Credentials.Username + "'", "'-p'", "'" + mqAccount.Credentials.Password + "'", "'-w'", "'" + basedir + string(os.PathSeparator) + workdirName + "'"} - err := internalRunSetdbparmsCommand(log, "mqsisetdbparms", args) - if err != nil { - return err - } - - return nil -} - -var createMQFlowBarOverridesProperties = func(log logger.LoggerInterface, basedir string, mqAccount MQAccountInfo) error { - log.Println("#createMQFlowBarOverridesProperties: Execute mqapplybaroverride command") - - barOverridesConfigDir := "/home/aceuser/initial-config/workdir_overrides" - - if _, err := osStat(barOverridesConfigDir); osIsNotExist(err) { - err = osMkdirAll(barOverridesConfigDir, os.ModePerm) - if err != nil { - log.Printf("#createMQFlowBarOverridesProperties Failed to create workdir_overrides folder %v", err) - return err - } - } - - specialCharsRegEx, err := regexp.Compile("[^a-zA-Z0-9]") - - if err != nil { - log.Printf("#createMQFlowBarOverridesProperties failed to compile regex") - return err - } - - barPropertiesFileContent := "" - - log.Printf("#createMQFlowBarOverridesProperties: setting up config for account - %v\n", mqAccount.Name) - - mqAccountName := convertToString(mqAccount.Name) - plainAccountName := specialCharsRegEx.ReplaceAllString(mqAccountName, "_") - accountFlowName := "gen.mq_" + plainAccountName - accountSha := getMQAccountSHA(&mqAccount) - accountIDUdfProperty := accountFlowName + "#mqRuntimeAccountId=" + plainAccountName + "~" + accountSha - - barPropertiesFileContent += accountIDUdfProperty + "\n" - - barPropertiesFilePath := "/home/aceuser/initial-config/workdir_overrides/mqconnectorbarfile.properties" - err = internalAppendFile(barPropertiesFilePath, []byte(barPropertiesFileContent), 0644) - - if err != nil { - log.Println("#createMQFlowBarOverridesProperties failed to append to barfile.properties") - return err - } - - return nil -} - -var transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - tpl, err := raymondParse(string(xmlTemplate)) - if err != nil { - log.Printf("#createMQPolicy: failed to parse the template - %v", err) - return "", err - } - - result, err := tpl.Exec(context) - if err != nil { - log.Printf("#createMQPolicy: rendering failed with an error - %v", err) - return "", err - } - - return result, nil -} - -func setupMqAccountsKdbFileImpl(log logger.LoggerInterface) error { - - serverconfMap := make(map[interface{}]interface{}) - - serverConfContents, err := readServerConfFile() - - updateServConf := true - - if err != nil { - log.Println("server.conf.yaml not found, proceeding with creating one") - } else { - - unmarshallError := yamlv2.Unmarshal(serverConfContents, &serverconfMap) - - if unmarshallError != nil { - log.Errorf("Error unmarshalling server.conf.yaml: %v", unmarshallError) - return unmarshallError - } - } - - if serverconfMap["BrokerRegistry"] == nil { - serverconfMap["BrokerRegistry"] = map[string]interface{}{ - "mqKeyRepository": strings.TrimSuffix(getMqAccountsKdbPath(), ".kdb"), - } - } else { - brokerRegistry := serverconfMap["BrokerRegistry"].(map[interface{}]interface{}) - - if brokerRegistry["mqKeyRepository"] == nil { - log.Println("Adding mqKeyRepository to server.conf.yml") - brokerRegistry["mqKeyRepository"] = strings.TrimSuffix(getMqAccountsKdbPath(), ".kdb") - } else { - log.Printf("An existing mq key repository already found in server.conf %v", brokerRegistry["mqKeyRepository"]) - updateServConf = false - } - } - - kdbError := createMqAccountsKdbFile(log) - - if kdbError != nil { - log.Errorf("Error while creating mq accounts kdb file %v\n", kdbError) - return kdbError - } - - serverconfYaml, marshallError := yamlv2.Marshal(&serverconfMap) - - if marshallError != nil { - log.Errorf("Error marshalling server.conf.yaml: %v", marshallError) - return marshallError - } - - if updateServConf { - err = writeServerConfFile(serverconfYaml) - - if err != nil { - log.Errorf("Error while writingg server.conf", err) - return err - } - } - - return nil -} - -var importMqAccountCertificatesImpl = func(log logger.LoggerInterface, mqAccount MQAccountInfo) error { - - tempDir := filepath.FromSlash("/tmp/mqssl-work") - - serverCertPem := filepath.FromSlash("/tmp/mqssl-work/servercrt.pem") - clientCertPem := filepath.FromSlash("/tmp/mqssl-work/clientcrt.pem") - clientCertP12 := filepath.FromSlash("/tmp/mqssl-work/clientcrt.p12") - - var cleanUp = func() { - osRemoveAll(tempDir) - } - - defer cleanUp() - - mkdirErr := osMkdirAll(tempDir, os.ModePerm) - - if mkdirErr != nil { - return fmt.Errorf("Failed to create mp dir to import certificates,Error: %v", mkdirErr.Error()) - } - - if mqAccount.Credentials.ServerCertificate != "" { - serverPem, err := convertMqAccountSingleLinePEM(mqAccount.Credentials.ServerCertificate) - if err != nil { - log.Errorf("An error while creating server certificate PEM for %v, error: %v", mqAccount.Name, err) - return err - } - err = ioutilWriteFile(serverCertPem, []byte(serverPem), os.ModePerm) - - if err != nil { - log.Errorf("An error while saving server certificate pem for %v, error: %v", mqAccount.Name, err) - return err - } - - err = importServerCertificate(log, serverCertPem) - - if err != nil { - log.Errorf("An error while converting server certificate PEM for %v, error: %v", mqAccount.Name, err.Error()) - return err - } - - log.Printf("Imported server certificate for %v", mqAccount.Name) - } - - if mqAccount.Credentials.ClientCertificate != "" { - - if mqAccount.Credentials.ClientCertificateLabel == "" { - return fmt.Errorf("Certificate label should not be empty for %v", mqAccount.Name) - } - - clientPem, err := convertMqAccountSingleLinePEM(mqAccount.Credentials.ClientCertificate) - if err != nil { - log.Errorf("An error while converting client certificate PEM for %v, error: %v", mqAccount.Name, err) - return err - } - - err = ioutilWriteFile(clientCertPem, []byte(clientPem), os.ModePerm) - - if err != nil { - log.Errorf("An error while saving pem client certificate pem for %v, error: %v", mqAccount.Name, err) - return err - } - - p12Pass := randomString(10) - - err = createPkcs12(log, clientCertP12, p12Pass, clientCertPem, mqAccount.Credentials.ClientCertificatePassword, mqAccount.Credentials.ClientCertificateLabel) - - if err != nil { - log.Errorf("An error while creating P12 of %v %v", mqAccount.Name, err) - return err - } - - err = importP12(log, clientCertP12, p12Pass, mqAccount.Credentials.ClientCertificateLabel) - - if err != nil { - log.Errorf("An error while importing P12 of %v %v", mqAccount.Name, err) - return err - } - - log.Printf("Imported client certificate for %v", mqAccount.Name) - - } - - return nil -} - -// readServerConfFile returns the content of the server.conf.yaml file in the overrides folder -func readServerConfFile() ([]byte, error) { - content, err := ioutilReadFile("/home/aceuser/ace-server/overrides/server.conf.yaml") - return content, err -} - -// writeServerConfFile writes the yaml content to the server.conf.yaml file in the overrides folder -// It creates the file if it doesn't already exist -func writeServerConfFile(content []byte) error { - return ioutilWriteFile("/home/aceuser/ace-server/overrides/server.conf.yaml", content, 0644) -} - -func createMqAccountsKdbFileImpl(log logger.LoggerInterface) error { - - mkdirError := osMkdirAll(filepath.FromSlash("/home/aceuser/kdb"), os.ModePerm) - - if mkdirError != nil { - return fmt.Errorf("Failed to create directory to create kdb file, Error %v", mkdirError.Error()) - } - - kdbPass := randomString(10) - - createKdbArgs := []string{"-keydb", "-create", "-type", "cms", "-db", getMqAccountsKdbPath(), "-pw", kdbPass} - err := runMqakmCommand(log, createKdbArgs) - if err != nil { - return fmt.Errorf("Create kdb failed, Error %v ", err.Error()) - } - - createSthArgs := []string{"-keydb", "-stashpw", "-type", "cms", "-db", getMqAccountsKdbPath(), "-pw", kdbPass} - err = runMqakmCommand(log, createSthArgs) - if err != nil { - return fmt.Errorf("Create stash file failed, Error %v", err.Error()) - } - - return nil - -} - -func importServerCertificate(log logger.LoggerInterface, pemFilePath string) error { - - cmdArgs := []string{"-cert", "-add", "-db", getMqAccountsKdbPath(), "-stashed", "-file", pemFilePath} - return runMqakmCommand(log, cmdArgs) -} - -func importP12(log logger.LoggerInterface, p12File string, p12Password string, certLabel string) error { - runMqakmCmdArgs := []string{"-cert", "-import", "-type", "p12", "-file", p12File, "-pw", p12Password, "-target_type", "cms", "-target", getMqAccountsKdbPath(), "-target_stashed"} - - if certLabel != "" { - runMqakmCmdArgs = append(runMqakmCmdArgs, "-new_label") - runMqakmCmdArgs = append(runMqakmCmdArgs, certLabel) - } - - return runMqakmCommand(log, runMqakmCmdArgs) -} - -func createPkcs12(log logger.LoggerInterface, p12OutFile string, p12Password string, pemFilePath string, pemPassword string, certLabel string) error { - openSslCmdArgs := []string{"pkcs12", "-export", "-out", p12OutFile, "-passout", "pass:" + p12Password, "-in", pemFilePath} - - if pemPassword != "" { - openSslCmdArgs = append(openSslCmdArgs, "-passin") - openSslCmdArgs = append(openSslCmdArgs, "pass:"+pemPassword) - } - - return runOpenSslCommand(log, openSslCmdArgs) -} - -func runOpenSslCommandImpl(log logger.LoggerInterface, cmdArgs []string) error { - return runCommand(log, "openssl", cmdArgs) -} - -func runMqakmCommandImpl(log logger.LoggerInterface, cmdArgs []string) error { - return runCommand(log, "runmqakm", cmdArgs) -} - -func getMqAccountsKdbPath() string { - return filepath.FromSlash(`/home/aceuser/kdb/mq.kdb`) -} - -func convertMQAccountSingleLinePEMImpl(singleLinePem string) (string, error) { - pemRegExp, err := regexp.Compile(`(?m)-----BEGIN (.*?)-----\s(.*?)-----END (.*?)-----\s?`) - if err != nil { - return "", err - } - - var pemContent strings.Builder - - matches := pemRegExp.FindAllStringSubmatch(singleLinePem, -1) - - if matches == nil { - return "", errors.New("PEM is not in expected format") - } - - for _, subMatches := range matches { - - if len(subMatches) != 4 { - return "", fmt.Errorf("PEM is not in expected format, found %v", len(subMatches)) - } - - pemContent.WriteString(fmt.Sprintf("-----BEGIN %v-----\n", subMatches[1])) - pemContent.WriteString(strings.ReplaceAll(subMatches[2], " ", "\n")) - pemContent.WriteString(fmt.Sprintf("-----END %v-----\n", subMatches[3])) - } - - return pemContent.String(), nil - -} - -func randomString(n int) string { - var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - - s := make([]rune, n) - for i := range s { - s[i] = letters[rand.Intn(len(letters))] - } - return string(s) -} diff --git a/internal/configuration/techConnectorsConfiguration_test.go b/internal/configuration/techConnectorsConfiguration_test.go deleted file mode 100644 index 0aa64d1..0000000 --- a/internal/configuration/techConnectorsConfiguration_test.go +++ /dev/null @@ -1,2018 +0,0 @@ -package configuration - -import ( - "encoding/json" - "errors" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/ot4i/ace-docker/common/logger" -) - -var processMqAccountRestore = processMqAccount -var processMqConnectorAccountsRestore = processMqConnectorAccounts -var processJdbcConnectorAccountsRestore = processJdbcConnectorAccounts -var mqAccount1Sha = "21659a449a678a235f7c14b8d8a196feedc0691124556624e57f2de60b5729d9" -var jdbcAccount1Sha = "569be438050b9875e183d092b133fa55e0ea57cfcdabe566c2d5613ab8492d50" -var runMqakmCommandRestore = runMqakmCommand -var runOpenSslCommandRestore = runOpenSslCommand -var createMqAccountsKdbFileRestore = createMqAccountsKdbFile - -func TestSetupTechConnectorsConfigurations(t *testing.T) { - - var reset = func() { - processMqAccount = func(log logger.LoggerInterface, basedir string, account MQAccountInfo, isDesignerAuthoringMode bool) error { - panic("should be mocked") - } - - processMqConnectorAccounts = func(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error { - panic("should be mocked") - } - - processJdbcConnectorAccounts = func(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error { - panic("should be mocked") - } - - setupMqAccountsKdbFile = func(log logger.LoggerInterface) error { - panic("should be mocked") - } - - runOpenSslCommand = func(log logger.LoggerInterface, params []string) error { - panic("should be mocked") - } - } - - var restore = func() { - processMqAccount = processMqAccountRestore - processMqConnectorAccounts = processMqConnectorAccountsRestore - processJdbcConnectorAccounts = processJdbcConnectorAccountsRestore - setupMqAccountsKdbFile = setupMqAccountsKdbFileRestore - runMqakmCommand = runMqakmCommandRestore - runOpenSslCommand = runOpenSslCommandRestore - setupMqAccountsKdbFile = setupMqAccountsKdbFileRestore - createMqAccountsKdbFile = createMqAccountsKdbFileRestore - } - - var accountsYaml = ` -accounts: - mq: - - name: mq-account-1 - endpoint: {} - credentials: - authType: BASIC - queueManager: QM1 - hostname: localhost1 - port: 123 - username: abc - password: xyz - channelName: CH.1 - applicationType: online - applicationVersion: v1 - authType: BASIC - default: true -` - - t.Run("Setups mq kdb file before processing accounts", func(t *testing.T) { - - reset() - defer restore() - - t.Run("Returns error if setupMqaccounts failed", func(t *testing.T) { - - setupMqAccountsError := errors.New("set up mq accounts kdb failed") - setupMqAccountsKdbFile = func(log logger.LoggerInterface) error { - return setupMqAccountsError - } - - err := SetupTechConnectorsConfigurations(testLogger, testBaseDir, []byte(accountsYaml)) - - assert.Error(t, err) - }) - - t.Run("when setupMqaccounts succeeded,process accounts ", func(t *testing.T) { - - setupMqAccountsKdbFile = func(log logger.LoggerInterface) error { - return nil - } - - t.Run("when accounts.yml contains mq accounts, process all mq accounts", func(t *testing.T) { - - processMqConnectorAccounts = func(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error { - assert.NotNil(t, accounts) - assert.Equal(t, 1, len(accounts)) - assert.Equal(t, "mq-account-1", accounts[0].Name) - assert.NotNil(t, accounts[0].Credentials) - - return nil - } - - err := SetupTechConnectorsConfigurations(testLogger, testBaseDir, []byte(accountsYaml)) - - assert.Nil(t, err) - }) - - t.Run("when converting accounts.yml to json failed, returns nil ", func(t *testing.T) { - - var invalidYaml = ` -accounts: - mq: - - name: test1 - invalid-entry: abc` - - err := SetupTechConnectorsConfigurations(testLogger, testBaseDir, []byte(invalidYaml)) - - assert.Nil(t, err) - }) - - t.Run("when accounts yaml contains both jdbc and mq accounts, processes both mq and jdbc account", func(t *testing.T) { - var accountsYaml = ` -accounts: - jdbc: - - name: jdbc-account-a - endpoint: {} - credentials: - dbType: 'IBM Db2 Linux, UNIX, or Windows (LUW) - client managed' - dbName: testdb - hostname: dbhost - port: '50000' - username: user1 - password: pwd1 - applicationType: online - applicationVersion: v1 - authType: BASIC - default: true - mq: - - name: mq-account-b - endpoint: {} - credentials: - authType: BASIC - queueManager: QM1 - hostname: localhost1 - port: '123' - username: abc - password: xyz - channelName: CH.1 - applicationType: online - applicationVersion: v1 - authType: BASIC - default: true -` - - processJdbcConnectorAccounts = func(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error { - assert.NotNil(t, accounts) - assert.Equal(t, 1, len(accounts)) - assert.Equal(t, "jdbc-account-a", accounts[0].Name) - assert.NotNil(t, accounts[0].Credentials) - - return nil - } - - processMqConnectorAccounts = func(log logger.LoggerInterface, basedir string, accounts []AccountInfo) error { - assert.NotNil(t, accounts) - assert.Equal(t, 1, len(accounts)) - assert.Equal(t, "mq-account-b", accounts[0].Name) - assert.NotNil(t, accounts[0].Credentials) - - return nil - } - - err := SetupTechConnectorsConfigurations(testLogger, testBaseDir, []byte(accountsYaml)) - - assert.Nil(t, err) - }) - }) - }) -} - -func TestSetMqAccountsKdbFileTests(t *testing.T) { - - var reset = func() { - ioutilReadFile = func(file string) ([]byte, error) { - panic("should be mocked") - } - - createMqAccountsKdbFile = func(log logger.LoggerInterface) error { - panic("should be mocked") - } - - ioutilWriteFile = func(file string, content []byte, perm os.FileMode) error { - panic("should be mocked") - } - } - - var restore = func() { - ioutilWriteFile = ioutilWriteFileRestore - ioutilReadFile = ioutilReadFileRestore - createMqAccountsKdbFile = createMqAccountsKdbFileRestore - } - - reset() - defer restore() - - t.Run("when no server.conf.yaml not found, continues to create kdb file", func(t *testing.T) { - - serverConfFilePath := "/home/aceuser/ace-server/overrides/server.conf.yaml" - ioutilReadFile = func(file string) ([]byte, error) { - assert.Equal(t, serverConfFilePath, file) - return nil, errors.New("File not found") - } - - t.Run("Returns error if failed to create kdb file", func(t *testing.T) { - - createMqAccountsKdbFile = func(log logger.LoggerInterface) error { - return errors.New("create mq accounts kdb failed") - } - - err := setupMqAccountsKdbFile(testLogger) - - assert.Error(t, err, "create mq accounts kdb failed") - }) - }) - - t.Run("when no server.conf.yaml found, continues to create kdb", func(t *testing.T) { - - t.Run("does not update server.conf if it already has mq key repository", func(t *testing.T) { - serverConfContent := "BrokerRegistry:\n mqKeyRepository: /home/aceuser/somekdbfile" - - ioutilReadFile = func(file string) ([]byte, error) { - return []byte(serverConfContent), nil - } - - ioutilWriteFile = func(file string, content []byte, perm os.FileMode) error { - assert.Fail(t, "shouldn't update server conf ") - return nil - } - - setupMqAccountsKdbFile(testLogger) - }) - - t.Run("Returns error if failed to create kdb file when server conf doesnot contain mqKeyRepository", func(t *testing.T) { - - serverConfContent := "" - - ioutilReadFile = func(file string) ([]byte, error) { - return []byte(serverConfContent), nil - } - - createMqAccountsKdbFile = func(log logger.LoggerInterface) error { - return errors.New("Failed to create kdb") - } - - err := setupMqAccountsKdbFile(testLogger) - - assert.Error(t, err, "Failed to create kdb") - }) - }) - - t.Run("when create kdb succeeded, writes server conf yaml with mqKeyRepsitory", func(t *testing.T) { - - createMqAccountsKdbFile = func(log logger.LoggerInterface) error { - return nil - } - - t.Run("Return error when write to server.conf failed", func(t *testing.T) { - - ioutilWriteFile = func(file string, contents []byte, fileMode os.FileMode) error { - return errors.New("server.conf write failed") - } - - err := setupMqAccountsKdbFile(testLogger) - - assert.Error(t, err, "server.conf write failed") - }) - - t.Run("Return nil when write server.conf failed succeeded", func(t *testing.T) { - - ioutilWriteFile = func(file string, contents []byte, fileMode os.FileMode) error { - serverConfContent := "BrokerRegistry:\n mqKeyRepository: /home/aceuser/kdb/mq\n" - serverConfFilePath := "/home/aceuser/ace-server/overrides/server.conf.yaml" - - assert.Equal(t, serverConfFilePath, file) - assert.Equal(t, serverConfContent, string(contents)) - assert.Equal(t, os.FileMode(0644), fileMode) - return nil - } - - err := setupMqAccountsKdbFile(testLogger) - - assert.Nil(t, err) - }) - }) -} - -func TestCreateMqAccountsKdbFileImpl(t *testing.T) { - - var reset = func() { - osMkdirAll = func(path string, filePerm os.FileMode) error { - panic("should be mocked") - } - runMqakmCommand = func(logger logger.LoggerInterface, cmdArgs []string) error { - panic("should be mocked") - } - } - - var restore = func() { - osMkdirAll = osMkdirAllRestore - runMqakmCommand = runMqakmCommandRestore - } - - reset() - defer restore() - - t.Run("creats directory mq-connector for kdb file", func(t *testing.T) { - - t.Run("Returns error if failed to create dir", func(t *testing.T) { - - osMkdirAll = func(path string, filePerm os.FileMode) error { - return errors.New("mkdir failed") - } - - err := createMqAccountsKdbFile(testLogger) - - assert.Error(t, err, "Failed to create directory to create kdb file, Error mkdir failed") - - }) - - t.Run("when create dir succeeded, creates kdb file ", func(t *testing.T) { - - osMkdirAll = func(path string, filePerm os.FileMode) error { - dirPath := "/home/aceuser/kdb" - - assert.Equal(t, dirPath, path) - assert.Equal(t, os.ModePerm, filePerm) - - return nil - } - - t.Run("Returns error when runmqakam failed to create kdb", func(t *testing.T) { - runMqakmCommand = func(logger logger.LoggerInterface, cmdArgs []string) error { - - assert.Equal(t, "-keydb", cmdArgs[0]) - assert.Equal(t, "-create", cmdArgs[1]) - assert.Equal(t, "-type", cmdArgs[2]) - assert.Equal(t, "cms", cmdArgs[3]) - assert.Equal(t, "-db", cmdArgs[4]) - assert.Equal(t, "/home/aceuser/kdb/mq.kdb", cmdArgs[5]) - assert.Equal(t, "-pw", cmdArgs[6]) - assert.NotEmpty(t, cmdArgs[7]) - - return errors.New("runmqakam failed") - } - - err := createMqAccountsKdbFile(testLogger) - - assert.Error(t, err, "Create kdb failed, Error runmqakam failed") - }) - - t.Run("when create kdb succeeded, creates stash file", func(t *testing.T) { - - t.Run("Returns error when create stash failed failed", func(t *testing.T) { - - runMqakmCommand = func(logger logger.LoggerInterface, cmdArgs []string) error { - if cmdArgs[1] == "-stashpw" { - assert.Equal(t, "-keydb", cmdArgs[0]) - assert.Equal(t, "-stashpw", cmdArgs[1]) - assert.Equal(t, "-type", cmdArgs[2]) - assert.Equal(t, "cms", cmdArgs[3]) - assert.Equal(t, "-db", cmdArgs[4]) - assert.Equal(t, "/home/aceuser/kdb/mq.kdb", cmdArgs[5]) - assert.Equal(t, "-pw", cmdArgs[6]) - assert.NotEmpty(t, cmdArgs[7]) - - return errors.New("create sth file failed") - } - - return nil - } - - err := createMqAccountsKdbFile(testLogger) - - assert.Error(t, err, "Create sth failed, Error runmqakam failed") - }) - - t.Run("Returns nil when creation of stash file succeeded", func(t *testing.T) { - - runMqakmCommand = func(logger logger.LoggerInterface, cmdArgs []string) error { - return nil - } - - err := createMqAccountsKdbFile(testLogger) - - assert.Nil(t, err) - }) - }) - }) - }) -} - -func TestProcessJdbcAccounts(t *testing.T) { - var setDSNForJDBCApplicationRestore = setDSNForJDBCApplication - var buildJDBCPoliciesRestore = buildJDBCPolicies - var reset = func() { - setDSNForJDBCApplication = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - panic("should be mocked") - } - - buildJDBCPolicies = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - panic("should be mocked") - } - } - - var restore = func() { - setDSNForJDBCApplication = setDSNForJDBCApplicationRestore - buildJDBCPolicies = buildJDBCPoliciesRestore - } - - accounts := []AccountInfo{ - { - Name: "acc 1", - Credentials: json.RawMessage(`{"authType":"BASIC","dbType":"Oracle","hostname":"abc","port":"123","Username":"user1","Password":"password1","dbName":"test"}`)}} - - unmarshalledJDBCAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Oracle", - Hostname: "abc", - Port: "123", - Username: "user1", - Password: "password1", - DbName: "test"}}} - - reset() - defer restore() - - t.Run("Returns error when failed to setDSN for jdbc account", func(t *testing.T) { - - setDSNError := errors.New("set dsn failed") - setDSNForJDBCApplication = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - return setDSNError - } - - errorReturned := processJdbcConnectorAccounts(testLogger, testBaseDir, accounts) - - assert.Equal(t, setDSNError, errorReturned) - }) - - t.Run("When setDSN succeeded, build jdbc policies", func(t *testing.T) { - setDSNForJDBCApplication = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - assert.Equal(t, unmarshalledJDBCAccounts, jdbcAccounts) - - return nil - } - - t.Run("Returns error when buildJdbcPolicies failed", func(t *testing.T) { - - buildPolicyError := errors.New("set dsn failed") - buildJDBCPolicies = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - return buildPolicyError - } - - errorReturned := processJdbcConnectorAccounts(testLogger, testBaseDir, accounts) - - assert.Equal(t, buildPolicyError, errorReturned) - }) - - t.Run("Returns nil when build jdbcPolicies succeeded", func(t *testing.T) { - - buildJDBCPolicies = func(log logger.LoggerInterface, basedir string, jdbcAccounts []JdbcAccountInfo) error { - assert.Equal(t, unmarshalledJDBCAccounts, jdbcAccounts) - return nil - } - - errorReturned := processJdbcConnectorAccounts(testLogger, testBaseDir, accounts) - - assert.Nil(t, errorReturned) - }) - }) - -} - -func TestSetDSNForJDBCApplication(t *testing.T) { - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Oracle", - Hostname: "abc", - Port: "123", - Username: "user1", - Password: "password1", - DbName: "test"}}} - - var internalRunSetdbparmsCommandRestore = internalRunSetdbparmsCommand - var reset = func() { - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - panic("should be mocked") - } - } - - var restore = func() { - internalRunSetdbparmsCommand = internalRunSetdbparmsCommandRestore - } - - reset() - defer restore() - - t.Run("invokes mqsisetdbparmas command with resource name which is sha of account", func(t *testing.T) { - - resourceName := "jdbc::" + jdbcAccount1Sha - workdir := "'" + testBaseDir + string(os.PathSeparator) + workdirName + "'" - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, "mqsisetdbparms", command) - assert.Equal(t, []string{"'-n'", resourceName, "'-u'", jdbAccounts[0].Credentials.Username.(string), "'-p'", jdbAccounts[0].Credentials.Password.(string), "'-w'", workdir}, params) - return nil - } - - setDSNForJDBCApplication(testLogger, testBaseDir, jdbAccounts) - - }) - - t.Run("when mqsisetdbparmas command failed returns error", func(t *testing.T) { - - err := errors.New("run setdb params failed") - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - return err - } - - errReturned := setDSNForJDBCApplication(testLogger, testBaseDir, jdbAccounts) - - assert.Equal(t, err, errReturned) - }) - - t.Run("when mqsisetdbparmas command succeeds returns nil", func(t *testing.T) { - - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - return nil - } - - errReturned := setDSNForJDBCApplication(testLogger, testBaseDir, jdbAccounts) - - assert.Nil(t, errReturned) - }) -} - -func TestBuildJDBCPolicies(t *testing.T) { - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Oracle", - Hostname: "abc", - Port: "123", - Username: "user1", - Password: "password1", - DbName: "test"}}} - - var osStatRestore = osStat - var osIsNotExistRestore = osIsNotExist - var osMkdirAllRestore = osMkdirAll - var transformXMLTemplateRestore = transformXMLTemplate - var ioutilWriteFileRestore = ioutilWriteFile - - var reset = func() { - osStat = func(dirName string) (os.FileInfo, error) { - panic("should be mocked") - } - osIsNotExist = func(err error) bool { - panic("should be mocked") - } - - osMkdirAll = func(dirName string, filePerm os.FileMode) error { - panic("should be mocked") - } - - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - panic("should be mocked") - } - - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - panic("should be mocked") - } - } - - var restore = func() { - osStat = osStatRestore - osIsNotExist = osIsNotExistRestore - osMkdirAll = osMkdirAllRestore - transformXMLTemplate = transformXMLTemplateRestore - ioutilWriteFile = ioutilWriteFileRestore - } - - policyDirName := testBaseDir + string(os.PathSeparator) + workdirName + string(os.PathSeparator) + "overrides" + string(os.PathSeparator) + "gen.jdbcConnectorPolicies" - - reset() - defer restore() - - t.Run("Returns error when failed to create 'gen.jdbcConnectorPolicies' directory if not exists", func(t *testing.T) { - - osStatError := errors.New("osStat failed") - - osStat = func(dirName string) (os.FileInfo, error) { - assert.Equal(t, policyDirName, dirName) - return nil, osStatError - } - - osIsNotExist = func(err error) bool { - assert.Equal(t, osStatError, err) - return true - } - - osMakeDirError := errors.New("os make dir error failed") - osMkdirAll = func(dirPath string, perm os.FileMode) error { - assert.Equal(t, policyDirName, dirPath) - assert.Equal(t, os.ModePerm, perm) - return osMakeDirError - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - - assert.Equal(t, osMakeDirError, errorReturned) - - t.Run("When creation of 'gen.jdbcConnectorPolicies' succeeds, transforms policy xml template", func(t *testing.T) { - - osMkdirAll = func(dirPath string, perm os.FileMode) error { - assert.Equal(t, policyDirName, dirPath) - assert.Equal(t, os.ModePerm, perm) - return nil - } - - t.Run("Returns error when failed to transform policy xml template", func(t *testing.T) { - - transformError := errors.New("transform xml template failed") - - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - return "", transformError - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Equal(t, transformError, errorReturned) - }) - - t.Run("when transform xml template succeeds, writes transforrmed policy xml in 'gen.MQPolicies' dir with the file name of account name", func(t *testing.T) { - - trasformedXML := "abc" - policyName := "oracle-" + jdbcAccount1Sha - - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": policyName, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "oracle", - "jdbcClassName": "com.ibm.appconnect.jdbc.oracle.OracleDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.oracle.OracleDataSource", - "jdbcURL": "jdbc:ibmappconnect:oracle://abc:123;user=[user];password=[password];DatabaseName=test;FetchDateAsTimestamp=false;loginTimeout=40", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": jdbcAccount1Sha, - }, context) - - return trasformedXML, nil - } - - t.Run("Returns error when failed to write to account name policy file", func(t *testing.T) { - - writeFileError := errors.New("write file failed") - policyFileNameForAccount1 := string(policyDirName + string(os.PathSeparator) + policyName + ".policyxml") - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - assert.Equal(t, policyFileNameForAccount1, filename) - assert.Equal(t, trasformedXML, string(data)) - assert.Equal(t, os.ModePerm, perm) - return writeFileError - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Equal(t, writeFileError, errorReturned) - }) - - t.Run("When write policyxml succeeds, writes policy descriptor", func(t *testing.T) { - - t.Run("Returns error when write policy descriptor failed", func(t *testing.T) { - - policyDescriptorWriteError := errors.New("policy descriptor write failed") - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - _, fileName := filepath.Split(filenameWithPath) - if fileName == "policy.descriptor" { - return policyDescriptorWriteError - } - - return nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Equal(t, policyDescriptorWriteError, errorReturned) - }) - - t.Run("Returns nil when write policy descriptor succeeeded", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - }) - - t.Run("When write policyxml succeeds for Oracle server account", func(t *testing.T) { - t.Run("Returns nil on success", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Oracle", - Hostname: "oracle.com", - Port: "1521", - Username: "user1", - Password: "password1", - DbName: "test", - AdditonalParams: "jdbcBehaviour=1"}}} - - oracleAccountSha := "6a9b827dae7d8af68ec78086404cc9ec64b49da76da996887c2d65d6b9abb96e" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": "oracle-" + oracleAccountSha, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "oracle", - "jdbcClassName": "com.ibm.appconnect.jdbc.oracle.OracleDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.oracle.OracleDataSource", - "jdbcURL": "jdbc:ibmappconnect:oracle://oracle.com:1521;user=[user];password=[password];DatabaseName=test;FetchDateAsTimestamp=false;loginTimeout=40;jdbcBehaviour=1", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": oracleAccountSha, - }, context) - - return trasformedXML, nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - - t.Run("Returns nil on success - overrides DatabaseName attribute with serviceName in the jdbcURL when found in additional params", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Oracle", - Hostname: "oracle.com", - Port: "1521", - Username: "user1", - Password: "password1", - DbName: "test", - AdditonalParams: "serviceName=dummy;jdbcBehaviour=1"}}} - - oracleAccountSha := "6a9b827dae7d8af68ec78086404cc9ec64b49da76da996887c2d65d6b9abb96e" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": "oracle-" + oracleAccountSha, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "oracle", - "jdbcClassName": "com.ibm.appconnect.jdbc.oracle.OracleDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.oracle.OracleDataSource", - "jdbcURL": "jdbc:ibmappconnect:oracle://oracle.com:1521;user=[user];password=[password];FetchDateAsTimestamp=false;loginTimeout=40;serviceName=dummy;jdbcBehaviour=1", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": oracleAccountSha, - }, context) - - return trasformedXML, nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - - t.Run("Returns nil on success - overrides DatabaseName attribute with sid in the jdbcURL when found in additional params", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Oracle", - Hostname: "oracle.com", - Port: "1521", - Username: "user1", - Password: "password1", - DbName: "test", - AdditonalParams: "sid=dummy;jdbcBehaviour=1"}}} - - oracleAccountSha := "6a9b827dae7d8af68ec78086404cc9ec64b49da76da996887c2d65d6b9abb96e" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": "oracle-" + oracleAccountSha, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "oracle", - "jdbcClassName": "com.ibm.appconnect.jdbc.oracle.OracleDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.oracle.OracleDataSource", - "jdbcURL": "jdbc:ibmappconnect:oracle://oracle.com:1521;user=[user];password=[password];FetchDateAsTimestamp=false;loginTimeout=40;sid=dummy;jdbcBehaviour=1", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": oracleAccountSha, - }, context) - - return trasformedXML, nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - - t.Run("Returns nil on success - overrides fetchdateastimestamp in the jdbcURL when found in additional params", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Oracle", - Hostname: "oracle.com", - Port: "1521", - Username: "user1", - Password: "password1", - DbName: "test", - AdditonalParams: "FetchDateAsTimestamp=true;jdbcBehaviour=1"}}} - - oracleAccountSha := "6a9b827dae7d8af68ec78086404cc9ec64b49da76da996887c2d65d6b9abb96e" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": "oracle-" + oracleAccountSha, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "oracle", - "jdbcClassName": "com.ibm.appconnect.jdbc.oracle.OracleDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.oracle.OracleDataSource", - "jdbcURL": "jdbc:ibmappconnect:oracle://oracle.com:1521;user=[user];password=[password];DatabaseName=test;loginTimeout=40;FetchDateAsTimestamp=true;jdbcBehaviour=1", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": oracleAccountSha, - }, context) - - return trasformedXML, nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - }) - - t.Run("When write policyxml succeeds for SQL server account", func(t *testing.T) { - t.Run("Returns nil on success", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Microsoft SQL Server", - Hostname: "sqlserver.com", - Port: "1521", - Username: "user1", - Password: "password1", - DbName: "test", - AdditonalParams: "jdbcBehaviour=1"}}} - - sqlserverAccountSha := "4fcfc055979498885b02297e168978a1baccc8bce130dac5277fca7342a48cdf" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": "sqlserver-" + sqlserverAccountSha, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "sqlserver", - "jdbcClassName": "com.ibm.appconnect.jdbc.sqlserver.SQLServerDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.sqlserver.SQLServerDataSource", - "jdbcURL": "jdbc:ibmappconnect:sqlserver://sqlserver.com:1521;DatabaseName=test;user=[user];password=[password];AuthenticationMethod=userIdPassword;loginTimeout=40;jdbcBehaviour=1", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": sqlserverAccountSha, - }, context) - - return trasformedXML, nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - - t.Run("Returns nil on success - override AuthenticationMethod in the jdbcURL", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Microsoft SQL Server", - Hostname: "sqlserver.com", - Port: "1521", - Username: "user1", - Password: "password1", - DbName: "test", - AdditonalParams: "AuthenticationMethod=ntlmjava;jdbcBehaviour=1"}}} - - sqlserverAccountSha := "4fcfc055979498885b02297e168978a1baccc8bce130dac5277fca7342a48cdf" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": "sqlserver-" + sqlserverAccountSha, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "sqlserver", - "jdbcClassName": "com.ibm.appconnect.jdbc.sqlserver.SQLServerDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.sqlserver.SQLServerDataSource", - "jdbcURL": "jdbc:ibmappconnect:sqlserver://sqlserver.com:1521;DatabaseName=test;user=[user];password=[password];loginTimeout=40;AuthenticationMethod=ntlmjava;jdbcBehaviour=1", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": sqlserverAccountSha, - }, context) - - return trasformedXML, nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - - t.Run("Returns nil on success - override loginTimeout in the jdbcURL", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - jdbAccounts := []JdbcAccountInfo{{ - Name: "acc 1", - Credentials: JdbcCredentials{ - AuthType: "BASIC", - DbType: "Microsoft SQL Server", - Hostname: "sqlserver.com", - Port: "1521", - Username: "user1", - Password: "password1", - DbName: "test", - AdditonalParams: "loginTimeout=100;jdbcBehaviour=1"}}} - - sqlserverAccountSha := "4fcfc055979498885b02297e168978a1baccc8bce130dac5277fca7342a48cdf" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - - assert.Equal(t, map[string]interface{}{ - "policyName": "sqlserver-" + sqlserverAccountSha, - "dbName": jdbAccounts[0].Credentials.DbName, - "dbType": "sqlserver", - "jdbcClassName": "com.ibm.appconnect.jdbc.sqlserver.SQLServerDriver", - "jdbcType4DataSourceName": "com.ibm.appconnect.jdbcx.sqlserver.SQLServerDataSource", - "jdbcURL": "jdbc:ibmappconnect:sqlserver://sqlserver.com:1521;DatabaseName=test;user=[user];password=[password];AuthenticationMethod=userIdPassword;loginTimeout=100;jdbcBehaviour=1", - "hostname": jdbAccounts[0].Credentials.Hostname, - "port": convertToNumber(jdbAccounts[0].Credentials.Port), - "maxPoolSize": convertToNumber(jdbAccounts[0].Credentials.MaxPoolSize), - "securityIdentity": sqlserverAccountSha, - }, context) - - return trasformedXML, nil - } - - errorReturned := buildJDBCPolicies(testLogger, testBaseDir, jdbAccounts) - assert.Nil(t, errorReturned) - }) - }) - }) - }) - }) -} - -func TestProcessMqConnectorAccounts(t *testing.T) { - - var reset = func() { - processMqAccount = func(log logger.LoggerInterface, baseDir string, mqAccount MQAccountInfo, isDesignerAuthoringMode bool) error { - panic("should be mocked") - } - } - var restore = func() { - processMqAccount = processMqAccountRestore - os.Unsetenv("DEVELOPMENT_MODE") - } - - t.Run("Process all mq accounts", func(t *testing.T) { - - reset() - defer restore() - - mqAccounts := []AccountInfo{ - {Name: "acc 1", Credentials: json.RawMessage(`{"authType":"BASIC","Username":"abc","Password":"xyz"}`)}, - {Name: "acc 2", Credentials: json.RawMessage(`{"authType":"BASIC","Username":"u1","Password":"p1"}`)}} - - var callCount = 0 - processMqAccount = func(log logger.LoggerInterface, baseDir string, mqAccount MQAccountInfo, isDesignerAuthoringMode bool) error { - - if callCount == 0 { - assert.Equal(t, "acc 1", mqAccount.Name) - assert.Equal(t, "BASIC", mqAccount.Credentials.AuthType) - assert.Equal(t, "abc", mqAccount.Credentials.Username) - assert.Equal(t, "xyz", mqAccount.Credentials.Password) - } - - if callCount == 1 { - assert.Equal(t, "acc 2", mqAccount.Name) - assert.Equal(t, "BASIC", mqAccount.Credentials.AuthType) - assert.Equal(t, "u1", mqAccount.Credentials.Username) - assert.Equal(t, "p1", mqAccount.Credentials.Password) - } - - callCount++ - return nil - } - - error := processMqConnectorAccounts(testLogger, testBaseDir, mqAccounts) - - assert.Equal(t, 2, callCount) - assert.Nil(t, error) - }) - - t.Run("Returns error if any processMqAccount returned error", func(t *testing.T) { - - reset() - defer restore() - - mqAccounts := []AccountInfo{ - {Name: "acc 1", Credentials: json.RawMessage(`{"authType":"BASIC","Username":"abc","Password":"xyz"}`)}, - {Name: "acc 2", Credentials: json.RawMessage(`{"authType":"BASIC","Username":"u1","Password":"p1"}`)}} - - var callCount = 0 - var err = errors.New("process mq account failed") - processMqAccount = func(log logger.LoggerInterface, baseDir string, mqAccount MQAccountInfo, isDesignerAuthoringMode bool) error { - if callCount == 1 { - return err - } - - callCount++ - return nil - } - - errReturned := processMqConnectorAccounts(testLogger, testBaseDir, mqAccounts) - - assert.Equal(t, err, errReturned) - }) - - t.Run("When running in authoring mode, invokes processAllAccounts with isDesignerAuthoringMode flag true", func(t *testing.T) { - reset() - defer restore() - - os.Setenv("DEVELOPMENT_MODE", "true") - - mqAccounts := []AccountInfo{ - {Name: "acc 1", Credentials: json.RawMessage(`{"authType":"BASIC","Username":"abc","Password":"xyz"}`)}} - - processMqAccount = func(log logger.LoggerInterface, baseDir string, mqAccount MQAccountInfo, isDesignerAuthoringMode bool) error { - assert.True(t, isDesignerAuthoringMode) - return nil - } - - err := processMqConnectorAccounts(testLogger, testBaseDir, mqAccounts) - - assert.Nil(t, err) - }) - - t.Run("When not running in authoring mode, invokes processAllAccounts with isDesignerAuthoringMode flag false", func(t *testing.T) { - reset() - defer restore() - - os.Unsetenv("DEVELOPMENT_MODE") - - mqAccounts := []AccountInfo{ - {Name: "acc 1", Credentials: json.RawMessage(`{"authType":"BASIC","Username":"abc","Password":"xyz"}`)}} - - processMqAccount = func(log logger.LoggerInterface, baseDir string, mqAccount MQAccountInfo, isDesignerAuthoringMode bool) error { - assert.False(t, isDesignerAuthoringMode) - return nil - } - - err := processMqConnectorAccounts(testLogger, testBaseDir, mqAccounts) - - assert.Nil(t, err) - }) -} - -func TestProcessMqAccount(t *testing.T) { - - var createMqAccountDbParamsRestore = createMqAccountDbParams - var createMQPolicyRestore = createMQPolicy - var createMQFlowBarOverridesPropertiesRestore = createMQFlowBarOverridesProperties - var importMqAccountCertificatesRestore = importMqAccountCertificates - - var reset = func() { - createMqAccountDbParams = func(log logger.LoggerInterface, basedir string, mqAccount MQAccountInfo) error { - panic("should be mocked") - } - createMQPolicy = func(log logger.LoggerInterface, basedir string, mqAccount MQAccountInfo) error { - panic("should be mocked") - } - createMQFlowBarOverridesProperties = func(log logger.LoggerInterface, basedir string, mqAccount MQAccountInfo) error { - panic("should be mocked") - } - importMqAccountCertificates = func(log logger.LoggerInterface, mqAccount MQAccountInfo) error { - panic("should be mocked") - } - os.Setenv("ACE_CONTENT_SERVER_URL", "http://localhost/a.bar") - } - - var restore = func() { - createMqAccountDbParams = createMqAccountDbParamsRestore - createMQPolicy = createMQPolicyRestore - createMQFlowBarOverridesProperties = createMQFlowBarOverridesPropertiesRestore - importMqAccountCertificates = importMqAccountCertificatesRestore - - os.Unsetenv("ACE_CONTENT_SERVER_URL") - } - - mqAccount := getMqAccount("acc-1") - - reset() - defer restore() - - t.Run("when creates db params fails returns error", func(t *testing.T) { - - err := errors.New("create db params failed") - createMqAccountDbParams = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - return err - } - - errReturned := processMqAccount(testLogger, testBaseDir, mqAccount, false) - - assert.Equal(t, err, errReturned) - }) - - t.Run("when creates db params succeeds,import certificates", func(t *testing.T) { - - createMqAccountDbParams = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - return nil - } - - t.Run("Returns error when import certificates failed", func(t *testing.T) { - - importError := errors.New("import certificates failed") - importMqAccountCertificates = func(log logger.LoggerInterface, mqAccount MQAccountInfo) error { - return importError - } - - errReturned := processMqAccount(testLogger, testBaseDir, mqAccount, false) - assert.Equal(t, importError, errReturned) - }) - - t.Run("when import certificates succeeded, creates policies", func(t *testing.T) { - - importMqAccountCertificates = func(log logger.LoggerInterface, mqAccount MQAccountInfo) error { - return nil - } - - createMqAccountDbParams = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - assert.Equal(t, mqAccount, mqAccountP) - return nil - } - - t.Run("when running in designer authoring mode, doesn't deploy policy and doesn't create bar overrides file", func(t *testing.T) { - - isDesignerAuthoringMode := true - createMQPolicy = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - assert.Fail(t, "Should not create policies in authoring mode") - return nil - } - - createMQFlowBarOverridesProperties = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - assert.Fail(t, "Should not create policies in authoring mode") - return nil - } - - err := processMqAccount(testLogger, testBaseDir, mqAccount, isDesignerAuthoringMode) - - assert.Nil(t, err) - }) - - t.Run("when create policy fails, returns error", func(t *testing.T) { - - err := errors.New("create mq policy failed") - createMQPolicy = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - return err - } - - errReturned := processMqAccount(testLogger, testBaseDir, mqAccount, false) - - assert.Equal(t, err, errReturned) - }) - - t.Run("when create policy succeeds, creates bar overrides files", func(t *testing.T) { - createMQPolicy = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - assert.Equal(t, mqAccount, mqAccountP) - return nil - } - - t.Run("when create bar ovrrides failed, returns error", func(t *testing.T) { - err := errors.New("create bar overrides failed") - createMQFlowBarOverridesProperties = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - return err - } - - errReturned := processMqAccount(testLogger, testBaseDir, mqAccount, false) - - assert.Equal(t, err, errReturned) - }) - - t.Run("when create bar ovrrides succeeds, returns nil", func(t *testing.T) { - - createMQFlowBarOverridesProperties = func(log logger.LoggerInterface, basedir string, mqAccountP MQAccountInfo) error { - assert.Equal(t, mqAccount, mqAccountP) - return nil - } - - err := processMqAccount(testLogger, testBaseDir, mqAccount, false) - - assert.Nil(t, err) - }) - }) - - }) - }) -} - -func TestCreateMqAccountDbParams(t *testing.T) { - - var internalRunSetdbparmsCommandRestore = internalRunSetdbparmsCommand - var reset = func() { - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - panic("should be mocked") - } - } - - var restore = func() { - internalRunSetdbparmsCommand = internalRunSetdbparmsCommandRestore - } - - t.Run("invokes mqsisetdbparmas command with resource name which is sha of account, username and password arguments", func(t *testing.T) { - - reset() - defer restore() - - mqAccount := getMqAccount("acc1") - resourceName := "mq::gen_" + mqAccount1Sha - workdir := "'" + testBaseDir + string(os.PathSeparator) + workdirName + "'" - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Equal(t, "mqsisetdbparms", command) - assert.Equal(t, []string{"'-n'", resourceName, "'-u'", "'" + mqAccount.Credentials.Username + "'", "'-p'", "'" + mqAccount.Credentials.Password + "'", "'-w'", workdir}, params) - return nil - } - - createMqAccountDbParams(testLogger, testBaseDir, mqAccount) - }) - - t.Run("when mqsisetdbparmas command failed returns error", func(t *testing.T) { - - reset() - defer restore() - - mqAccount := getMqAccount("acc1") - err := errors.New("run setdb params failed") - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - return err - } - - errReturned := createMqAccountDbParams(testLogger, testBaseDir, mqAccount) - - assert.Equal(t, err, errReturned) - }) - - t.Run("when mqsisetdbparmas command succeeds returns nil", func(t *testing.T) { - - reset() - defer restore() - - mqAccount := getMqAccount("acc1") - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - return nil - } - - errReturned := createMqAccountDbParams(testLogger, testBaseDir, mqAccount) - - assert.Nil(t, errReturned) - }) - - t.Run("when username and password fields are empty doesn't execute setdbparams command", func(t *testing.T) { - - reset() - defer restore() - - internalRunSetdbparmsCommand = func(log logger.LoggerInterface, command string, params []string) error { - assert.Fail(t, "setdbparams should not be invoked when username and password fields are empty") - return nil - } - - mqAccount := getMqAccount("acc1") - mqAccount.Credentials.Username = "" - mqAccount.Credentials.Password = "" - - errReturned := createMqAccountDbParams(testLogger, testBaseDir, mqAccount) - - assert.Nil(t, errReturned) - }) -} - -func TestCreateMqPolicy(t *testing.T) { - - var osStatRestore = osStat - var osIsNotExistRestore = osIsNotExist - var osMkdirAllRestore = osMkdirAll - var transformXMLTemplateRestore = transformXMLTemplate - var ioutilWriteFileRestore = ioutilWriteFile - mqAccount := getMqAccount("acc-1") - - var reset = func() { - osStat = func(dirName string) (os.FileInfo, error) { - panic("should be mocked") - } - osIsNotExist = func(err error) bool { - panic("should be mocked") - } - - osMkdirAll = func(dirName string, filePerm os.FileMode) error { - panic("should be mocked") - } - - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - panic("should be mocked") - } - - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - panic("should be mocked") - } - } - - var restore = func() { - osStat = osStatRestore - osIsNotExist = osIsNotExistRestore - osMkdirAll = osMkdirAllRestore - transformXMLTemplate = transformXMLTemplateRestore - ioutilWriteFile = ioutilWriteFileRestore - } - - policyDirName := testBaseDir + string(os.PathSeparator) + workdirName + string(os.PathSeparator) + "overrides" + string(os.PathSeparator) + "gen.MQPolicies" - - t.Run("Returns error when failed to create 'gen.MQPolicies' directory if not exists", func(t *testing.T) { - - reset() - defer restore() - - osStatError := errors.New("osStat failed") - - osStat = func(dirName string) (os.FileInfo, error) { - assert.Equal(t, policyDirName, dirName, mqAccount) - return nil, osStatError - } - - osIsNotExist = func(err error) bool { - assert.Equal(t, osStatError, err) - return true - } - - osMakeDirError := errors.New("os make dir error failed") - osMkdirAll = func(dirPath string, perm os.FileMode) error { - assert.Equal(t, policyDirName, dirPath) - assert.Equal(t, os.ModePerm, perm) - return osMakeDirError - } - - errorReturned := createMQPolicy(testLogger, testBaseDir, mqAccount) - - assert.Equal(t, osMakeDirError, errorReturned) - - t.Run("When creation of 'gen.MQPolicies' succeeds, transforms xml template", func(t *testing.T) { - - osMkdirAll = func(dirPath string, perm os.FileMode) error { - assert.Equal(t, policyDirName, dirPath) - assert.Equal(t, os.ModePerm, perm) - return nil - } - - t.Run("Returns error when failed to transform policy xml template", func(t *testing.T) { - - transformError := errors.New("transform xml template failed") - policyName := "account_1" - - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - assert.Equal(t, map[string]interface{}{ - "policyName": policyName, - "queueManager": mqAccount.Credentials.QueueManager, - "hostName": mqAccount.Credentials.Hostname, - "port": mqAccount.Credentials.Port, - "channelName": mqAccount.Credentials.ChannelName, - "securityIdentity": "gen_" + mqAccount1Sha, - "useSSL": false, - "sslPeerName": "", - "sslCipherSpec": "", - "sslCertificateLabel": "", - }, context) - - return "", transformError - } - - errorReturned := createMQPolicy(testLogger, testBaseDir, mqAccount) - assert.Equal(t, transformError, errorReturned) - }) - - t.Run("Sets security idenity empty with mq account username and password are empty", func(t *testing.T) { - - transformError := errors.New("transform xml template failed") - policyName := "account_1" - - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - assert.Equal(t, map[string]interface{}{ - "policyName": policyName, - "queueManager": mqAccount.Credentials.QueueManager, - "hostName": mqAccount.Credentials.Hostname, - "port": mqAccount.Credentials.Port, - "channelName": mqAccount.Credentials.ChannelName, - "securityIdentity": "", - "useSSL": false, - "sslPeerName": "", - "sslCipherSpec": "", - "sslCertificateLabel": "", - }, context) - - return "", transformError - } - - mqAccountWithEmptyCredentials := getMqAccountWithEmptyCredentials() - - errorReturned := createMQPolicy(testLogger, testBaseDir, mqAccountWithEmptyCredentials) - assert.Equal(t, transformError, errorReturned) - }) - - t.Run("when transform xml template succeeds, writes transforrmed policy xml in 'gen.MQPolicies' dir with the file name of account name", func(t *testing.T) { - - trasformedXML := "abc" - transformXMLTemplate = func(xmlTemplate string, context interface{}) (string, error) { - return trasformedXML, nil - } - - t.Run("Returns error when failed to write to account name policy file", func(t *testing.T) { - - writeFileError := errors.New("write file failed") - policyFileNameForAccount1 := string(policyDirName + string(os.PathSeparator) + "account_1.policyxml") - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - assert.Equal(t, policyFileNameForAccount1, filename) - assert.Equal(t, trasformedXML, string(data)) - assert.Equal(t, os.ModePerm, perm) - return writeFileError - } - - errorReturned := createMQPolicy(testLogger, testBaseDir, mqAccount) - assert.Equal(t, writeFileError, errorReturned) - }) - - t.Run("When write policyxml succeeds, writes policy descriptor", func(t *testing.T) { - - t.Run("Returns error when write policy descriptor failed", func(t *testing.T) { - - policyDescriptorWriteError := errors.New("policy descriptor write failed") - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - _, fileName := filepath.Split(filenameWithPath) - if fileName == "policy.descriptor" { - return policyDescriptorWriteError - } - - return nil - } - - errorReturned := createMQPolicy(testLogger, testBaseDir, mqAccount) - assert.Equal(t, policyDescriptorWriteError, errorReturned) - }) - - t.Run("Returns nil when write policy descriptor succeeeded", func(t *testing.T) { - - ioutilWriteFile = func(filenameWithPath string, data []byte, perm os.FileMode) error { - return nil - } - - errorReturned := createMQPolicy(testLogger, testBaseDir, mqAccount) - assert.Nil(t, errorReturned) - }) - }) - - }) - }) - }) -} - -func TestCreateMQFlowBarOverridesPropertiest(t *testing.T) { - - var osStatRestore = osStat - var osIsNotExistRestore = osIsNotExist - var osMkdirAllRestore = osMkdirAll - var internalAppendFileRestore = internalAppendFile - mqAccount := getMqAccount("acc-1") - - var reset = func() { - osStat = func(dirName string) (os.FileInfo, error) { - panic("should be mocked") - } - osIsNotExist = func(err error) bool { - panic("should be mocked") - } - - osMkdirAll = func(dirName string, filePerm os.FileMode) error { - panic("should be mocked") - } - - internalAppendFile = func(filename string, data []byte, perm os.FileMode) error { - panic("should be mocked") - } - } - - var restore = func() { - osStat = osStatRestore - osIsNotExist = osIsNotExistRestore - osMkdirAll = osMkdirAllRestore - internalAppendFile = internalAppendFileRestore - } - - reset() - defer restore() - - t.Run("Returns error when failed to create workdir_overrides dir if not exist", func(t *testing.T) { - - barOverridesDir := "/home/aceuser/initial-config/workdir_overrides" - osStatError := errors.New("osStat failed") - - osStat = func(dirName string) (os.FileInfo, error) { - assert.Equal(t, barOverridesDir, dirName) - return nil, osStatError - } - - osIsNotExist = func(err error) bool { - assert.Equal(t, osStatError, err) - return true - } - - osMakeDirError := errors.New("os make dir error failed") - - osMkdirAll = func(dirPath string, perm os.FileMode) error { - assert.Equal(t, barOverridesDir, dirPath) - assert.Equal(t, os.ModePerm, perm) - return osMakeDirError - } - - errorReturned := createMQFlowBarOverridesProperties(testLogger, testBaseDir, mqAccount) - - assert.Equal(t, osMakeDirError, errorReturned) - - t.Run("when create barfile overrides dir succeeded, creates barfile.properties with httpendpoint of mq runtime flow", func(t *testing.T) { - - osMkdirAll = func(dirPath string, perm os.FileMode) error { - return nil - } - - t.Run("Returns error when failed to write barfile.properties", func(t *testing.T) { - - barPropertiesFile := "/home/aceuser/initial-config/workdir_overrides/mqconnectorbarfile.properties" - appendFileError := errors.New("append file failed") - - udfProperty := "gen.mq_account_1#mqRuntimeAccountId=account_1~" + mqAccount1Sha + "\n" - internalAppendFile = func(fileName string, fileContent []byte, filePerm os.FileMode) error { - assert.Equal(t, barPropertiesFile, fileName) - assert.Equal(t, udfProperty, string(fileContent)) - return appendFileError - } - - errorReturned := createMQFlowBarOverridesProperties(testLogger, testBaseDir, mqAccount) - assert.Equal(t, appendFileError, errorReturned) - }) - - t.Run("Returns nil when write barfile.properties succeeded", func(t *testing.T) { - - internalAppendFile = func(fileName string, fileContent []byte, filePerm os.FileMode) error { - return nil - } - - errorReturned := createMQFlowBarOverridesProperties(testLogger, testBaseDir, mqAccount) - assert.Nil(t, errorReturned) - }) - }) - }) -} - -func TestImportMqAccountCertificatesTests(t *testing.T) { - - var convertMqAccountSingleLinePEMRestore = convertMqAccountSingleLinePEM - var kdbFilePath = "/home/aceuser/kdb/mq.kdb" - - var reset = func() { - osMkdirAll = func(dirName string, filePerm os.FileMode) error { - panic("should be mocked") - } - runOpenSslCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - panic("should be mocked") - } - - runMqakmCommand = func(logger logger.LoggerInterface, cmdArgs []string) error { - panic("should be mocked") - } - - convertMqAccountSingleLinePEM = func(pem string) (string, error) { - panic("should be mocked") - } - } - - var restore = func() { - osMkdirAll = osMkdirAllRestore - runOpenSslCommand = runOpenSslCommandRestore - runMqakmCommand = runMqakmCommandRestore - convertMqAccountSingleLinePEM = convertMqAccountSingleLinePEMRestore - } - - reset() - defer restore() - - t.Run("Creates temp dir to import certificates", func(t *testing.T) { - - mqAccountWithSsl := getMqAccountWithSsl("acc1") - - t.Run("Returns error if failed to create temp dir", func(t *testing.T) { - osMkdirAll = func(dirName string, filePerm os.FileMode) error { - assert.Equal(t, "/tmp/mqssl-work", dirName) - assert.Equal(t, os.ModePerm, filePerm) - return errors.New("Create temp dir failed") - } - - err := importMqAccountCertificates(testLogger, mqAccountWithSsl) - - assert.Error(t, err, "Failed to create mp dir to import certificates,Error: Create temp dir failed") - - }) - - t.Run("when create temp dir succeeded", func(t *testing.T) { - - osMkdirAll = func(dirName string, filePerm os.FileMode) error { - return nil - } - - t.Run("imports server cerificates if present", func(t *testing.T) { - - t.Run("Returns error if the server certificate is not valid a PEM ", func(t *testing.T) { - pemError := errors.New("Invalid pem error") - convertMqAccountSingleLinePEM = func(content string) (string, error) { - assert.Equal(t, mqAccountWithSsl.Credentials.ServerCertificate, content) - return "", pemError - } - - err := importMqAccountCertificates(testLogger, mqAccountWithSsl) - - assert.Equal(t, pemError, err) - }) - - t.Run("when server certificate is a valid PEM, writes converted PEM to a temp file to import", func(t *testing.T) { - - convertedPem := "abc" - tempServerCertificatePemFile := "/tmp/mqssl-work/servercrt.pem" - convertMqAccountSingleLinePEM = func(content string) (string, error) { - return convertedPem, nil - } - - t.Run("Returns error if write to temp file failed", func(t *testing.T) { - - writeError := errors.New("Wrie temp file failed") - - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - assert.Equal(t, tempServerCertificatePemFile, filename) - assert.Equal(t, convertedPem, string(data)) - assert.Equal(t, os.ModePerm, perm) - - return writeError - } - - err := importMqAccountCertificates(testLogger, mqAccountWithSsl) - - assert.Equal(t, writeError, err) - }) - - t.Run("when write to temp file succeeded, runs mqakam command to import certificate to kdb", func(t *testing.T) { - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - return nil - } - - t.Run("Return error if runmqakm command failed", func(t *testing.T) { - cmdError := errors.New("command error") - runMqakmCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - return cmdError - } - - err := importMqAccountCertificates(testLogger, mqAccountWithSsl) - - assert.Equal(t, cmdError, err) - - }) - - t.Run("Returns nil When runMqakm command succeeded to import certificate", func(t *testing.T) { - - runMqakmCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - assert.Equal(t, "-cert", cmdArgs[0]) - assert.Equal(t, "-add", cmdArgs[1]) - assert.Equal(t, "-db", cmdArgs[2]) - assert.Equal(t, kdbFilePath, cmdArgs[3]) - assert.Equal(t, "-stashed", cmdArgs[4]) - assert.Equal(t, "-file", cmdArgs[5]) - assert.Equal(t, tempServerCertificatePemFile, cmdArgs[6]) - return nil - } - - err := importMqAccountCertificates(testLogger, mqAccountWithSsl) - - assert.Nil(t, err) - }) - }) - - }) - }) - - t.Run("imports client cerificate if present", func(t *testing.T) { - - mqAccountWithMutualSsl := getMqAccountWithMutualAuthSsl("acc1") - mqAccountWithMutualSsl.Credentials.ServerCertificate = "" - - t.Run("Returns error if the client certificate label is empty", func(t *testing.T) { - - mqAccountWithEmptyCertLabel := getMqAccountWithMutualAuthSsl("acc1") - mqAccountWithEmptyCertLabel.Credentials.ClientCertificateLabel = "" - err := importMqAccountCertificates(testLogger, mqAccountWithEmptyCertLabel) - - assert.Error(t, err, "Certificate label should not be empty for acc1") - }) - - t.Run("Returns error if the client certificate is not valid a PEM ", func(t *testing.T) { - pemError := errors.New("Invalid pem error") - convertMqAccountSingleLinePEM = func(content string) (string, error) { - assert.Equal(t, mqAccountWithMutualSsl.Credentials.ClientCertificate, content) - return "", pemError - } - - err := importMqAccountCertificates(testLogger, mqAccountWithMutualSsl) - - assert.Equal(t, pemError, err) - }) - - t.Run("when client certificate is a valid PEM, writes converted PEM to a temp file to import", func(t *testing.T) { - - convertedPem := "abc" - tempClientCertificatePemFile := "/tmp/mqssl-work/clientcrt.pem" - convertMqAccountSingleLinePEM = func(content string) (string, error) { - return convertedPem, nil - } - - t.Run("Returns error when write to temp file failed", func(t *testing.T) { - - writeError := errors.New("Write client pem temp file failed") - - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - assert.Equal(t, tempClientCertificatePemFile, filename) - assert.Equal(t, convertedPem, string(data)) - assert.Equal(t, os.ModePerm, perm) - - return writeError - } - - err := importMqAccountCertificates(testLogger, mqAccountWithMutualSsl) - - assert.Error(t, err, "Write client pem temp file failed") - }) - - t.Run("when write to temp file succeeded, creates pkcs12 file usingg openssl command to import to kdb", func(t *testing.T) { - ioutilWriteFile = func(filename string, data []byte, perm os.FileMode) error { - return nil - } - - tempP12File := "/tmp/mqssl-work/clientcrt.p12" - - t.Run("runs openssl command to create p12 with required arguments when no password present", func(t *testing.T) { - - runOpenSslCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - assert.Equal(t, "pkcs12", cmdArgs[0]) - assert.Equal(t, "-export", cmdArgs[1]) - assert.Equal(t, "-out", cmdArgs[2]) - assert.Equal(t, tempP12File, cmdArgs[3]) - assert.Equal(t, "-passout", cmdArgs[4]) - assert.NotEmpty(t, cmdArgs[5]) - assert.NotEmpty(t, "-in", cmdArgs[6]) - assert.Equal(t, tempClientCertificatePemFile, cmdArgs[7]) - - return errors.New("open ssl failed") - } - - err := importMqAccountCertificates(testLogger, mqAccountWithMutualSsl) - - assert.NotNil(t, err) - }) - - t.Run("when password is parent runs openssl command password in flag", func(t *testing.T) { - - mqAccountWithPassword := getMqAccountWithMutualAuthSsl("acc1") - mqAccountWithPassword.Credentials.ClientCertificatePassword = "p123" - - runOpenSslCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - - assert.NotEmpty(t, "-passin", cmdArgs[8]) - assert.Equal(t, "pass:p123", cmdArgs[9]) - - return errors.New("open ssl failed") - } - - err := importMqAccountCertificates(testLogger, mqAccountWithPassword) - - assert.NotNil(t, err) - }) - - t.Run("Return error if openssl command failed to create p12 file failed", func(t *testing.T) { - cmdError := errors.New("command error") - runOpenSslCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - return cmdError - } - - err := importMqAccountCertificates(testLogger, mqAccountWithMutualSsl) - - assert.Equal(t, cmdError, err) - - }) - - t.Run("When creation of p12 file from pem succeeded runs runmqakm command to import p12 file to kdb", func(t *testing.T) { - - runOpenSslCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - return nil - } - - t.Run("Returns error if runmqakm command failed to import p12 file", func(t *testing.T) { - - cmdErr := errors.New("runmqakm failed") - runMqakmCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - return cmdErr - } - err := importMqAccountCertificates(testLogger, mqAccountWithMutualSsl) - assert.Equal(t, cmdErr, err) - }) - - t.Run("Returns nil when runmqakm command succeeded", func(t *testing.T) { - - runMqakmCommand = func(log logger.LoggerInterface, cmdArgs []string) error { - assert.Equal(t, "-cert", cmdArgs[0]) - assert.Equal(t, "-import", cmdArgs[1]) - assert.Equal(t, "-type", cmdArgs[2]) - assert.Equal(t, "p12", cmdArgs[3]) - assert.Equal(t, "-file", cmdArgs[4]) - assert.Equal(t, tempP12File, cmdArgs[5]) - assert.Equal(t, "-pw", cmdArgs[6]) - assert.NotEmpty(t, cmdArgs[7]) - assert.NotEmpty(t, "-target_type", cmdArgs[8]) - assert.NotEmpty(t, "cms", cmdArgs[9]) - assert.NotEmpty(t, "-target", cmdArgs[10]) - assert.Equal(t, kdbFilePath, cmdArgs[11]) - assert.NotEmpty(t, "-target_stashed", cmdArgs[12]) - - return nil - } - - err := importMqAccountCertificates(testLogger, mqAccountWithMutualSsl) - assert.Nil(t, err) - }) - }) - }) - }) - }) - }) - }) -} - -func TestConvertMQAccountSingleLinePEM(t *testing.T) { - - t.Run("Returns error if no PEM headers and footers present in the PEM line", func(t *testing.T) { - pemLine := "abc no pem headers and footers" - - _, err := convertMqAccountSingleLinePEM(pemLine) - - assert.NotNil(t, err) - }) - - t.Run("Returns error if not a valid PEM", func(t *testing.T) { - pemLineInvalidFooter := "-----BEGIN CERTIFICATE----- line1 line2 -----END CERTIFICATE" - - _, err := convertMqAccountSingleLinePEM(pemLineInvalidFooter) - - assert.NotNil(t, err) - }) - - t.Run("Returns error if not a valid PEM", func(t *testing.T) { - pemLineInvalidHeader := "-----BEGIN----- line1 line2 -----END CERTIFICATE-----" - - _, err := convertMqAccountSingleLinePEM(pemLineInvalidHeader) - - assert.NotNil(t, err) - }) - - t.Run("Returns multiline PEM", func(t *testing.T) { - expectedPem := "-----BEGIN CERTIFICATE-----\nline1\nline2\n-----END CERTIFICATE-----\n-----BEGIN ENCRYPTED PRIVATE KEY-----\nkeyline1\nkeyline2\n-----END ENCRYPTED PRIVATE KEY-----\n" - pemLineInvalidHeader := "-----BEGIN CERTIFICATE----- line1 line2 -----END CERTIFICATE----- -----BEGIN ENCRYPTED PRIVATE KEY----- keyline1 keyline2 -----END ENCRYPTED PRIVATE KEY-----" - - multiLinePem, err := convertMqAccountSingleLinePEM(pemLineInvalidHeader) - - assert.Nil(t, err) - assert.Equal(t, expectedPem, multiLinePem) - - }) -} - -func getMqAccount(accountName string) MQAccountInfo { - return MQAccountInfo{ - Name: "account-1", - Credentials: MQCredentials{ - AuthType: "BASIC", - QueueManager: "QM1", - Hostname: "host1", - Port: 123, - Username: "abc", - Password: "xyz", - ChannelName: "testchannel"}} -} - -func getMqAccountWithEmptyCredentials() MQAccountInfo { - return MQAccountInfo{ - Name: "account-1", - Credentials: MQCredentials{ - AuthType: "BASIC", - QueueManager: "QM1", - Hostname: "host1", - Port: 123, - Username: "", - Password: "", - ChannelName: "testchannel"}} -} - -func getMqAccountWithSsl(accountName string) MQAccountInfo { - return MQAccountInfo{ - Name: "account-1", - Credentials: MQCredentials{ - AuthType: "BASIC", - QueueManager: "QM1", - Hostname: "host1", - Port: 123, - Username: "abc", - Password: "xyz", - ChannelName: "testchannel", - ServerCertificate: "-----BEGIN CERTIFICATE----- Line1Content Line2Content -----END CERTIFICATE-----", - }} -} - -func getMqAccountWithMutualAuthSsl(accountName string) MQAccountInfo { - return MQAccountInfo{ - Name: "account-1", - Credentials: MQCredentials{ - AuthType: "BASIC", - QueueManager: "QM1", - Hostname: "host1", - Port: 123, - Username: "abc", - Password: "xyz", - ChannelName: "testchannel", - ServerCertificate: "-----BEGIN CERTIFICATE----- Line1Content Line2Content -----END CERTIFICATE-----", - ClientCertificate: "-----BEGIN CERTIFICATE----- Line1Content Line2Content -----END CERTIFICATE-----", - ClientCertificatePassword: "abcd", - ClientCertificateLabel: "somelabel", - }} -} diff --git a/internal/isCommandsApi/apiServer_test.go b/internal/isCommandsApi/apiServer_test.go deleted file mode 100644 index 59ca56e..0000000 --- a/internal/isCommandsApi/apiServer_test.go +++ /dev/null @@ -1,250 +0,0 @@ -package iscommandsapi - -import ( - "errors" - "io" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/ot4i/ace-docker/common/logger" - "github.com/stretchr/testify/assert" -) - -var startHTTPServerRestore = startHTTPServer -var restartIntegrationServerFuncRestore = restartIntegrationServerFunc -var runCommandRestore func(name string, args ...string) (string, int, error) = runCommand - -type testCommandHandler struct { - executeHandler func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) -} - -func (me *testCommandHandler) execute(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - return me.executeHandler(log, body) -} - -func reset() { - startHTTPServer = func(log logger.LoggerInterface, portNo int) *http.Server { - panic("Start http server should be mocked") - } - - restartIntegrationServerFunc = func() error { - panic("Restart integration server should be mocked") - } - - runCommand = func(name string, args ...string) (string, int, error) { - panic("Run command should be implemented") - } -} - -func restore() { - runCommand = runCommandRestore - startHTTPServer = startHTTPServerRestore - restartIntegrationServerFunc = restartIntegrationServerFuncRestore -} - -func TestStartCommandsAPIServer(t *testing.T) { - - var testLogger, _ = logger.NewLogger(os.Stdout, true, true, "test") - restartIsFunc := func() error { return nil } - - t.Run("When restart func is nil, returns error", func(t *testing.T) { - reset() - defer restore() - - err := StartCommandsAPIServer(testLogger, 123, nil) - - assert.NotNil(t, err) - assert.Equal(t, "Restart handler should not be nil", err.Error()) - }) - - t.Run("registers setdbparms command handler and invokes startHTTPServer with specified portNo", func(t *testing.T) { - reset() - defer restore() - - var logP logger.LoggerInterface - var httpServerInstance = &http.Server{} - var portNoP int - startHTTPServer = func(log logger.LoggerInterface, portNo int) *http.Server { - logP = log - portNoP = portNo - - return httpServerInstance - } - - err := StartCommandsAPIServer(testLogger, 123, restartIsFunc) - - assert.NotNil(t, commandsHandler["setdbparms"]) - assert.Nil(t, err) - assert.Equal(t, logP, testLogger) - assert.Equal(t, portNoP, 123) - assert.Equal(t, httpServerInstance, httpServer) - - }) -} - -func TestCommandRequestHttpHandler(t *testing.T) { - - var request *http.Request - var response *httptest.ResponseRecorder - var handler http.HandlerFunc - var testCommandURL string = "/commands/test" - - setupTestCommand := func(requestURL string, executeCommandFunc func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError)) { - restartIntegrationServerFunc = func() error { - return nil - } - - handler = http.HandlerFunc(commandRequestHandler) - request, _ = http.NewRequest("POST", requestURL, nil) - response = httptest.NewRecorder() - - testCommandHadler := testCommandHandler{executeHandler: executeCommandFunc} - handleCRUDCommand("test", &testCommandHadler) - } - - t.Run("Returns not found when url doesn't have command ", func(t *testing.T) { - reset() - setupTestCommand("/commands", nil) - defer restore() - - handler.ServeHTTP(response, request) - assert.Equal(t, http.StatusNotFound, response.Code) - assert.Equal(t, `{"success":false,"message":"Not found"}`+"\n", response.Body.String()) - }) - - t.Run("when no handler registered for the command, returns not found", func(t *testing.T) { - reset() - defer restore() - - setupTestCommand("/commands/test2", nil) - - handler.ServeHTTP(response, request) - assert.Equal(t, http.StatusNotFound, response.Code) - assert.Equal(t, `{"success":false,"message":"Not found"}`+"\n", response.Body.String()) - }) - - t.Run("Invokes registered command handlers with request body", func(t *testing.T) { - reset() - defer restore() - - called := false - executeCommandFunc := func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - called = true - return &commandResponse{message: "ok"}, nil - } - - setupTestCommand(testCommandURL, executeCommandFunc) - - handler.ServeHTTP(response, request) - - assert.Equal(t, true, called) - }) - - t.Run("when command handler returns message, responds with status ok and message", func(t *testing.T) { - reset() - defer restore() - - executeCommandFunc := func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - return &commandResponse{message: "command executed", restartIs: true}, nil - } - - setupTestCommand(testCommandURL, executeCommandFunc) - - handler.ServeHTTP(response, request) - - assert.Equal(t, http.StatusOK, response.Code) - assert.Equal(t, `{"success":true,"message":"command executed"}`+"\n", response.Body.String()) - }) - - t.Run("when command handler returns invalid input, responds with bad request", func(t *testing.T) { - reset() - defer restore() - - executeCommandFunc := func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - return nil, &commandError{error: "Invalid input", errorCode: commandErrorInvalidInput} - } - - setupTestCommand(testCommandURL, executeCommandFunc) - - handler.ServeHTTP(response, request) - - assert.Equal(t, http.StatusBadRequest, response.Code) - assert.Equal(t, `{"success":false,"message":"Invalid input"}`+"\n", response.Body.String()) - }) - - t.Run("when command handler returns internal error, responds with internal server error", func(t *testing.T) { - reset() - defer restore() - - executeCommandFunc := func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - return nil, &commandError{error: "Internal error while invkoing command", errorCode: commandErrorInternal} - } - - setupTestCommand(testCommandURL, executeCommandFunc) - - handler.ServeHTTP(response, request) - - assert.Equal(t, http.StatusInternalServerError, response.Code) - assert.Equal(t, `{"success":false,"message":"Internal error while invkoing command"}`+"\n", response.Body.String()) - }) - - t.Run("when command handler returns restartIs with true in response, invokes restart integration server func", func(t *testing.T) { - reset() - defer restore() - - executeCommandFunc := func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - return &commandResponse{message: "command executed", restartIs: true}, nil - } - - setupTestCommand(testCommandURL, executeCommandFunc) - restartIsCalled := false - - restartIntegrationServerFunc = func() error { - restartIsCalled = true - return nil - } - - handler.ServeHTTP(response, request) - - assert.Equal(t, true, restartIsCalled) - }) - - t.Run("when restart inegration server failed, returns internal server error", func(t *testing.T) { - reset() - defer restore() - - executeCommandFunc := func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - return &commandResponse{message: "command executed", restartIs: true}, nil - } - setupTestCommand(testCommandURL, executeCommandFunc) - - restartIsCalled := false - restartIntegrationServerFunc = func() error { - restartIsCalled = true - return errors.New("restart failed") - } - - handler.ServeHTTP(response, request) - - assert.Equal(t, true, restartIsCalled) - assert.Equal(t, http.StatusInternalServerError, response.Code) - assert.Equal(t, `{"success":false,"message":"Integration server restart failed"}`+"\n", response.Body.String()) - }) - - t.Run("when restart inegration server is nill, returns integration server has not restarted message", func(t *testing.T) { - reset() - defer restore() - executeCommandFunc := func(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - return &commandResponse{message: "command executed", restartIs: true}, nil - } - - setupTestCommand(testCommandURL, executeCommandFunc) - restartIntegrationServerFunc = nil - handler.ServeHTTP(response, request) - - assert.Equal(t, http.StatusInternalServerError, response.Code) - assert.Equal(t, `{"success":false,"message":"Integration server has not restarted"}`+"\n", response.Body.String()) - }) -} diff --git a/internal/isCommandsApi/apiserver.go b/internal/isCommandsApi/apiserver.go deleted file mode 100644 index 7b5b14d..0000000 --- a/internal/isCommandsApi/apiserver.go +++ /dev/null @@ -1,193 +0,0 @@ -package iscommandsapi - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "regexp" - "time" - - "github.com/ot4i/ace-docker/common/logger" -) - -var log logger.LoggerInterface - -type commandResponse struct { - message string - restartIs bool -} - -type commandError struct { - error string - errorCode int -} - -const commandErrorInternal = 1 -const commandErrorInvalidInput = 2 - -type commandHandlerInterface interface { - execute(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) -} - -type handlerInvocationDetails struct { - handler commandHandlerInterface -} - -type apiResponse struct { - Success bool `json:"success"` - Message string `json:"message"` -} - -var commandsHandler map[string]handlerInvocationDetails = make(map[string]handlerInvocationDetails) -var restartIntegrationServerFunc func() error -var httpServer *http.Server = nil - -func writeRequestesponse(writer http.ResponseWriter, statusCode int, message string) { - writer.Header().Set("Content-Type", "application/json") - writer.WriteHeader(statusCode) - - apiReturn := apiResponse{} - apiReturn.Success = statusCode == http.StatusOK - apiReturn.Message = message - - json.NewEncoder(writer).Encode(&apiReturn) -} - -func commandRequestHandler(writer http.ResponseWriter, request *http.Request) { - url := request.URL.Path - - log.Printf("#commandRequestHandler serving %f, method %s", url, request.Method) - apiRegEx := regexp.MustCompile("^/commands/(\\w*)/?(.*)?$") - - matches := apiRegEx.FindStringSubmatch(url) - - var err error = nil - if len(matches) != 3 { - log.Printf("#commandRequestHandler url doesn't have expected 3 tokens", url) - writeRequestesponse(writer, http.StatusNotFound, "Not found") - return - } - - command := matches[1] - commandHandler, found := commandsHandler[command] - if !found { - log.Printf("#commandRequestHandler No hanlders found for %s", command) - writeRequestesponse(writer, http.StatusNotFound, "Not found") - return - } - - restartIs := false - - apiResponse := "" - - switch request.Method { - case http.MethodPost: - commandResponse, commandError := commandHandler.handler.execute(log, request.Body) - - if commandError != nil { - log.Printf("#commandRequestHandler an error occurred while processing command %s, error %s", command, commandError.error) - - if commandError.errorCode == commandErrorInvalidInput { - writeRequestesponse(writer, http.StatusBadRequest, commandError.error) - } else { - writeRequestesponse(writer, http.StatusInternalServerError, commandError.error) - } - - return - } - - apiResponse = commandResponse.message - restartIs = commandResponse.restartIs - break - - default: - writeRequestesponse(writer, http.StatusNotImplemented, "Not implemented") - return - } - - if restartIs { - log.Printf("#commandRequestHandler command %s requested integration server restart, invoking restart callback", command) - if restartIntegrationServerFunc != nil { - err = restartIntegrationServerFunc() - - if err != nil { - log.Errorf("#commandRequestHandler an error occurred while restarting integration server %v", err) - apiResponse = "Integration server restart failed" - } - } else { - log.Error("Intergration server function is nil") - err = errors.New("Integration server has not restarted") - apiResponse = "Integration server has not restarted" - } - } - - if err == nil { - writeRequestesponse(writer, http.StatusOK, apiResponse) - } else { - writeRequestesponse(writer, http.StatusInternalServerError, apiResponse) - } -} - -func handleCRUDCommand(command string, crudCommandHandler commandHandlerInterface) { - - crudHandler := handlerInvocationDetails{ - handler: crudCommandHandler} - - commandsHandler[command] = crudHandler -} - -var startHTTPServer = func(logger logger.LoggerInterface, portNo int) *http.Server { - log = logger - - address := fmt.Sprintf(":%v", portNo) - server := &http.Server{Addr: address} - - go func() { - err := server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Errorf("Error in serving " + err.Error()) - } - log.Println("Commands API server stopped ") - }() - - return server -} - -// StartCommandsAPIServer Starts the api server -func StartCommandsAPIServer(logger logger.LoggerInterface, portNumber int, restartIsFunc func() error) error { - - if restartIsFunc == nil { - return errors.New("Restart handler should not be nil") - } - - log = logger - restartIntegrationServerFunc = restartIsFunc - - dbParamsHandler := DbParamsHandler{} - - handleCRUDCommand("setdbparms", dbParamsHandler) - http.HandleFunc("/commands/", commandRequestHandler) - httpServer = startHTTPServer(log, portNumber) - - return nil -} - -// StopCommandsAPIServer Stops commands api server -func StopCommandsAPIServer() { - if httpServer != nil { - ctxShutDown, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer func() { - cancel() - }() - - if err := httpServer.Shutdown(ctxShutDown); err != nil { - log.Fatalf("Server Shutdown Failed:%+s", err) - } else { - log.Println("Integration commands API stopped") - } - } - -} diff --git a/internal/isCommandsApi/dbparamsHandler.go b/internal/isCommandsApi/dbparamsHandler.go deleted file mode 100644 index 94df81f..0000000 --- a/internal/isCommandsApi/dbparamsHandler.go +++ /dev/null @@ -1,83 +0,0 @@ -package iscommandsapi - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "regexp" - - "github.com/ot4i/ace-docker/common/logger" - "github.com/ot4i/ace-docker/internal/command" -) - -// DbParamsCommand for mqsisetdbparams command -type DbParamsCommand struct { - ResourceType string `json:"resourceType"` - ResourceName string `json:"resourceName"` - UserName string `json:"userName"` - Password string `json:"password"` -} - -// DbParamsHandler handler -type DbParamsHandler struct { -} - -var runCommand func(name string, args ...string) (string, int, error) = command.Run - -func (handler DbParamsHandler) execute(log logger.LoggerInterface, body io.Reader) (*commandResponse, *commandError) { - - dbParamsCommand := DbParamsCommand{} - err := json.NewDecoder(body).Decode(&dbParamsCommand) - - if err != nil { - log.Println("#setdbparamsHandler Error in decoding json") - return nil, &commandError{errorCode: commandErrorInvalidInput, error: "Invalid request"} - } - - if len(dbParamsCommand.UserName) == 0 || len(dbParamsCommand.ResourceType) == 0 || len(dbParamsCommand.ResourceName) == 0 { - log.Println("#setdbparamsHandler one of required parameters not found") - return nil, &commandError{errorCode: commandErrorInvalidInput, error: "Invalid request"} - } - - resource := fmt.Sprintf("%s::%s", dbParamsCommand.ResourceType, dbParamsCommand.ResourceName) - - isCredentialsExists, err := isResourceCredentialsExists(resource, dbParamsCommand.UserName, dbParamsCommand.Password) - - if err != nil { - log.Printf("#setdbparamsHandler, an error occurred while checking the resource already exists, error %s", err.Error()) - return nil, &commandError{errorCode: commandErrorInternal, error: "Internal error"} - } - - if isCredentialsExists { - log.Printf("#setdbparamsHandler credentials with the same user name and password already exists for the resource %s", resource) - return &commandResponse{message: "success", restartIs: false}, nil - } - - log.Printf("#setdbparamsHandler adding %s of type %s", dbParamsCommand.ResourceName, dbParamsCommand.ResourceType) - - runCommand("ace_mqsicommand.sh", "setdbparms", "-w", "/home/aceuser/ace-server/", "-n", resource, - "-u", dbParamsCommand.UserName, "-p", dbParamsCommand.Password) - - return &commandResponse{message: "success", restartIs: true}, nil -} - -func isResourceCredentialsExists(resourceName string, username string, password string) (bool, error) { - - cmdOutput, exitCode, err := runCommand("ace_mqsicommand.sh", "reportdbparms", "-w", "/home/aceuser/ace-server/", "-n", resourceName, - "-u", username, "-p", password) - - if err != nil { - return false, err - } - - if exitCode != 0 { - errorMsg := fmt.Sprintf("mqsireportdbparams command exited with non zero, exit code %v", exitCode) - return false, errors.New(errorMsg) - } - - credentialsMatchRegx := regexp.MustCompile("(?m)^(\\s)*\\b(BIP8201I).*\\b(correct).*$") - - return credentialsMatchRegx.MatchString(cmdOutput), nil - -} diff --git a/internal/isCommandsApi/dbparamsHandler_test.go b/internal/isCommandsApi/dbparamsHandler_test.go deleted file mode 100644 index 4212169..0000000 --- a/internal/isCommandsApi/dbparamsHandler_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package iscommandsapi - -import ( - "errors" - "os" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/ot4i/ace-docker/common/logger" -) - -func TestDbParamsHandlerExecute(t *testing.T) { - - testLogger, _ := logger.NewLogger(os.Stdout, true, true, "test") - t.Run("When invalid json input is given, returns invalid input", func(t *testing.T) { - - reset() - defer restore() - - invalidJSON := `{"ResourceName":"abc}` - - cmdResponse, cmdError := DbParamsHandler{}.execute(testLogger, strings.NewReader(invalidJSON)) - - assert.NotNil(t, cmdError) - assert.Equal(t, commandErrorInvalidInput, cmdError.errorCode) - assert.Equal(t, "Invalid request", cmdError.error) - assert.Nil(t, cmdResponse) - }) - - t.Run("When resource name is missing, returns invalid input", func(t *testing.T) { - - reset() - defer restore() - - invalidJSON := `{"ResourceType":"mq","UserName":"abc","Password":"xyz"}` - - cmdResponse, cmdError := DbParamsHandler{}.execute(testLogger, strings.NewReader(invalidJSON)) - - assert.NotNil(t, cmdError) - assert.Equal(t, commandErrorInvalidInput, cmdError.errorCode) - assert.Equal(t, "Invalid request", cmdError.error) - assert.Nil(t, cmdResponse) - }) - - t.Run("When resource type is empty, returns invalid input", func(t *testing.T) { - - reset() - defer restore() - - invalidJSON := `{"resourceType":"","resourceName":"123","UserName":"abc","Password":"xyz"}` - - cmdResponse, cmdError := DbParamsHandler{}.execute(testLogger, strings.NewReader(invalidJSON)) - - assert.NotNil(t, cmdError) - assert.Equal(t, commandErrorInvalidInput, cmdError.errorCode) - assert.Equal(t, "Invalid request", cmdError.error) - assert.Nil(t, cmdResponse) - }) - - t.Run("When username is empty, returns invalid input", func(t *testing.T) { - - reset() - defer restore() - - invalidJSON := `{"resourceType":"mq","resourceName":"123","UserName":"","Password":"xyz"}` - - cmdResponse, cmdError := DbParamsHandler{}.execute(testLogger, strings.NewReader(invalidJSON)) - - assert.NotNil(t, cmdError) - assert.Equal(t, commandErrorInvalidInput, cmdError.errorCode) - assert.Equal(t, "Invalid request", cmdError.error) - assert.Nil(t, cmdResponse) - }) - - t.Run("when request is valid invokes reportdbparms command to check supplied resource credentials", func(t *testing.T) { - - reset() - defer restore() - - var invokedCommandName string = "" - var invokedCommandArgs []string = nil - - runCommand = func(cmdName string, cmdArguments ...string) (string, int, error) { - - invokedCommandName = cmdName - invokedCommandArgs = cmdArguments - return "", 1, nil - } - - validJSON := `{"resourceType":"mq","resourceName":"123","UserName":"abc","Password":"xyz"}` - expectedCommandArgs := []string{"reportdbparms", "-w", "/home/aceuser/ace-server/", "-n", "mq::123", "-u", "abc", "-p", "xyz"} - - _, err := DbParamsHandler{}.execute(testLogger, strings.NewReader(validJSON)) - - assert.NotNil(t, err) - assert.Equal(t, "ace_mqsicommand.sh", invokedCommandName) - assert.Equal(t, expectedCommandArgs, invokedCommandArgs) - }) - - t.Run("when reportdbparms cmd output contains credentials correct line, returns success with restart integration server flag false", func(t *testing.T) { - - reset() - defer restore() - - var credentialsExistsOutput = `BIP8180I: The resource name 'mq::123' has userID 'abc'. - BIP8201I: The password you entered, 'xyz' for resource 'mq::123' and userId 'abc' is correct.` - - runCommand = func(cmdName string, cmdArguments ...string) (string, int, error) { - return credentialsExistsOutput, 0, nil - } - - validJSON := `{"resourceType":"mq","resourceName":"123","UserName":"abc","Password":"xyz"}` - - cmdResponse, _ := DbParamsHandler{}.execute(testLogger, strings.NewReader(validJSON)) - - assert.NotNil(t, cmdResponse) - assert.Equal(t, "success", cmdResponse.message) - assert.Equal(t, false, cmdResponse.restartIs) - }) - - t.Run("When reportdbparams command output doesn't contain credentials correct line, invokes setdbparams and returns success", func(t *testing.T) { - - reset() - defer restore() - - var invokedCommandName string = "" - var invokedCommandArgs []string = nil - - reportDbPramsOutput := `BIP8180I: The resource name 'mq::123' has userID 'abc'. - BIP8204W: The password you entered, 'xyz' for resource 'mq::123' and userId 'test1' is incorrect` - - runCommand = func(cmdName string, cmdArguments ...string) (string, int, error) { - - if cmdArguments[0] == "reportdbparms" { - return reportDbPramsOutput, 0, nil - } - - invokedCommandName = cmdName - invokedCommandArgs = cmdArguments - return "", 0, nil - } - - validJSON := `{"resourceType":"mq","resourceName":"123","UserName":"abc","Password":"xyz"}` - - expectedCommandArgs := []string{"setdbparms", "-w", "/home/aceuser/ace-server/", "-n", "mq::123", "-u", "abc", "-p", "xyz"} - - cmdResponse, _ := DbParamsHandler{}.execute(testLogger, strings.NewReader(validJSON)) - - assert.NotNil(t, cmdResponse) - assert.Equal(t, "success", cmdResponse.message) - assert.Equal(t, "ace_mqsicommand.sh", invokedCommandName) - assert.Equal(t, expectedCommandArgs, invokedCommandArgs) - }) - - t.Run("When reportdbparams command returns error, returns internal error", func(t *testing.T) { - reset() - defer restore() - - runCommand = func(cmdName string, cmdArguments ...string) (string, int, error) { - if cmdArguments[0] == "reportdbparms" { - return "", 0, errors.New("some error") - } - - panic("should not come here") - } - - validJSON := `{"resourceType":"mq","resourceName":"123","UserName":"abc","Password":"xyz"}` - - cmdResponse, cmdError := DbParamsHandler{}.execute(testLogger, strings.NewReader(validJSON)) - - assert.Nil(t, cmdResponse) - assert.NotNil(t, commandErrorInternal, cmdError.errorCode) - assert.Equal(t, "Internal error", cmdError.error) - }) - - t.Run("When reportdbparams command exits with non zero, returns internal error", func(t *testing.T) { - reset() - defer restore() - - runCommand = func(cmdName string, cmdArguments ...string) (string, int, error) { - if cmdArguments[0] == "reportdbparms" { - return "", 1, nil - } - - return "", 0, nil - } - - validJSON := `{"resourceType":"mq","resourceName":"123","UserName":"abc","Password":"xyz"}` - - cmdResponse, cmdError := DbParamsHandler{}.execute(testLogger, strings.NewReader(validJSON)) - - assert.Nil(t, cmdResponse) - assert.Equal(t, commandErrorInternal, cmdError.errorCode) - assert.Equal(t, "Internal error", cmdError.error) - }) -} diff --git a/internal/metrics/exporter.go b/internal/metrics/exporter.go deleted file mode 100644 index 72bb682..0000000 --- a/internal/metrics/exporter.go +++ /dev/null @@ -1,185 +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 metrics contains code to provide metrics for the queue manager -package metrics - -import ( - "github.com/ot4i/ace-docker/common/logger" - - "github.com/prometheus/client_golang/prometheus" -) - -const ( - namespace = "ibmace" - msgflowPrefix = "msgflow" - msgflownodePrefix = "msgflownode" - serverLabel = "server" - applicationLabel = "application" - msgflowLabel = "msgflow" - msgflownodeLabel = "msgflownode" - msgflownodeTypeLabel = "msgflownodetype" - originLabel = "accountingorigin" -) - -type exporter struct { - serverName string - counterMap map[string]*prometheus.CounterVec - gaugeMap map[string]*prometheus.GaugeVec - firstCollect bool - log logger.LoggerInterface -} - -func newExporter(serverName string, log logger.LoggerInterface) *exporter { - return &exporter{ - serverName: serverName, - counterMap: make(map[string]*prometheus.CounterVec), - gaugeMap: make(map[string]*prometheus.GaugeVec), - log: log, - } -} - -// Describe provides details of all available metrics -func (e *exporter) Describe(ch chan<- *prometheus.Desc) { - - requestChannel <- false - response := <-responseChannel - - response.Lock() - defer response.Unlock() - - for key, metric := range response.internal { - - if metric.metricType == Total { - // For delta type metrics - allocate a Prometheus Counter - counterVec := createCounterVec(metric.name, metric.description, metric.metricLevel) - e.counterMap[key] = counterVec - - // Describe metric - counterVec.Describe(ch) - } else { - // For non-delta type metrics - allocate a Prometheus Gauge - gaugeVec := createGaugeVec(metric.name, metric.description, metric.metricLevel) - e.gaugeMap[key] = gaugeVec - - // Describe metric - gaugeVec.Describe(ch) - } - - } -} - -// Collect is called at regular intervals to provide the current metric data -func (e *exporter) Collect(ch chan<- prometheus.Metric) { - - requestChannel <- true - response := <-responseChannel - - response.Lock() - defer response.Unlock() - - for key, metric := range response.internal { - if metric.metricType == Total { - // For delta type metrics - update their Prometheus Counter - counterVec := e.counterMap[key] - - // Populate Prometheus Counter with metric values - for _, value := range metric.values { - var err error - var counter prometheus.Counter - - counter, err = counterVec.GetMetricWith(value.labels) - - if err == nil { - counter.Add(value.value) - } else { - e.log.Errorf("Metrics Error: %s", err.Error()) - } - } - - // Collect metric and reset cached values - counterVec.Collect(ch) - response.internal[key].values = make(map[string]*Metric) - } else { - // For non-delta type metrics - reset their Prometheus Gauge - gaugeVec := e.gaugeMap[key] - gaugeVec.Reset() - - for _, value := range metric.values { - var err error - var gauge prometheus.Gauge - - gauge, err = gaugeVec.GetMetricWith(value.labels) - - if err == nil { - gauge.Set(value.value) - } else { - e.log.Errorf("Metrics Error: %s", err.Error()) - } - } - - // Collect metric - gaugeVec.Collect(ch) - } - } -} - -// createCounterVec returns a Prometheus CounterVec populated with metric details -func createCounterVec(name, description string, metricLevel MetricLevel) *prometheus.CounterVec { - - labels := getVecDetails(metricLevel) - - counterVec := prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: namespace, - Name: name, - Help: description, - }, - labels, - ) - return counterVec -} - -// createGaugeVec returns a Prometheus GaugeVec populated with metric details -func createGaugeVec(name, description string, metricLevel MetricLevel) *prometheus.GaugeVec { - - labels := getVecDetails(metricLevel) - - gaugeVec := prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Name: name, - Help: description, - }, - labels, - ) - return gaugeVec -} - -// getVecDetails returns the required prefix and labels for a metric -func getVecDetails(metricLevel MetricLevel) (labels []string) { - - //TODO: What if messageflow is in a library? - if metricLevel == MsgFlowLevel { - labels = []string{msgflowLabel, applicationLabel, serverLabel, originLabel} - } else if metricLevel == MsgFlowNodeLevel { - labels = []string{msgflownodeLabel, msgflownodeTypeLabel, msgflowLabel, applicationLabel, serverLabel, originLabel} - } else if metricLevel == Resource { - labels = []string{serverLabel} - } - - return labels -} diff --git a/internal/metrics/exporter_test.go b/internal/metrics/exporter_test.go deleted file mode 100644 index 9747ee2..0000000 --- a/internal/metrics/exporter_test.go +++ /dev/null @@ -1,314 +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 metrics - -import ( - "os" - "testing" - - "github.com/prometheus/client_golang/prometheus" - dto "github.com/prometheus/client_model/go" - "github.com/ot4i/ace-docker/common/logger" -) - -func getTestLogger() logger.LoggerInterface { - log, _ := logger.NewLogger(os.Stdout, false, false, "test") - return log -} - -func TestDescribe(t *testing.T) { - log := getTestLogger() - - ch := make(chan *prometheus.Desc) - go func() { - exporter := newExporter("serverName", log) - exporter.Describe(ch) - }() - - collect := <-requestChannel - if collect { - t.Errorf("Received unexpected collect request") - } - - metrics := NewMetricsMap() - - countMetric := metricData{ - name: "test_count_metric", - description: "This is a test counter metric", - metricType: Total, - metricUnits: Count, - metricLevel: MsgFlowLevel, - } - countMetric.values = make(map[string]*Metric) - - gaugeMetric := metricData{ - name: "test_gauge_metric", - description: "This is a test gauge metric", - metricType: Current, - metricUnits: Count, - metricLevel: Resource, - } - gaugeMetric.values = make(map[string]*Metric) - - metrics.internal["count_metrics"] = &countMetric - metrics.internal["gauge_metrics"] = &gaugeMetric - - responseChannel <- metrics - - expectedDesc1 := "Desc{fqName: \"ibmace_test_count_metric\", help: \"This is a test counter metric\", constLabels: {}, variableLabels: [msgflow application server accountingorigin]}" - expectedDesc2 := "Desc{fqName: \"ibmace_test_gauge_metric\", help: \"This is a test gauge metric\", constLabels: {}, variableLabels: [server]}" - - var found1, found2 bool = false, false - - for i := 0; i < len(metrics.internal); i++ { - prometheusDesc := <-ch - actualDesc := prometheusDesc.String() - - if actualDesc != expectedDesc1 && actualDesc != expectedDesc2 { - t.Errorf("Expected a value of either\n- %s OR\n- %s\n\nActual value was - %s", expectedDesc1, expectedDesc2, actualDesc) - return - } - - if actualDesc == expectedDesc1 { - if found1 { - t.Errorf("Duplicate metrics sent over channel, was only expected once\n- %s\n", expectedDesc1) - } else { - found1 = true - } - } - - if actualDesc == expectedDesc2 { - if found2 { - t.Errorf("Duplicate metrics sent over channel, was only expected once\n- %s\n", expectedDesc2) - } else { - found2 = true - } - } - } - - if !found1 { - t.Errorf("Expected to find value\n- %s", expectedDesc1) - } - - if !found2 { - t.Errorf("Expected to find value\n- %s", expectedDesc2) - } -} - -func TestCollect_Counters(t *testing.T) { - log := getTestLogger() - - exporter := newExporter("serverName", log) - - exporter.counterMap["count_metrics"] = createCounterVec("test_count_metric", "This is a test counter metric", Resource) - - metrics := NewMetricsMap() - - countMetric := metricData{ - name: "test_count_metric", - description: "This is a test counter metric", - metricType: Total, - metricUnits: Count, - metricLevel: Resource, - } - countMetric.values = make(map[string]*Metric) - metrics.internal["count_metrics"] = &countMetric - - countValues := []float64{0.0, 4.0, 5.0, 0.0, 3.0} - expected := 0.0 - - // Call collect several times and ensure the values are reset and counter is incremented as expected - for _, countValue := range countValues { - expected += countValue - - ch := make(chan prometheus.Metric) - go func() { - exporter.Collect(ch) - close(ch) - }() - - collect := <-requestChannel - if !collect { - t.Errorf("Received unexpected describe request") - } - - countMetric.values["Test1"] = &Metric{labels: prometheus.Labels{serverLabel: "test server1"}, value: countValue} - - responseChannel <- metrics - <-ch - prometheusMetric := dto.Metric{} - exporter.counterMap["count_metrics"].WithLabelValues("test server1").Write(&prometheusMetric) - actual := prometheusMetric.GetCounter().GetValue() - - if actual != expected { - t.Errorf("Expected value=%f; actual=%f", expected, actual) - } - - if len(countMetric.values) != 0 { - t.Errorf("Counter values should be reset after collect: %+v", countMetric.values) - } - } -} - -func TestCollect_Gauges(t *testing.T) { - log := getTestLogger() - - exporter := newExporter("TestServer", log) - - exporter.gaugeMap["gauge_metrics"] = createGaugeVec("test_gauge_metric", "This is a test gauge metric", Resource) - - metrics := NewMetricsMap() - - gaugeMetric := metricData{ - name: "test_gauge_metric", - description: "This is a test gauge metric", - metricType: Current, - metricUnits: Count, - metricLevel: Resource, - } - gaugeMetric.values = make(map[string]*Metric) - metrics.internal["gauge_metrics"] = &gaugeMetric - - gaugeValues := []float64{0.0, 4.0, 5.0, 0.0, 3.0} - - // Call collect several times and ensure the values are reset and counter is incremented as expected - for _, gaugeValue := range gaugeValues { - expected := gaugeValue - - ch := make(chan prometheus.Metric) - go func() { - exporter.Collect(ch) - close(ch) - }() - - collect := <-requestChannel - if !collect { - t.Errorf("Received unexpected describe request") - } - - gaugeMetric.values["Test"] = &Metric{labels: prometheus.Labels{serverLabel: "TestServer"}, value: gaugeValue} - - responseChannel <- metrics - <-ch - prometheusMetric := dto.Metric{} - exporter.gaugeMap["gauge_metrics"].WithLabelValues("TestServer").Write(&prometheusMetric) - actual := prometheusMetric.GetGauge().GetValue() - - if actual != expected { - t.Errorf("Expected value=%f; actual=%f", expected, actual) - } - - if len(gaugeMetric.values) != 1 { - t.Errorf("Gauge values should not be reset after collect: %+v", gaugeMetric.values) - } - } -} - -func TestCreateCounterVec_msgFlow(t *testing.T) { - - ch := make(chan *prometheus.Desc) - counterVec := createCounterVec("MetricName", "MetricDescription", MsgFlowLevel) - go func() { - counterVec.Describe(ch) - }() - description := <-ch - - expected := "Desc{fqName: \"ibmace_MetricName\", help: \"MetricDescription\", constLabels: {}, variableLabels: [msgflow application server accountingorigin]}" - actual := description.String() - if actual != expected { - t.Errorf("Expected value=%s; actual %s", expected, actual) - } -} - -func TestCreateCounterVec_MsgFlowNode(t *testing.T) { - - ch := make(chan *prometheus.Desc) - counterVec := createCounterVec("MetricName", "MetricDescription", MsgFlowNodeLevel) - go func() { - counterVec.Describe(ch) - }() - description := <-ch - - expected := "Desc{fqName: \"ibmace_MetricName\", help: \"MetricDescription\", constLabels: {}, variableLabels: [msgflownode msgflownodetype msgflow application server accountingorigin]}" - actual := description.String() - if actual != expected { - t.Errorf("Expected value=%s; actual %s", expected, actual) - } -} - -func TestCreateCounterVec_Resource(t *testing.T) { - - ch := make(chan *prometheus.Desc) - counterVec := createCounterVec("MetricName", "MetricDescription", Resource) - go func() { - counterVec.Describe(ch) - }() - description := <-ch - - expected := "Desc{fqName: \"ibmace_MetricName\", help: \"MetricDescription\", constLabels: {}, variableLabels: [server]}" - actual := description.String() - if actual != expected { - t.Errorf("Expected value=%s; actual %s", expected, actual) - } -} - -func TestCreateGaugeVec_MsgFlow(t *testing.T) { - - ch := make(chan *prometheus.Desc) - gaugeVec := createGaugeVec("MetricName", "MetricDescription", MsgFlowLevel) - go func() { - gaugeVec.Describe(ch) - }() - description := <-ch - - expected := "Desc{fqName: \"ibmace_MetricName\", help: \"MetricDescription\", constLabels: {}, variableLabels: [msgflow application server accountingorigin]}" - actual := description.String() - if actual != expected { - t.Errorf("Expected value=%s; actual %s", expected, actual) - } -} - -func TestCreateGaugeVec_MsgFlowNode(t *testing.T) { - - ch := make(chan *prometheus.Desc) - gaugeVec := createGaugeVec("MetricName", "MetricDescription", MsgFlowNodeLevel) - go func() { - gaugeVec.Describe(ch) - }() - description := <-ch - - expected := "Desc{fqName: \"ibmace_MetricName\", help: \"MetricDescription\", constLabels: {}, variableLabels: [msgflownode msgflownodetype msgflow application server accountingorigin]}" - actual := description.String() - if actual != expected { - t.Errorf("Expected value=%s; actual %s", expected, actual) - } -} - -func TestCreateGaugeVec_Resource(t *testing.T) { - - ch := make(chan *prometheus.Desc) - gaugeVec := createGaugeVec("MetricName", "MetricDescription", Resource) - go func() { - gaugeVec.Describe(ch) - }() - description := <-ch - - expected := "Desc{fqName: \"ibmace_MetricName\", help: \"MetricDescription\", constLabels: {}, variableLabels: [server]}" - actual := description.String() - if actual != expected { - t.Errorf("Expected value=%s; actual %s", expected, actual) - } -} diff --git a/internal/metrics/mapping.go b/internal/metrics/mapping.go deleted file mode 100644 index 04e4be2..0000000 --- a/internal/metrics/mapping.go +++ /dev/null @@ -1,280 +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 metrics contains code to provide metrics for the queue manager -package metrics - -const ( - ResourceStatisticsData = 0 - AccountingAndStatisticsData = 2 -) - -type MetricType int - -const ( - Total MetricType = 0 - Minimum MetricType = 1 - Maximum MetricType = 2 - Current MetricType = 3 -) - -type MetricUnits int - -const ( - Microseconds MetricUnits = 0 - Seconds MetricUnits = 1 - Bytes MetricUnits = 2 - MegaBytes MetricUnits = 3 - Count MetricUnits = 4 -) - -type MetricLevel int - -const ( - MsgFlowLevel MetricLevel = 0 - MsgFlowNodeLevel MetricLevel = 1 - Resource MetricLevel = 2 -) - -type metricLookup struct { - name string - description string - enabled bool - metricType MetricType - metricUnits MetricUnits - metricLevel MetricLevel -} - -type ThreadStatisticsStruct struct { -} - -type TerminalStatisticsStruct struct { -} - -type NodesStatisticsStruct struct { - Label string `json:"Label"` - Type string `json:"Type"` - TotalElapsedTime int `json:"TotalElapsedTime"` - MaximumElapsedTime int `json:"MaximumElapsedTime"` - MinimumElapsedTime int `json:"MinimumElapsedTime"` - TotalCPUTime int `json:"TotalCPUTime"` - MaximumCPUTime int `json:"MaximumCPUTime"` - MinimumCPUTime int `json:"MinimumCPUTime"` - CountOfInvocations int `json:"CountOfInvocations"` - NumberOfInputTerminals int `json:"NumberOfInputTerminals"` - NumberOfOutputTerminals int `json:"NumberOfOutputTerminals"` - TerminalStatistics []TerminalStatisticsStruct -} - -type MessageFlowStruct struct { - BrokerLabel string `json:"BrokerLabel"` - BrokerUUID string `json:"BrokerUUID"` - ExecutionGroupName string `json:"ExecutionGroupName"` - ExecutionGroupUUID string `json:"ExecutionGroupUUID"` - MessageFlowName string `json:"MessageFlowName"` - ApplicationName string `json:"ApplicationName"` - StartDate string `json:"StartDate"` - StartTime string `json:"StartTime"` - GMTStartTime string `json:"GMTStartTime"` - EndDate string `json:"EndDate"` - EndTime string `json:"EndTime"` - GMTEndTime string `json:"GMTEndTime"` - TotalElapsedTime int `json:"TotalElapsedTime"` - MaximumElapsedTime int `json:"MaximumElapsedTime"` - MinimumElapsedTime int `json:"MinimumElapsedTime"` - TotalCPUTime int `json:"TotalCPUTime"` - MaximumCPUTime int `json:"MaximumCPUTime"` - MinimumCPUTime int `json:"MinimumCPUTime"` - CPUTimeWaitingForInputMessage int `json:"CPUTimeWaitingForInputMessage"` - ElapsedTimeWaitingForInputMessage int `json:"ElapsedTimeWaitingForInputMessage"` - TotalInputMessages int `json:"TotalInputMessages"` - TotalSizeOfInputMessages int `json:"TotalSizeOfInputMessages"` - MaximumSizeOfInputMessages int `json:"MaximumSizeOfInputMessages"` - MinimumSizeOfInputMessages int `json:"MinimumSizeOfInputMessages"` - NumberOfThreadsInPool int `json:"NumberOfThreadsInPool"` - TimesMaximumNumberOfThreadsReached int `json:"TimesMaximumNumberOfThreadsReached"` - TotalNumberOfMQErrors int `json:"TotalNumberOfMQErrors"` - TotalNumberOfMessagesWithErrors int `json:"TotalNumberOfMessagesWithErrors"` - TotalNumberOfErrorsProcessingMessages int `json:"TotalNumberOfErrorsProcessingMessages"` - TotalNumberOfTimeOutsWaitingForRepliesToAggregateMessages int `json:"TotalNumberOfTimeOutsWaitingForRepliesToAggregateMessages"` - TotalNumberOfCommits int `json:"TotalNumberOfCommits"` - TotalNumberOfBackouts int `json:"TotalNumberOfBackouts"` - AccountingOrigin string `json:"AccountingOrigin"` -} - -type DataStruct struct { - WMQIStatisticsAccounting *WMQIStatisticsAccountingStruct `json:"WMQIStatisticsAccounting,omitempty"` - ResourceStatistics *ResourceStatisticsStruct `json:"ResourceStatistics,omitempty"` -} - -type WMQIStatisticsAccountingStruct struct { - RecordType string `json:"RecordType"` - RecordCode string `json:"RecordCode"` - MessageFlow MessageFlowStruct `json:"MessageFlow"` - NumberOfThreads int `json:"NumberOfThreads"` - ThreadStatistics []ThreadStatisticsStruct `json:"ThreadStatistics"` - NumberOfNodes int `json:"NumberOfNodes"` - Nodes []NodesStatisticsStruct `json:"Nodes"` -} - -type StatisticsDataStruct struct { - Data DataStruct `json:"data"` - Event int `json:"event"` -} - -type ResourceStatisticsStruct struct { - BrokerLabel string `json:"brokerLabel"` - BrokerUUID string `json:"brokerUUID"` - ExecutionGroupName string `json:"executionGroupName"` - ExecutiongGroupUUID string `json:"executionGroupUUID"` - CollectionStartDate string `json:"collectionStartDate"` - CollectionStartTime string `json:"collectionStartTime"` - StartDate string `json:"startDate"` - StartTime string `json:"startTime"` - EndDate string `json:"endDate"` - EndTime string `json:"endTime"` - Timezone string `json:"timezone"` - ResourceType []ResourceTypeStruct `json:"ResourceType"` -} - -type ResourceTypeStruct struct { - Name string `json:"name"` - ResourceIdentifier []map[string]interface{} `json:"resourceIdentifier"` -} - -type JvmDataStruct struct { - SummaryInitial int - SummaryUsed int - SummaryCommitted int - SummaryMax int - SummaryGCTime int - SummaryGCCount int - HeapInitial int - HeapUsed int - HeapCommitted int - HeapMax int - NativeInitial int - NativeUsed int - NativeCommitted int - NativeMax int - ScavengerGCTime int - ScavengerGCCount int - GlobalGCTime int - GlobalGCCount int -} - -func NewJVMData(ma []map[string]interface{}) *JvmDataStruct { - - jvmData := JvmDataStruct{} - - for _, m := range ma { - switch m["name"] { - case "summary": - jvmData.SummaryInitial = int(m["InitialMemoryInMB"].(float64)) - jvmData.SummaryUsed = int(m["UsedMemoryInMB"].(float64)) - jvmData.SummaryCommitted = int(m["CommittedMemoryInMB"].(float64)) - jvmData.SummaryMax = int(m["MaxMemoryInMB"].(float64)) - jvmData.SummaryGCTime = int(m["CumulativeGCTimeInSeconds"].(float64)) - jvmData.SummaryGCCount = int(m["CumulativeNumberOfGCCollections"].(float64)) - case "Heap Memory": - jvmData.HeapInitial = int(m["InitialMemoryInMB"].(float64)) - jvmData.HeapUsed = int(m["UsedMemoryInMB"].(float64)) - jvmData.HeapCommitted = int(m["CommittedMemoryInMB"].(float64)) - jvmData.HeapMax = int(m["MaxMemoryInMB"].(float64)) - case "Non-Heap Memory": - jvmData.NativeInitial = int(m["InitialMemoryInMB"].(float64)) - jvmData.NativeUsed = int(m["UsedMemoryInMB"].(float64)) - jvmData.NativeCommitted = int(m["CommittedMemoryInMB"].(float64)) - jvmData.NativeMax = int(m["MaxMemoryInMB"].(float64)) - case "Garbage Collection - scavenge": - jvmData.ScavengerGCTime = int(m["CumulativeGCTimeInSeconds"].(float64)) - jvmData.ScavengerGCCount = int(m["CumulativeNumberOfGCCollections"].(float64)) - case "Garbage Collection - global": - jvmData.GlobalGCTime = int(m["CumulativeGCTimeInSeconds"].(float64)) - jvmData.GlobalGCCount = int(m["CumulativeNumberOfGCCollections"].(float64)) - } - } - - return &jvmData -} - -// generates metric names mapped from their description -func generateMetricNamesMap() (msgFlowMetricNamesMap, msgFlowNodeMetricNamesMap map[string]metricLookup) { - - msgFlowMetricNamesMap = map[string]metricLookup{ - "MsgFlow/TotalElapsedTime": metricLookup{"msgflow_elapsed_time_seconds_total", "Total elapsed time spent processing messages by the message flow", true, Total, Microseconds, MsgFlowLevel}, - "MsgFlow/MaximumElapsedTime": metricLookup{"msgflow_elapsed_time_seconds_max", "Maximum elapsed time spent processing a message by the message flow", true, Maximum, Microseconds, MsgFlowLevel}, - "MsgFlow/MinimumElapsedTime": metricLookup{"msgflow_elapsed_time_seconds_min", "Minimum elapsed time spent processing a message by the message flow", true, Minimum, Microseconds, MsgFlowLevel}, - "MsgFlow/TotalCpuTime": metricLookup{"msgflow_cpu_time_seconds_total", "Total CPU time spent processing messages by the message flow", true, Total, Microseconds, MsgFlowLevel}, - "MsgFlow/MaximumCpuTime": metricLookup{"msgflow_cpu_time_seconds_max", "Maximum CPU time spent processing a message by the message flow", true, Maximum, Microseconds, MsgFlowLevel}, - "MsgFlow/MinimumCpuTime": metricLookup{"msgflow_cpu_time_seconds_min", "Minimum CPU time spent processing a message by the message flow", true, Minimum, Microseconds, MsgFlowLevel}, - "MsgFlow/TotalSizeOfInputMessages": metricLookup{"msgflow_messages_bytes_total", "Total size of messages processed by the message flow", true, Total, Bytes, MsgFlowLevel}, - "MsgFlow/MaximumSizeOfInputMessages": metricLookup{"msgflow_messages_bytes_max", "Maximum size of message processed by the message flow", true, Maximum, Bytes, MsgFlowLevel}, - "MsgFlow/MinimumSizeOfInputMessages": metricLookup{"msgflow_messages_bytes_min", "Minimum size of message processed by the message flow", true, Minimum, Bytes, MsgFlowLevel}, - "MsgFlow/TotalInputMessages": metricLookup{"msgflow_messages_total", "Total number of messages processed by the message flow", true, Total, Count, MsgFlowLevel}, - "MsgFlow/TotalCPUTimeWaiting": metricLookup{"msgflow_cpu_time_waiting_seconds_total", "Total CPU time spent waiting for input messages by the message flow", true, Total, Microseconds, MsgFlowLevel}, - "MsgFlow/TotalElapsedTimeWaiting": metricLookup{"msgflow_elapsed_time_waiting_seconds_total", "Total elapsed time spent waiting for input messages by the message flow", true, Total, Microseconds, MsgFlowLevel}, - "MsgFlow/NumberOfThreadsInPool": metricLookup{"msgflow_threads_total", "Number of threads in the pool for the message flow", true, Current, Count, MsgFlowLevel}, - "MsgFlow/TimesMaximumNumberOfThreadsReached": metricLookup{"msgflow_threads_reached_maximum_total", "Number of times that maximum number of threads in the pool for the message flow was reached", true, Total, Count, MsgFlowLevel}, - "MsgFlow/TotalNumberOfMQErrors": metricLookup{"msgflow_mq_errors_total", "Total number of MQ errors in the message flow", true, Total, Count, MsgFlowLevel}, - "MsgFlow/TotalNumberOfMessagesWithErrors": metricLookup{"msgflow_messages_with_error_total", "Total number of messages processed by the message flow that had errors", true, Total, Count, MsgFlowLevel}, - "MsgFlow/TotalNumberOfErrorsProcessingMessages": metricLookup{"msgflow_errors_total", "Total number of errors processing messages by the message flow", true, Total, Count, MsgFlowLevel}, - "MsgFlow/TotalNumberOfTimeOutsWaitingForRepliesToAggregateMessages": metricLookup{"msgflow_aggregation_timeouts_total", "Total number of timeouts waiting for replies to Aggregate messages", true, Total, Count, MsgFlowLevel}, - "MsgFlow/TotalNumberOfCommits": metricLookup{"msgflow_commits_total", "Total number of commits by the message flow", true, Total, Count, MsgFlowLevel}, - "MsgFlow/TotalNumberOfBackouts": metricLookup{"msgflow_backouts_total", "Total number of backouts by the message flow", true, Total, Count, MsgFlowLevel}, - } - - msgFlowNodeMetricNamesMap = map[string]metricLookup{ - "MsgFlowNode/TotalElapsedTime": metricLookup{"msgflownode_elapsed_time_seconds_total", "Total elapsed time spent processing messages by the message flow node", true, Total, Microseconds, MsgFlowNodeLevel}, - "MsgFlowNode/MaximumElapsedTime": metricLookup{"msgflownode_elapsed_time_seconds_max", "Maximum elapsed time spent processing a message by the message flow node", true, Maximum, Microseconds, MsgFlowNodeLevel}, - "MsgFlowNode/MinimumElapsedTime": metricLookup{"msgflownode_elapsed_time_seconds_min", "Minimum elapsed time spent processing a message by the message flow node", true, Minimum, Microseconds, MsgFlowNodeLevel}, - "MsgFlowNode/TotalCpuTime": metricLookup{"msgflownode_cpu_time_seconds_total", "Total CPU time spent processing messages by the message flow node", true, Total, Microseconds, MsgFlowNodeLevel}, - "MsgFlowNode/MaximumCpuTime": metricLookup{"msgflownode_cpu_time_seconds_max", "Maximum CPU time spent processing a message by the message flow node", true, Maximum, Microseconds, MsgFlowNodeLevel}, - "MsgFlowNode/MinimumCpuTime": metricLookup{"msgflownode_cpu_time_seconds_min", "Minimum CPU time spent processing a message by the message flow node", true, Minimum, Microseconds, MsgFlowNodeLevel}, - "MsgFlowNode/TotalInvocations": metricLookup{"msgflownode_messages_total", "Total number of messages processed by the message flow node", true, Total, Count, MsgFlowNodeLevel}, - "MsgFlowNode/InputTerminals": metricLookup{"msgflownode_input_terminals_total", "Total number of input terminals on the message flow node", true, Current, Count, MsgFlowNodeLevel}, - "MsgFlowNode/OutputTerminals": metricLookup{"msgflownode_output_terminals_total", "Total number of output terminals on the message flow node", true, Current, Count, MsgFlowNodeLevel}, - } - - return -} - -// generates metric names mapped from their description -func generateResourceMetricNamesMap() (jvmMetricNamesMap map[string]metricLookup) { - - jvmMetricNamesMap = map[string]metricLookup{ - "JVM/Summary/InitialMemoryInMB": metricLookup{"jvm_summary_initial_memory_bytes", "Initial memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Summary/UsedMemoryInMB": metricLookup{"jvm_summary_used_memory_bytes", "Used memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Summary/CommittedMemoryInMB": metricLookup{"jvm_summary_committed_memory_bytes", "Committed memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Summary/MaxMemoryInMB": metricLookup{"jvm_summary_max_memory_bytes", "Committed memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Summary/CumulativeGCTimeInSeconds": metricLookup{"jvm_summary_gcs_elapsed_time_seconds_total", "Total time spent in GCs for the JVM", true, Current, Seconds, Resource}, - "JVM/Summary/CumulativeNumberOfGCCollections": metricLookup{"jvm_summary_gcs_total", "Total number of GCs for the JVM", true, Current, Count, Resource}, - "JVM/Heap/InitialMemoryInMB": metricLookup{"jvm_heap_initial_memory_bytes", "Initial heap memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Heap/UsedMemoryInMB": metricLookup{"jvm_heap_used_memory_bytes", "Used heap memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Heap/CommittedMemoryInMB": metricLookup{"jvm_heap_committed_memory_bytes", "Committed heap memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Heap/MaxMemoryInMB": metricLookup{"jvm_heap_max_memory_bytes", "Committed heap memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Native/InitialMemoryInMB": metricLookup{"jvm_native_initial_memory_bytes", "Initial native memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Native/UsedMemoryInMB": metricLookup{"jvm_native_used_memory_bytes", "Used native memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Native/CommittedMemoryInMB": metricLookup{"jvm_native_committed_memory_bytes", "Committed native memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/Native/MaxMemoryInMB": metricLookup{"jvm_native_max_memory_bytes", "Committed native memory for the JVM", true, Current, MegaBytes, Resource}, - "JVM/ScavengerGC/CumulativeGCTimeInSeconds": metricLookup{"jvm_scavenger_gcs_elapsed_time_seconds_total", "Total time spent in scavenger GCs for the JVM", true, Current, Seconds, Resource}, - "JVM/ScavengerGC/CumulativeNumberOfGCCollections": metricLookup{"jvm_scavenger_gcs_total", "Total number of scavenger GCs for the JVM", true, Current, Count, Resource}, - "JVM/GlobalGC/CumulativeGCTimeInSeconds": metricLookup{"jvm_global_gcs_elapsed_time_seconds_total", "Total time spent in global GCs for the JVM", true, Current, Seconds, Resource}, - "JVM/GlobalGC/CumulativeNumberOfGCCollections": metricLookup{"jvm_global_gcs_total", "Total number of global GCs for the JVM", true, Current, Count, Resource}, - } - - return -} diff --git a/internal/metrics/mapping_test.go b/internal/metrics/mapping_test.go deleted file mode 100644 index ea1bbf5..0000000 --- a/internal/metrics/mapping_test.go +++ /dev/null @@ -1,147 +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 metrics - -import ( - "encoding/json" - "testing" -) - -func TestGenerateMetricNamesMap(t *testing.T) { - - msgFlowMetricNamesMap, msgFlowNodeMetricNamesMap := generateMetricNamesMap() - - if len(msgFlowMetricNamesMap) != 20 { - t.Errorf("Expected mapping-size=%d; actual %d", 20, len(msgFlowMetricNamesMap)) - } - - if len(msgFlowNodeMetricNamesMap) != 9 { - t.Errorf("Expected mapping-size=%d; actual %d", 9, len(msgFlowNodeMetricNamesMap)) - } - - for _, v := range msgFlowMetricNamesMap { - if v.name == "" { - t.Errorf("Name for metricLookup is empty string: %+v", v) - } - if v.description == "" { - t.Errorf("Description for metricLookup is empty string: %+v", v) - } - } - - for _, v := range msgFlowNodeMetricNamesMap { - if v.name == "" { - t.Errorf("Name for metricLookup is empty string: %+v", v) - } - if v.description == "" { - t.Errorf("Description for metricLookup is empty string: %+v", v) - } - } -} - -func TestGenerateResourceMetricNamesMap(t *testing.T) { - - jvmMetricNamesMap := generateResourceMetricNamesMap() - - if len(jvmMetricNamesMap) != 18 { - t.Errorf("Expected mapping-size=%d; actual %d", 18, len(jvmMetricNamesMap)) - } - - for _, v := range jvmMetricNamesMap { - if v.name == "" { - t.Errorf("Name for metricLookup is empty string: %+v", v) - } - if v.description == "" { - t.Errorf("Description for metricLookup is empty string: %+v", v) - } - } -} - -func TestNewJVMData(t *testing.T) { - resourceStatisticsString := "{\"data\":{\"ResourceStatistics\":{\"brokerLabel\":\"integration_server\",\"brokerUUID\":\"\",\"executionGroupName\":\"websockettest\",\"executionGroupUUID\":\"00000000-0000-0000-0000-000000000000\",\"collectionStartDate\":\"2018-08-28\",\"collectionStartTime\":\"21:13:33\",\"startDate\":\"2018-08-30\",\"startTime\":\"13:50:54\",\"endDate\":\"2018-08-30\",\"endTime\":\"13:51:15\",\"timezone\":\"Europe/London\",\"ResourceType\":[{\"name\":\"JVM\",\"resourceIdentifier\":[{\"name\":\"summary\",\"InitialMemoryInMB\":305,\"UsedMemoryInMB\":40,\"CommittedMemoryInMB\":314,\"MaxMemoryInMB\":-1,\"CumulativeGCTimeInSeconds\":0,\"CumulativeNumberOfGCCollections\":132},{\"name\":\"Heap Memory\",\"InitialMemoryInMB\":32,\"UsedMemoryInMB\":18,\"CommittedMemoryInMB\":34,\"MaxMemoryInMB\":256},{\"name\":\"Non-Heap Memory\",\"InitialMemoryInMB\":273,\"UsedMemoryInMB\":22,\"CommittedMemoryInMB\":280,\"MaxMemoryInMB\":-1},{\"name\":\"Garbage Collection - scavenge\",\"CumulativeGCTimeInSeconds\":0,\"CumulativeNumberOfGCCollections\":131},{\"name\":\"Garbage Collection - global\",\"CumulativeGCTimeInSeconds\":0,\"CumulativeNumberOfGCCollections\":1}]}]}},\"event\":0}" - - var sds StatisticsDataStruct - - unmarshallError := json.Unmarshal([]byte(resourceStatisticsString), &sds) - - if unmarshallError != nil { - t.Errorf("Error parsing json: %e", unmarshallError) - } - - // s, _ := json.Marshal(sds) - // t.Errorf("resource json=%s", string(s)) - - for _, v := range sds.Data.ResourceStatistics.ResourceType { - if v.Name == "JVM" { - jvmData := NewJVMData(v.ResourceIdentifier) - if jvmData.SummaryInitial != 305 { - t.Errorf("Expected value=%d; actual=%d", 305, jvmData.SummaryInitial) - } - if jvmData.SummaryUsed != 40 { - t.Errorf("Expected value=%d; actual=%d", 40, jvmData.SummaryUsed) - } - if jvmData.SummaryCommitted != 314 { - t.Errorf("Expected value=%d; actual=%d", 314, jvmData.SummaryCommitted) - } - if jvmData.SummaryMax != -1 { - t.Errorf("Expected value=%d; actual=%d", -1, jvmData.SummaryMax) - } - if jvmData.SummaryGCTime != 0 { - t.Errorf("Expected value=%d; actual=%d", 0, jvmData.SummaryGCTime) - } - if jvmData.SummaryGCCount != 132 { - t.Errorf("Expected value=%d; actual=%d", 132, jvmData.SummaryGCCount) - } - if jvmData.HeapInitial != 32 { - t.Errorf("Expected value=%d; actual=%d", 32, jvmData.HeapInitial) - } - if jvmData.HeapUsed != 18 { - t.Errorf("Expected value=%d; actual=%d", 18, jvmData.HeapUsed) - } - if jvmData.HeapCommitted != 34 { - t.Errorf("Expected value=%d; actual=%d", 34, jvmData.HeapCommitted) - } - if jvmData.HeapMax != 256 { - t.Errorf("Expected value=%d; actual=%d", 256, jvmData.HeapMax) - } - if jvmData.NativeInitial != 273 { - t.Errorf("Expected value=%d; actual=%d", 273, jvmData.NativeInitial) - } - if jvmData.NativeUsed != 22 { - t.Errorf("Expected value=%d; actual=%d", 22, jvmData.NativeUsed) - } - if jvmData.NativeCommitted != 280 { - t.Errorf("Expected value=%d; actual=%d", 280, jvmData.NativeCommitted) - } - if jvmData.NativeMax != -1 { - t.Errorf("Expected value=%d; actual=%d", -1, jvmData.NativeMax) - } - if jvmData.ScavengerGCTime != 0 { - t.Errorf("Expected value=%d; actual=%d", 0, jvmData.ScavengerGCTime) - } - if jvmData.ScavengerGCCount != 131 { - t.Errorf("Expected value=%d; actual=%d", 131, jvmData.ScavengerGCCount) - } - if jvmData.GlobalGCTime != 0 { - t.Errorf("Expected value=%d; actual=%d", 0, jvmData.GlobalGCTime) - } - if jvmData.GlobalGCCount != 1 { - t.Errorf("Expected value=%d; actual=%d", 1, jvmData.GlobalGCCount) - } - } - } - - //TODO: Doesn't seem to give code coverage of scavenger of global gc data - are these values being picked up? -} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go deleted file mode 100644 index e19d24e..0000000 --- a/internal/metrics/metrics.go +++ /dev/null @@ -1,109 +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 metrics contains code to provide metrics for the queue manager -package metrics - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/ot4i/ace-docker/common/logger" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -const ( - defaultPort = "9483" -) - -var ( - metricsEnabled = false - metricsServer = &http.Server{Addr: ":" + defaultPort} -) - -// GatherMetrics gathers metrics for the integration server -func GatherMetrics(serverName string, log logger.LoggerInterface) { - log.Println("Metrics: Gathering Metrics...") - metricsEnabled = true - - err := startMetricsGathering(serverName, log) - if err != nil { - log.Errorf("Metrics Error: %s", err.Error()) - StopMetricsGathering() - } -} - -// startMetricsGathering starts gathering metrics for the integration server -func startMetricsGathering(serverName string, log logger.LoggerInterface) error { - - defer func() { - if r := recover(); r != nil { - log.Errorf("Metrics Error: %v", r) - } - }() - - log.Println("Metrics: Starting metrics gathering") - - // Start processing metrics - go processMetrics(log, serverName) - - // Wait for metrics to be ready before starting the Prometheus handler - <-startChannel - - // Register metrics - metricsExporter := newExporter(serverName, log) - err := prometheus.Register(metricsExporter) - if err != nil { - return fmt.Errorf("Metrics: Failed to register metrics: %v", err) - } - - // Setup HTTP server to handle requests from Prometheus - http.Handle("/metrics", promhttp.Handler()) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - w.Write([]byte("Status: METRICS ACTIVE")) - }) - - go func() { - err = metricsServer.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Errorf("Metrics: Error: Failed to handle metrics request: %v", err) - - StopMetricsGathering() - } - }() - - return nil -} - -// StopMetricsGathering stops gathering metrics for the integration server -func StopMetricsGathering() { - - if metricsEnabled { - - // Stop processing metrics - stopChannel <- true - - // Shutdown HTTP server - timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - metricsServer.Shutdown(timeout) - } -} diff --git a/internal/metrics/update.go b/internal/metrics/update.go deleted file mode 100644 index fec898b..0000000 --- a/internal/metrics/update.go +++ /dev/null @@ -1,593 +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 metrics contains code to provide metrics for the queue manager -package metrics - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "flag" - "fmt" - "io/ioutil" - "math" - "net" - "net/http" - "net/http/cookiejar" - "net/url" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/ot4i/ace-docker/common/logger" - - "github.com/gorilla/websocket" - "github.com/prometheus/client_golang/prometheus" -) - -var ( - addr = flag.String("addr", "localhost:7600", "http service address") - startChannel = make(chan bool) - stopChannel = make(chan bool, 2) - stopping = false - requestChannel = make(chan bool) - responseChannel = make(chan *MetricsMap) - statisticsChannel = make(chan StatisticsDataStruct, 10) // Block on writing to channel if we already have 10 queued so we don't retrieve any more -) - -type MetricsMap struct { - sync.Mutex - internal map[string]*metricData -} - -func NewMetricsMap() *MetricsMap { - return &MetricsMap{ - internal: make(map[string]*metricData), - } -} - -type Metric struct { - labels prometheus.Labels - value float64 -} - -type metricData struct { - name string - description string - values map[string]*Metric - metricType MetricType - metricUnits MetricUnits - metricLevel MetricLevel -} - -/* -Normalise returns a float64 representation of the metric value normalised to a base metric type (seconds, bytes, etc.) -*/ -func (md *metricData) Normalise(value int) float64 { - f := float64(value) - - if f < 0 { - f = 0 - } - - // Convert microseconds to seconds - if md.metricUnits == Microseconds { - f = f / 1000000 - } - - // Convert megabytes to bytes - if md.metricUnits == MegaBytes { - f = f * 1024 * 1024 - } - - return f -} - -func ReadStatistics(log logger.LoggerInterface) { - // Check if the admin server is secure so we know whether to connect with wss or ws - aceAdminServerSecurity := os.Getenv("ACE_ADMIN_SERVER_SECURITY") - if aceAdminServerSecurity == "" { - log.Printf("Metrics: Can't tell if ace admin server security is enabled defaulting to false") - aceAdminServerSecurity = "false" - } else { - log.Printf("Metrics: ACE_ADMIN_SERVER_SECURITY is %s", aceAdminServerSecurity) - } - - var firstConnect = true - - for { - if stopping { - // Stopping will trigger a read error on the c.ReadJSON call and re-entry into this loop, - // but we want to exit this function when that happens - return - } - - var c *websocket.Conn - var dialError error - - // Use wss with TLS if using the admin server is secured - if aceAdminServerSecurity == "true" { - adminServerCACert := os.Getenv("ACE_ADMIN_SERVER_CA") - - caCertPool := x509.NewCertPool() - if stat, err := os.Stat(adminServerCACert); err == nil && stat.IsDir() { - // path is a directory load all certs - log.Printf("Metrics: Using CA Certificate folder %s", adminServerCACert) - filepath.Walk(adminServerCACert, func(cert string, info os.FileInfo, err error) error { - if strings.HasSuffix(cert, "crt.pem") { - log.Printf("Metrics: Adding Certificate %s to CA pool", cert) - binaryCert, err := ioutil.ReadFile(cert) - if err != nil { - log.Errorf("Metrics: Error reading CA Certificate %s", err) - } - ok := caCertPool.AppendCertsFromPEM(binaryCert) - if !ok { - log.Errorf("Metrics: Failed to parse Certificate %s", cert) - } - } - return nil - }) - } else { - log.Printf("Metrics: Using CA Certificate file %s", adminServerCACert) - caCert, err := ioutil.ReadFile(adminServerCACert) - if err != nil { - log.Errorf("Metrics: Error reading CA Certificate %s", err) - return - } - ok := caCertPool.AppendCertsFromPEM(caCert) - if !ok { - log.Errorf("Metrics: failed to parse root CA Certificate") - } - } - - // Read the key/ cert pair to create tls certificate - adminServerCert := os.Getenv("ACE_ADMIN_SERVER_CERT") - adminServerKey := os.Getenv("ACE_ADMIN_SERVER_KEY") - adminServerCerts, err := tls.LoadX509KeyPair(adminServerCert, adminServerKey) - if err != nil { - if adminServerCert != "" && adminServerKey != "" { - log.Errorf("Metrics: Error reading TLS Certificates: %s", err) - return - } - } else { - log.Printf("Metrics: Using provided cert and key for mutual auth") - } - - aceAdminServerName := os.Getenv("ACE_ADMIN_SERVER_NAME") - if aceAdminServerName == "" { - log.Printf("Metrics: No ace admin server name available") - return - } else { - log.Printf("Metrics: ACE_ADMIN_SERVER_NAME is %s", aceAdminServerName) - } - - u := url.URL{Scheme: "wss", Host: *addr, Path: "/"} - log.Printf("Metrics: Connecting to %s for statistics gathering", u.String()) - d := websocket.Dialer{ - TLSClientConfig: &tls.Config{ - RootCAs: caCertPool, - Certificates: []tls.Certificate{adminServerCerts}, - ServerName: aceAdminServerName, - }, - } - - // Retrieve session if the webusers exist - contentBytes, err := ioutil.ReadFile("/home/aceuser/initial-config/webusers/admin-users.txt") - if err != nil { - log.Printf("Metrics: Cannot find webusers/admin-users.txt file, connecting without user authentication") - } else { - log.Printf("Metrics: Using provided webusers/admin-users.txt, connecting with user authentication") - userPassword := strings.Fields(string(contentBytes)) - username := userPassword[0] - password := userPassword[1] - - var conn *tls.Conn - httpUrl := url.URL{Scheme: "https", Host: *addr, Path: "/"} - tlsConfig := &tls.Config{ - RootCAs: caCertPool, - Certificates: []tls.Certificate{adminServerCerts}, - ServerName: aceAdminServerName, - } - client := &http.Client{ - Transport: &http.Transport{ - DialTLS: func(network, addr string) (net.Conn, error) { - conn, err = tls.Dial(network, addr, tlsConfig) - return conn, err - }, - }, - } - req, _ := http.NewRequest("GET", httpUrl.String(), nil) - req.SetBasicAuth(username, password) - resp, err := client.Do(req) - if err != nil { - log.Errorf("Metrics: Error retrieving session: %s", err) - } - - jar, _ := cookiejar.New(nil) - if resp != nil { - cookies := resp.Cookies() - jar.SetCookies(&httpUrl, cookies) - } - - if jar.Cookies(&httpUrl) != nil { - log.Printf("Metrics: Connecting to %s using session cookie and SSL", u.String()) - d.Jar = jar - } else { - log.Printf("Metrics: Connecting to %s with SSL", u.String()) - } - } - - // Create the websocket connection - c, _, dialError = d.Dial(u.String(), http.Header{"Origin": {u.String()}}) - } else { - wsUrl := url.URL{Scheme: "ws", Host: *addr, Path: "/"} - log.Printf("Metrics: Connecting to %s for statistics", wsUrl.String()) - - d := websocket.DefaultDialer - - // Retrieve session if the webusers exist - contentBytes, err := ioutil.ReadFile("/home/aceuser/initial-config/webusers/admin-users.txt") - if err != nil { - log.Printf("Metrics: Cannot find admin-users.txt file, not retrieving session") - } else { - log.Printf("Metrics: Using provided webusers/admin-users.txt for basic auth session cookie") - userPassword := strings.Fields(string(contentBytes)) - username := userPassword[0] - password := userPassword[1] - - httpUrl := url.URL{Scheme: "http", Host: *addr, Path: "/"} - client := &http.Client{} - req, _ := http.NewRequest("GET", httpUrl.String(), nil) - req.SetBasicAuth(username, password) - resp, err := client.Do(req) - if err != nil { - log.Errorf("Metrics: Error retrieving session: %s", err) - } - - jar, _ := cookiejar.New(nil) - if resp != nil { - cookies := resp.Cookies() - jar.SetCookies(&httpUrl, cookies) - } - - if jar.Cookies(&httpUrl) != nil { - log.Printf("Metrics: Connecting to %s using session cookie", wsUrl.String()) - d.Jar = jar - } else { - log.Printf("Metrics: Connecting to %s without using session cookie", wsUrl.String()) - } - } - // Create the websocket connection - c, _, dialError = d.Dial(wsUrl.String(), http.Header{"Origin": {wsUrl.String()}}) - } - - if dialError == nil { - log.Printf("Metrics: Connected") - if firstConnect { - firstConnect = false - startChannel <- true - } - - defer c.Close() - - // Loop reading from websocket and put messages on the statistics statisticsChannel - // End the loop and reconnect if there is an error reading from the websocket - var readError error - for readError == nil { - var m StatisticsDataStruct - - readError = c.ReadJSON(&m) - if readError == nil { - statisticsChannel <- m - } - } - } else { - log.Errorf("Metrics: Error calling ace admin server webservice endpoint %s", dialError) - log.Println("Metrics: If this repeats then check you have assigned enough memory to your Pod and you aren't running out of memory") - log.Println("Metrics: Sleeping for 5 seconds before retrying to connect to metrics...") - time.Sleep(5 * time.Second) - } - } -} - -// processMetrics processes publications of metric data and handles describe/collect/stop requests -func processMetrics(log logger.LoggerInterface, serverName string) { - log.Println("Metrics: Processing metrics...") - - metrics := initialiseMetrics(log) - - go ReadStatistics(log) - - // Handle update/describe/collect/stop requests - for { - select { - case m := <-statisticsChannel: - newMetrics, parseError := parseMetrics(log, &m) - - if parseError != nil { - log.Println("Metrics: Parse Error:", parseError) - } else { - updateMetrics(log, metrics, newMetrics) - } - case <-requestChannel: - responseChannel <- metrics - case <-stopChannel: - log.Println("Stopping metrics gathering") - stopping = true - return - } - } -} - -// initialiseMetrics sets initial details for all available metrics -func initialiseMetrics(log logger.LoggerInterface) *MetricsMap { - - metrics := NewMetricsMap() - msgFlowMetricNamesMap, msgFlowNodeMetricNamesMap := generateMetricNamesMap() - - for k, v := range msgFlowMetricNamesMap { - if v.enabled { - // Set metric details - metric := metricData{ - name: v.name, - description: v.description, - metricType: v.metricType, - metricUnits: v.metricUnits, - metricLevel: v.metricLevel, - } - metric.values = make(map[string]*Metric) - - // Add metric - metrics.internal[k] = &metric - } - } - - for k, v := range msgFlowNodeMetricNamesMap { - if v.enabled { - // Set metric details - metric := metricData{ - name: v.name, - description: v.description, - metricType: v.metricType, - metricUnits: v.metricUnits, - metricLevel: v.metricLevel, - } - metric.values = make(map[string]*Metric) - - // Add metric - metrics.internal[k] = &metric - } - } - - jvmResourceMetricNamesMap := generateResourceMetricNamesMap() - - for k, v := range jvmResourceMetricNamesMap { - if v.enabled { - // Set metric details - metric := metricData{ - name: v.name, - description: v.description, - metricType: v.metricType, - metricUnits: v.metricUnits, - metricLevel: v.metricLevel, - } - metric.values = make(map[string]*Metric) - - // Add metric - metrics.internal[k] = &metric - } - } - - return metrics -} - -func parseMetrics(log logger.LoggerInterface, m *StatisticsDataStruct) (*MetricsMap, error) { - if m.Event == ResourceStatisticsData { - return parseResourceMetrics(log, m) - } else if m.Event == AccountingAndStatisticsData { - return parseAccountingMetrics(log, m) - } else { - return nil, fmt.Errorf("Metrics: Unable to parse data with event: %d", m.Event) - } -} - -func parseAccountingMetrics(log logger.LoggerInterface, m *StatisticsDataStruct) (*MetricsMap, error) { - parsedMetrics := NewMetricsMap() - - msgFlowMetricNamesMap, msgFlowNodeMetricNamesMap := generateMetricNamesMap() - - accountingOrigin := m.Data.WMQIStatisticsAccounting.MessageFlow.AccountingOrigin - serverName := m.Data.WMQIStatisticsAccounting.MessageFlow.ExecutionGroupName - applicationName := m.Data.WMQIStatisticsAccounting.MessageFlow.ApplicationName - msgflowName := m.Data.WMQIStatisticsAccounting.MessageFlow.MessageFlowName - - if msgflowName == "" { - err := errors.New("Metrics: parse error - no message flow name in statistics") - return parsedMetrics, err - } - - flowValuesMap := map[string]int{ - "MsgFlow/TotalElapsedTime": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalElapsedTime, - "MsgFlow/MaximumElapsedTime": m.Data.WMQIStatisticsAccounting.MessageFlow.MaximumElapsedTime, - "MsgFlow/MinimumElapsedTime": m.Data.WMQIStatisticsAccounting.MessageFlow.MinimumElapsedTime, - "MsgFlow/TotalCpuTime": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalCPUTime, - "MsgFlow/MaximumCpuTime": m.Data.WMQIStatisticsAccounting.MessageFlow.MaximumCPUTime, - "MsgFlow/MinimumCpuTime": m.Data.WMQIStatisticsAccounting.MessageFlow.MinimumCPUTime, - "MsgFlow/TotalSizeOfInputMessages": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalSizeOfInputMessages, - "MsgFlow/MaximumSizeOfInputMessages": m.Data.WMQIStatisticsAccounting.MessageFlow.MaximumSizeOfInputMessages, - "MsgFlow/MinimumSizeOfInputMessages": m.Data.WMQIStatisticsAccounting.MessageFlow.MinimumSizeOfInputMessages, - "MsgFlow/TotalInputMessages": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalInputMessages, - "MsgFlow/TotalCPUTimeWaiting": m.Data.WMQIStatisticsAccounting.MessageFlow.CPUTimeWaitingForInputMessage, - "MsgFlow/TotalElapsedTimeWaiting": m.Data.WMQIStatisticsAccounting.MessageFlow.ElapsedTimeWaitingForInputMessage, - "MsgFlow/NumberOfThreadsInPool": m.Data.WMQIStatisticsAccounting.MessageFlow.NumberOfThreadsInPool, - "MsgFlow/TimesMaximumNumberOfThreadsReached": m.Data.WMQIStatisticsAccounting.MessageFlow.TimesMaximumNumberOfThreadsReached, - "MsgFlow/TotalNumberOfMQErrors": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalNumberOfMQErrors, - "MsgFlow/TotalNumberOfMessagesWithErrors": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalNumberOfMessagesWithErrors, - "MsgFlow/TotalNumberOfErrorsProcessingMessages": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalNumberOfErrorsProcessingMessages, - "MsgFlow/TotalNumberOfTimeOutsWaitingForRepliesToAggregateMessages": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalNumberOfTimeOutsWaitingForRepliesToAggregateMessages, - "MsgFlow/TotalNumberOfCommits": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalNumberOfCommits, - "MsgFlow/TotalNumberOfBackouts": m.Data.WMQIStatisticsAccounting.MessageFlow.TotalNumberOfBackouts, - } - - /* - Process flow level accounting and statistics data - */ - for k, v := range flowValuesMap { - metricDesc := msgFlowMetricNamesMap[k] - if metricDesc.enabled { - metric := metricData{ - name: metricDesc.name, - description: metricDesc.description, - metricType: metricDesc.metricType, - metricUnits: metricDesc.metricUnits, - metricLevel: metricDesc.metricLevel, - } - metric.values = make(map[string]*Metric) - metric.values[accountingOrigin+"_"+applicationName+"_"+msgflowName] = &Metric{labels: prometheus.Labels{msgflowPrefix: msgflowName, serverLabel: serverName, applicationLabel: applicationName, originLabel: accountingOrigin}, value: metric.Normalise(v)} - parsedMetrics.internal[k] = &metric - } - } - - /* - Process node level accounting and statistics data - */ - for k, v := range msgFlowNodeMetricNamesMap { - - if v.enabled { - metric := metricData{ - name: v.name, - description: v.description, - metricType: v.metricType, - metricUnits: v.metricUnits, - metricLevel: v.metricLevel, - } - metric.values = make(map[string]*Metric) - - for _, node := range m.Data.WMQIStatisticsAccounting.Nodes { - nodeValuesMap := map[string]int{ - "MsgFlowNode/TotalElapsedTime": node.TotalElapsedTime, - "MsgFlowNode/MaximumElapsedTime": node.MaximumElapsedTime, - "MsgFlowNode/MinimumElapsedTime": node.MinimumElapsedTime, - "MsgFlowNode/TotalCpuTime": node.TotalCPUTime, - "MsgFlowNode/MaximumCpuTime": node.MaximumCPUTime, - "MsgFlowNode/MinimumCpuTime": node.MinimumCPUTime, - "MsgFlowNode/TotalInvocations": node.CountOfInvocations, - "MsgFlowNode/InputTerminals": node.NumberOfInputTerminals, - "MsgFlowNode/OutputTerminals": node.NumberOfOutputTerminals, - } - msgflownodeName := node.Label - msgflownodeType := node.Type - - metric.values[accountingOrigin+"_"+applicationName+"_"+msgflowName+"_"+msgflownodeName] = &Metric{labels: prometheus.Labels{msgflownodeLabel: msgflownodeName, msgflownodeTypeLabel: msgflownodeType, msgflowLabel: msgflowName, serverLabel: serverName, applicationLabel: applicationName, originLabel: accountingOrigin}, value: metric.Normalise(nodeValuesMap[k])} - } - parsedMetrics.internal[k] = &metric - } - } - - return parsedMetrics, nil -} - -func parseResourceMetrics(log logger.LoggerInterface, m *StatisticsDataStruct) (*MetricsMap, error) { - parsedResourceMetrics := NewMetricsMap() - - serverName := m.Data.ResourceStatistics.ExecutionGroupName - - for _, v := range m.Data.ResourceStatistics.ResourceType { - switch v.Name { - case "JVM": - jvmData := NewJVMData(v.ResourceIdentifier) - - jvmResourceMetricNamesMap := generateResourceMetricNamesMap() - - jvmValuesMap := map[string]int{ - "JVM/Summary/InitialMemoryInMB": jvmData.SummaryInitial, - "JVM/Summary/UsedMemoryInMB": jvmData.SummaryUsed, - "JVM/Summary/CommittedMemoryInMB": jvmData.SummaryCommitted, - "JVM/Summary/MaxMemoryInMB": jvmData.SummaryMax, - "JVM/Summary/CumulativeGCTimeInSeconds": jvmData.SummaryGCTime, - "JVM/Summary/CumulativeNumberOfGCCollections": jvmData.SummaryGCCount, - "JVM/Heap/InitialMemoryInMB": jvmData.HeapInitial, - "JVM/Heap/UsedMemoryInMB": jvmData.HeapUsed, - "JVM/Heap/CommittedMemoryInMB": jvmData.HeapCommitted, - "JVM/Heap/MaxMemoryInMB": jvmData.HeapMax, - "JVM/Native/InitialMemoryInMB": jvmData.NativeInitial, - "JVM/Native/UsedMemoryInMB": jvmData.NativeUsed, - "JVM/Native/CommittedMemoryInMB": jvmData.NativeCommitted, - "JVM/Native/MaxMemoryInMB": jvmData.NativeMax, - "JVM/ScavengerGC/CumulativeGCTimeInSeconds": jvmData.ScavengerGCTime, - "JVM/ScavengerGC/CumulativeNumberOfGCCollections": jvmData.ScavengerGCCount, - "JVM/GlobalGC/CumulativeGCTimeInSeconds": jvmData.GlobalGCTime, - "JVM/GlobalGC/CumulativeNumberOfGCCollections": jvmData.GlobalGCCount, - } - - for metricKey, metricDesc := range jvmResourceMetricNamesMap { - if metricDesc.enabled { - metric := metricData{ - name: metricDesc.name, - description: metricDesc.description, - metricType: metricDesc.metricType, - metricUnits: metricDesc.metricUnits, - metricLevel: metricDesc.metricLevel, - } - metric.values = make(map[string]*Metric) - metric.values[metricKey] = &Metric{labels: prometheus.Labels{serverLabel: serverName}, value: metric.Normalise(jvmValuesMap[metricKey])} - parsedResourceMetrics.internal[metricKey] = &metric - } - } - default: - //TODO: Support other resource statistic types - } - } - - return parsedResourceMetrics, nil -} - -// updateMetrics updates values for all available metrics -func updateMetrics(log logger.LoggerInterface, mm1 *MetricsMap, mm2 *MetricsMap) { - mm1.Lock() - mm2.Lock() - defer mm1.Unlock() - defer mm2.Unlock() - - for k, md2 := range mm2.internal { - if md1, ok := mm1.internal[k]; ok { - //Iterate over the labels - for l, m2 := range md2.values { - if m1, ok := md1.values[l]; ok { - switch md1.metricType { - case Total: - md1.values[l].value = m1.value + m2.value - case Maximum: - md1.values[l].value = math.Max(m1.value, m2.value) - case Minimum: - md1.values[l].value = math.Min(m1.value, m2.value) - case Current: - md1.values[l].value = m2.value - default: - log.Printf("Metrics: Should not reach here - only a set enumeration of metric types. %d is unknown...", md1.metricType) - } - } else { - md1.values[l] = m2 - } - } - } else { - mm1.internal[k] = mm2.internal[k] - } - } -} diff --git a/internal/metrics/update_test.go b/internal/metrics/update_test.go deleted file mode 100644 index 11be2eb..0000000 --- a/internal/metrics/update_test.go +++ /dev/null @@ -1,550 +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 metrics - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/prometheus/client_golang/prometheus" -) - -var normaliseTests = []struct { - md metricData - val int - expected float64 -}{ - {metricData{metricUnits: Microseconds}, 1, 0.000001}, - {metricData{metricUnits: Microseconds}, 2000, 0.002}, - {metricData{metricUnits: Microseconds}, 0, 0.0}, - {metricData{metricUnits: Microseconds}, -1, 0.0}, - {metricData{metricUnits: Seconds}, 1, 1.0}, - {metricData{metricUnits: Seconds}, 100, 100.0}, - {metricData{metricUnits: Seconds}, 0, 0.0}, - {metricData{metricUnits: Seconds}, -1, 0.0}, - {metricData{metricUnits: Bytes}, 1, 1.0}, - {metricData{metricUnits: Bytes}, 1999, 1999.0}, - {metricData{metricUnits: Bytes}, 0, 0.0}, - {metricData{metricUnits: Bytes}, -1, 0.0}, - {metricData{metricUnits: MegaBytes}, 1, 1048576.0}, - {metricData{metricUnits: MegaBytes}, 50, 52428800.0}, - {metricData{metricUnits: MegaBytes}, 0, 0.0}, - {metricData{metricUnits: MegaBytes}, -1, 0.0}, - {metricData{metricUnits: Count}, 1, 1.0}, - {metricData{metricUnits: Count}, 542, 542.0}, - {metricData{metricUnits: Count}, 0, 0.0}, - {metricData{metricUnits: Count}, -1, 0.0}, -} - -func TestNormalise(t *testing.T) { - for _, tt := range normaliseTests { - t.Run(fmt.Sprintf("%d:%d", tt.md.metricUnits, tt.val), func(t *testing.T) { - actual := tt.md.Normalise(tt.val) - if actual != tt.expected { - t.Errorf("Expected %d of type %d to normalise to %f, but got %f", tt.val, tt.md.metricUnits, tt.expected, actual) - } - }) - } -} - -func TestInitialiseMetrics(t *testing.T) { - - /* - Get all the metric maps - */ - msgFlowMetricNamesMap, msgFlowNodeMetricNamesMap := generateMetricNamesMap() - jvmMetricNamesMap := generateResourceMetricNamesMap() - - /* - Merge all the metric name maps into a single map to iterate over - */ - metricMapArray := [3]map[string]metricLookup{msgFlowMetricNamesMap, msgFlowNodeMetricNamesMap, jvmMetricNamesMap} - - aggregatedMetricsMap := make(map[string]metricLookup) - disabledMetricsMap := make(map[string]metricLookup) - - for _, m := range metricMapArray { - for k, v := range m { - if v.enabled { - aggregatedMetricsMap[k] = v - } else { - disabledMetricsMap[k] = v - } - } - } - - /* - Initialise the metrics and check that: - - All entries from the metric name maps that are enabled are included - - There are no entries that are not in the metric name maps - */ - metrics := initialiseMetrics(getTestLogger()) - - t.Logf("Iterating over aggregated metric name maps") - for k, v := range aggregatedMetricsMap { - t.Logf("- %s", k) - - metric, ok := metrics.internal[k] - if !ok { - t.Error("Expected metric not found in map") - } else { - if metric.name != v.name { - t.Errorf("Expected name=%s; actual %s", v.name, metric.name) - } - if metric.description != v.description { - t.Errorf("Expected description=%s; actual %s", v.description, metric.description) - } - if metric.metricType != v.metricType { - t.Errorf("Expected metricType=%v; actual %v", v.metricType, metric.metricType) - } - if metric.metricLevel != v.metricLevel { - t.Errorf("Expected metricLevel=%v; actual %v", v.metricLevel, metric.metricLevel) - } - if metric.metricUnits != v.metricUnits { - t.Errorf("Expected metricUnits=%v; actual %v", v.metricUnits, metric.metricUnits) - } - if len(metric.values) != 0 { - t.Errorf("Expected values-size=%d; actual %d", 0, len(metric.values)) - } - } - } - - if len(metrics.internal) != len(aggregatedMetricsMap) { - t.Errorf("Map contains unexpected metrics, map size=%d", len(metrics.internal)) - } - - t.Logf("Iterating over map of disabled metric names") - for k, _ := range disabledMetricsMap { - t.Logf("- %s", k) - - metric, ok := metrics.internal[k] - if ok { - t.Errorf("Unexpected metric (%s) found in map: %+v", metric.name, metric) - } - } -} - -func TestParseMetrics_AccountingAndStatistics(t *testing.T) { - log := getTestLogger() - - expectedValues := map[string]int{ - "MsgFlow/TotalElapsedTime": 310845, - "MsgFlow/MaximumElapsedTime": 54772, - "MsgFlow/MinimumElapsedTime": 49184, - "MsgFlow/TotalCpuTime": 262984, - "MsgFlow/MaximumCpuTime": 53386, - "MsgFlow/MinimumCpuTime": 40644, - "MsgFlow/TotalSizeOfInputMessages": 2376, - "MsgFlow/MaximumSizeOfInputMessages": 396, - "MsgFlow/MinimumSizeOfInputMessages": 396, - "MsgFlow/TotalInputMessages": 6, - "MsgFlow/TotalCPUTimeWaiting": 125000, - "MsgFlow/TotalElapsedTimeWaiting": 18932397, - "MsgFlow/NumberOfThreadsInPool": 1, - "MsgFlow/TimesMaximumNumberOfThreadsReached": 6, - "MsgFlow/TotalNumberOfMQErrors": 0, - "MsgFlow/TotalNumberOfMessagesWithErrors": 0, - "MsgFlow/TotalNumberOfErrorsProcessingMessages": 0, - "MsgFlow/TotalNumberOfTimeOutsWaitingForRepliesToAggregateMessages": 0, - "MsgFlow/TotalNumberOfCommits": 0, - "MsgFlow/TotalNumberOfBackouts": 0, - } - - expectedNodeValues := map[string]int{ - "HTTP Input/MsgFlowNode/TotalElapsedTime": 62219, - "HTTP Input/MsgFlowNode/MaximumElapsedTime": 11826, - "HTTP Input/MsgFlowNode/MinimumElapsedTime": 8797, - "HTTP Input/MsgFlowNode/TotalCpuTime": 20747, - "HTTP Input/MsgFlowNode/MaximumCpuTime": 10440, - "HTTP Input/MsgFlowNode/MinimumCpuTime": 1, - "HTTP Input/MsgFlowNode/TotalInvocations": 6, - "HTTP Input/MsgFlowNode/InputTerminals": 0, - "HTTP Input/MsgFlowNode/OutputTerminals": 4, - "HTTP Reply/MsgFlowNode/TotalElapsedTime": 248626, - "HTTP Reply/MsgFlowNode/MaximumElapsedTime": 42946, - "HTTP Reply/MsgFlowNode/MinimumElapsedTime": 37639, - "HTTP Reply/MsgFlowNode/TotalCpuTime": 242237, - "HTTP Reply/MsgFlowNode/MaximumCpuTime": 42946, - "HTTP Reply/MsgFlowNode/MinimumCpuTime": 31250, - "HTTP Reply/MsgFlowNode/TotalInvocations": 6, - "HTTP Reply/MsgFlowNode/InputTerminals": 1, - "HTTP Reply/MsgFlowNode/OutputTerminals": 2, - } - - /* - Parse a JSON string into a StatisticsDataStruct - */ - accountingAndStatisticsString := - "{\"data\":{\"WMQIStatisticsAccounting\":{\"RecordType\":\"SnapShot\",\"RecordCode\":\"SnapShot\",\"MessageFlow\":{\"BrokerLabel\":\"integration_server\",\"BrokerUUID\":\"\",\"ExecutionGroupName\":\"testintegrationserver\",\"ExecutionGroupUUID\":\"00000000-0000-0000-0000-000000000000\",\"MessageFlowName\":\"msgflow1\",\"ApplicationName\":\"application1\",\"StartDate\":\"2018-08-30\",\"StartTime\":\"13:51:11.277\",\"GMTStartTime\":\"2018-08-30T12:51:11.277+00:00\",\"EndDate\":\"2018-08-30\",\"EndTime\":\"13:51:31.514\",\"GMTEndTime\":\"2018-08-30T12:51:31.514+00:00\",\"TotalElapsedTime\":310845,\"MaximumElapsedTime\":54772,\"MinimumElapsedTime\":49184,\"TotalCPUTime\":262984,\"MaximumCPUTime\":53386,\"MinimumCPUTime\":40644,\"CPUTimeWaitingForInputMessage\":125000,\"ElapsedTimeWaitingForInputMessage\":18932397,\"TotalInputMessages\":6,\"TotalSizeOfInputMessages\":2376,\"MaximumSizeOfInputMessages\":396,\"MinimumSizeOfInputMessages\":396,\"NumberOfThreadsInPool\":1,\"TimesMaximumNumberOfThreadsReached\":6,\"TotalNumberOfMQErrors\":0,\"TotalNumberOfMessagesWithErrors\":0,\"TotalNumberOfErrorsProcessingMessages\":0,\"TotalNumberOfTimeOutsWaitingForRepliesToAggregateMessages\":0,\"TotalNumberOfCommits\":0,\"TotalNumberOfBackouts\":0,\"AccountingOrigin\":\"Anonymous\"},\"NumberOfThreads\":0,\"ThreadStatistics\":[],\"NumberOfNodes\":2,\"Nodes\":[{\"Label\":\"HTTP Input\",\"Type\":\"WSInputNode\",\"TotalElapsedTime\":62219,\"MaximumElapsedTime\":11826,\"MinimumElapsedTime\":8797,\"TotalCPUTime\":20747,\"MaximumCPUTime\":10440,\"MinimumCPUTime\":1,\"CountOfInvocations\":6,\"NumberOfInputTerminals\":0,\"NumberOfOutputTerminals\":4,\"TerminalStatistics\":[]},{\"Label\":\"HTTP Reply\",\"Type\":\"WSReplyNode\",\"TotalElapsedTime\":248626,\"MaximumElapsedTime\":42946,\"MinimumElapsedTime\":37639,\"TotalCPUTime\":242237,\"MaximumCPUTime\":42946,\"MinimumCPUTime\":31250,\"CountOfInvocations\":6,\"NumberOfInputTerminals\":1,\"NumberOfOutputTerminals\":2,\"TerminalStatistics\":[]}]}},\"event\":2}" - - var sds StatisticsDataStruct - - unmarshallError := json.Unmarshal([]byte(accountingAndStatisticsString), &sds) - - if unmarshallError != nil { - t.Errorf("Error parsing json: %e", unmarshallError) - return - } - - mm, parseError := parseMetrics(log, &sds) - if parseError != nil { - t.Errorf("Error parsing metrics: %e", parseError) - return - } - msgFlowMetricNamesMap, msgFlowNodeMetricNamesMap := generateMetricNamesMap() - - for k, v := range msgFlowMetricNamesMap { - if v.enabled == false { - metric, ok := mm.internal[k] - if ok { - t.Errorf("Unexpected metric (%s) found in map: %+v", k, metric) - } - } else { - metric, ok := mm.internal[k] - if !ok { - t.Errorf("Missing expected metric (%s)", k) - } - if metric.name != v.name { - t.Errorf("Expected name=%s; actual %s", v.name, metric.name) - } - if metric.description != v.description { - t.Errorf("Expected description=%s; actual %s", v.description, metric.description) - } - if metric.metricType != v.metricType { - t.Errorf("Expected metricType=%v; actual %v", v.metricType, metric.metricType) - } - if metric.metricLevel != v.metricLevel { - t.Errorf("Expected metricLevel=%v; actual %v", v.metricLevel, metric.metricLevel) - } - if metric.metricUnits != v.metricUnits { - t.Errorf("Expected metricUnits=%v; actual %v", v.metricUnits, metric.metricUnits) - } - if len(metric.values) != 1 { - t.Errorf("Expected values-size=%d; actual %d", 1, len(metric.values)) - } - - for vk, values := range metric.values { - if vk != "Anonymous_application1_msgflow1" { - t.Errorf("Expected values key=%s; actual %s", "Anonymous_application1_msgflow1", vk) - } - - for label, labelValue := range values.labels { - switch label { - case serverLabel: - if labelValue != "testintegrationserver" { - t.Errorf("Expected server label=%s; actual %s", "testintegrationserver", labelValue) - } - case applicationLabel: - if labelValue != "application1" { - t.Errorf("Expected application label=%s; actual %s", "application1", labelValue) - } - case msgflowPrefix: - if labelValue != "msgflow1" { - t.Errorf("Expected msgflow label=%s; actual %s", "msgflow1", labelValue) - } - case originLabel: - if labelValue != "Anonymous" { - t.Errorf("Expected origin label=%s; actual %s", "Anonymous", labelValue) - } - default: - t.Errorf("Unexpected label (%s) found for metric: %s", label, metric.name) - } - } - - if values.value != metric.Normalise(expectedValues[k]) { - t.Errorf("Expected %s value=%f; actual %f", metric.name, metric.Normalise(expectedValues[k]), values.value) - } - } - } - } - - for k, v := range msgFlowNodeMetricNamesMap { - if v.enabled == false { - metric, ok := mm.internal[k] - if ok { - t.Errorf("Unexpected metric (%s) found in map: %+v", metric.name, metric) - } - } else { - metric, ok := mm.internal[k] - if !ok { - t.Errorf("Missing expected metric (%s)", k) - } - if metric.name != v.name { - t.Errorf("Expected name=%s; actual %s", v.name, metric.name) - } - if metric.description != v.description { - t.Errorf("Expected description=%s; actual %s", v.description, metric.description) - } - if metric.metricType != v.metricType { - t.Errorf("Expected metricType=%v; actual %v", v.metricType, metric.metricType) - } - if metric.metricLevel != v.metricLevel { - t.Errorf("Expected metricLevel=%v; actual %v", v.metricLevel, metric.metricLevel) - } - if metric.metricUnits != v.metricUnits { - t.Errorf("Expected metricUnits=%v; actual %v", v.metricUnits, metric.metricUnits) - } - if len(metric.values) != 2 { - t.Errorf("Expected values-size=%d; actual %d", 2, len(metric.values)) - } - - for vk, values := range metric.values { - - nodeName, ok := values.labels["msgflownode"] - if !ok { - t.Errorf("Missing label for msgflownode name: %s", k) - } - - if vk != "Anonymous_application1_msgflow1_"+nodeName { - t.Errorf("Expected values key=%s; actual %s", "Anonymous_application1_msgflow1_"+nodeName, vk) - } - - for label, labelValue := range values.labels { - switch label { - case serverLabel: - if labelValue != "testintegrationserver" { - t.Errorf("Expected server label=%s; actual %s", "testintegrationserver", labelValue) - } - case applicationLabel: - if labelValue != "application1" { - t.Errorf("Expected application label=%s; actual %s", "application1", labelValue) - } - case msgflowPrefix: - if labelValue != "msgflow1" { - t.Errorf("Expected msgflow label=%s; actual %s", "msgflow1", labelValue) - } - case originLabel: - if labelValue != "Anonymous" { - t.Errorf("Expected origin label=%s; actual %s", "Anonymous", labelValue) - } - case msgflownodeLabel: - // TODO: Check label value - case msgflownodeTypeLabel: - // TODO: Check label value - default: - t.Errorf("Unexpected label (%s) found for metric: %s", label, metric.name) - } - } - - if values.value != metric.Normalise(expectedNodeValues[nodeName+"/"+k]) { - t.Errorf("Expected %s value=%f; actual %f", metric.name, metric.Normalise(expectedValues[nodeName+"/"+k]), values.value) - } - } - } - } -} - -func TestParseMetrics_ResourceStatistics(t *testing.T) { - log := getTestLogger() - - expectedValues := map[string]int{ - "JVM/Summary/InitialMemoryInMB": 305, - "JVM/Summary/UsedMemoryInMB": 40, - "JVM/Summary/CommittedMemoryInMB": 314, - "JVM/Summary/MaxMemoryInMB": -1, - "JVM/Summary/CumulativeGCTimeInSeconds": 0, - "JVM/Summary/CumulativeNumberOfGCCollections": 132, - "JVM/Heap/InitialMemoryInMB": 32, - "JVM/Heap/UsedMemoryInMB": 18, - "JVM/Heap/CommittedMemoryInMB": 34, - "JVM/Heap/MaxMemoryInMB": 256, - "JVM/Native/InitialMemoryInMB": 273, - "JVM/Native/UsedMemoryInMB": 22, - "JVM/Native/CommittedMemoryInMB": 280, - "JVM/Native/MaxMemoryInMB": -1, - "JVM/ScavengerGC/CumulativeGCTimeInSeconds": 0, - "JVM/ScavengerGC/CumulativeNumberOfGCCollections": 131, - "JVM/GlobalGC/CumulativeGCTimeInSeconds": 0, - "JVM/GlobalGC/CumulativeNumberOfGCCollections": 1, - } - - resourceStatisticsString := "{\"data\":{\"ResourceStatistics\":{\"brokerLabel\":\"integration_server\",\"brokerUUID\":\"\",\"executionGroupName\":\"testintegrationserver\",\"executionGroupUUID\":\"00000000-0000-0000-0000-000000000000\",\"collectionStartDate\":\"2018-08-28\",\"collectionStartTime\":\"21:13:33\",\"startDate\":\"2018-08-30\",\"startTime\":\"13:50:54\",\"endDate\":\"2018-08-30\",\"endTime\":\"13:51:15\",\"timezone\":\"Europe/London\",\"ResourceType\":[{\"name\":\"JVM\",\"resourceIdentifier\":[{\"name\":\"summary\",\"InitialMemoryInMB\":305,\"UsedMemoryInMB\":40,\"CommittedMemoryInMB\":314,\"MaxMemoryInMB\":-1,\"CumulativeGCTimeInSeconds\":0,\"CumulativeNumberOfGCCollections\":132},{\"name\":\"Heap Memory\",\"InitialMemoryInMB\":32,\"UsedMemoryInMB\":18,\"CommittedMemoryInMB\":34,\"MaxMemoryInMB\":256},{\"name\":\"Non-Heap Memory\",\"InitialMemoryInMB\":273,\"UsedMemoryInMB\":22,\"CommittedMemoryInMB\":280,\"MaxMemoryInMB\":-1},{\"name\":\"Garbage Collection - scavenge\",\"CumulativeGCTimeInSeconds\":0,\"CumulativeNumberOfGCCollections\":131},{\"name\":\"Garbage Collection - global\",\"CumulativeGCTimeInSeconds\":0,\"CumulativeNumberOfGCCollections\":1}]}]}},\"event\":0}" - - var sds StatisticsDataStruct - - unmarshallError := json.Unmarshal([]byte(resourceStatisticsString), &sds) - - if unmarshallError != nil { - t.Errorf("Error parsing json: %e", unmarshallError) - } - - mm, parseError := parseMetrics(log, &sds) - if parseError != nil { - t.Errorf("Error parsing metrics: %e", parseError) - return - } - jvmMetricNamesMap := generateResourceMetricNamesMap() - - for k, v := range jvmMetricNamesMap { - if v.enabled == false { - metric, ok := mm.internal[k] - if ok { - t.Errorf("Unexpected metric (%s) found in map: %+v", metric.name, metric) - } - } else { - metric, ok := mm.internal[k] - if !ok { - t.Errorf("Missing expected metric (%s)", k) - } - if metric.name != v.name { - t.Errorf("Expected name=%s; actual %s", v.name, metric.name) - } - if metric.description != v.description { - t.Errorf("Expected description=%s; actual %s", v.description, metric.description) - } - if metric.metricType != v.metricType { - t.Errorf("Expected metricType=%v; actual %v", v.metricType, metric.metricType) - } - if metric.metricLevel != v.metricLevel { - t.Errorf("Expected metricLevel=%v; actual %v", v.metricLevel, metric.metricLevel) - } - if metric.metricUnits != v.metricUnits { - t.Errorf("Expected metricUnits=%v; actual %v", v.metricUnits, metric.metricUnits) - } - if len(metric.values) != 1 { - t.Errorf("Expected values-size=%d; actual %d", 1, len(metric.values)) - } - - for _, values := range metric.values { - for label, labelValue := range values.labels { - switch label { - case serverLabel: - if labelValue != "testintegrationserver" { - t.Errorf("Expected server label=%s; actual %s", "testintegrationserver", labelValue) - } - default: - t.Errorf("Unexpected label (%s) found for metric: %s", label, metric.name) - } - } - - if values.value != metric.Normalise(expectedValues[k]) { - t.Errorf("Expected %s value=%f; actual %f", metric.name, metric.Normalise(expectedValues[k]), values.value) - } - } - } - } -} - -var updateTests = []struct { - mType MetricType - mValue1 float64 - mValue2 float64 - expected float64 -}{ - {Total, 9.0, 5.0, 14.0}, - {Total, 0.0, 5.0, 5.0}, - {Total, 32.0, 0.0, 32.0}, - {Total, 0.0, 0.0, 0.0}, - {Minimum, 0.0, 0.0, 0.0}, - {Minimum, 1.0, 0.0, 0.0}, - {Minimum, 0.0, 3.4, 0.0}, - {Minimum, 5.0, 23.7, 5.0}, - {Minimum, 31.0, 30.9, 30.9}, - {Maximum, 0.0, 0.0, 0.0}, - {Maximum, 1.0, 0.0, 1.0}, - {Maximum, 0.0, 3.4, 3.4}, - {Maximum, 5.0, 23.7, 23.7}, - {Maximum, 31.0, 30.9, 31.0}, - {Current, 0.0, 0.0, 0.0}, - {Current, 1.0, 0.0, 0.0}, - {Current, 0.0, 3.4, 3.4}, - {Current, 5.0, 23.7, 23.7}, - {Current, 31.0, 30.9, 30.9}, -} - -func TestUpdateMetrics_Simple(t *testing.T) { - log := getTestLogger() - - for _, tt := range updateTests { - mm1 := NewMetricsMap() - mm2 := NewMetricsMap() - - m1 := metricData{metricType: tt.mType} - m1.values = make(map[string]*Metric) - m1.values["test_val_key"] = &Metric{labels: prometheus.Labels{serverLabel: "test_server"}, value: tt.mValue1} - mm1.internal["test_mm_key"] = &m1 - - m2 := metricData{metricType: tt.mType} - m2.values = make(map[string]*Metric) - m2.values["test_val_key"] = &Metric{labels: prometheus.Labels{serverLabel: "test_server"}, value: tt.mValue2} - mm2.internal["test_mm_key"] = &m2 - - t.Run(fmt.Sprintf("%d", tt.mType), func(t *testing.T) { - updateMetrics(log, mm1, mm2) - actual := mm1.internal["test_mm_key"].values["test_val_key"].value - if actual != tt.expected { - t.Errorf("Expected update of type:%d with values %f and %f to result in a new value of %f, but actual=%f", tt.mType, tt.mValue1, tt.mValue2, tt.expected, actual) - } - }) - } -} - -func TestUpdateMetrics_NewValue(t *testing.T) { - log := getTestLogger() - - mm1 := NewMetricsMap() - mm2 := NewMetricsMap() - - m1 := metricData{metricType: Total} - m1.values = make(map[string]*Metric) - m1.values["non_existent_test_key"] = &Metric{labels: prometheus.Labels{serverLabel: "test_server"}, value: 4.0} - mm1.internal["test_mm_key"] = &m1 - - m2 := metricData{metricType: Total} - m2.values = make(map[string]*Metric) - m2.values["existing_test_key"] = &Metric{labels: prometheus.Labels{serverLabel: "test_server"}, value: 7.1} - mm2.internal["test_mm_key"] = &m2 - - updateMetrics(log, mm1, mm2) - actual1 := mm1.internal["test_mm_key"].values["existing_test_key"].value - if actual1 != 7.1 { - t.Errorf("Value for metric key existing_test_key expected:%f, actual=%f", 7.1, actual1) - } - - actual2 := mm1.internal["test_mm_key"].values["non_existent_test_key"].value - if actual2 != 4.0 { - t.Errorf("Value for metric key non_existent_test_key expected:%f, actual=%f", 4.0, actual2) - } -} - -func TestUpdateMetrics_NewKey(t *testing.T) { - log := getTestLogger() - - mm1 := NewMetricsMap() - mm2 := NewMetricsMap() - - m1 := metricData{metricType: Total} - m1.values = make(map[string]*Metric) - m1.values["test_key"] = &Metric{labels: prometheus.Labels{serverLabel: "test_server"}, value: 4.0} - mm1.internal["new_key"] = &m1 - - m2 := metricData{metricType: Total} - m2.values = make(map[string]*Metric) - m2.values["test_key"] = &Metric{labels: prometheus.Labels{serverLabel: "test_server"}, value: 7.1} - mm2.internal["old_key"] = &m2 - - updateMetrics(log, mm1, mm2) - actual1 := mm1.internal["old_key"].values["test_key"].value - if actual1 != 7.1 { - t.Errorf("Value for metric key existing_test_key expected:%f, actual=%f", 7.1, actual1) - } - - actual2 := mm1.internal["new_key"].values["test_key"].value - if actual2 != 4.0 { - t.Errorf("Value for metric key non_existent_test_key expected:%f, actual=%f", 4.0, actual2) - } -} diff --git a/internal/name/name.go b/internal/name/name.go deleted file mode 100644 index b43ade3..0000000 --- a/internal/name/name.go +++ /dev/null @@ -1,45 +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 name contains code to manage the queue manager name -package name - -import ( - "os" - "regexp" -) - -// sanitizeName removes any invalid characters from a queue manager name -func sanitizeName(name string) string { - var re = regexp.MustCompile("[^a-zA-Z0-9._%/]") - return re.ReplaceAllString(name, "") -} - -// GetIntegrationServerName resolves the integration server naem to use. -// Resolved from either an environment variable, or the hostname. -func GetIntegrationServerName() (string, error) { - var name string - var err error - name, ok := os.LookupEnv("ACE_SERVER_NAME") - if !ok || name == "" { - name, err = os.Hostname() - if err != nil { - return "", err - } - name = sanitizeName(name) - } - return name, nil -} diff --git a/internal/name/name_internal_test.go b/internal/name/name_internal_test.go deleted file mode 100644 index d6c8a4f..0000000 --- a/internal/name/name_internal_test.go +++ /dev/null @@ -1,40 +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 name - -import ( - "testing" -) - -var sanitizeTests = []struct { - in string - out string -}{ - {"foo", "foo"}, - {"foo-0", "foo0"}, - {"foo-", "foo"}, - {"-foo", "foo"}, - {"foo_0", "foo_0"}, -} - -func TestSanitizeName(t *testing.T) { - for _, table := range sanitizeTests { - s := sanitizeName(table.in) - if s != table.out { - t.Errorf("sanitizeName(%v) - expected %v, got %v", table.in, table.out, s) - } - } -} diff --git a/internal/name/name_test.go b/internal/name/name_test.go deleted file mode 100644 index 79d9ca0..0000000 --- a/internal/name/name_test.go +++ /dev/null @@ -1,38 +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 name_test - -import ( - "os" - "testing" - - "github.com/ot4i/ace-docker/internal/name" -) - -func TestGetIntegrationServerNameFromEnv(t *testing.T) { - const data string = "bar" - err := os.Setenv("ACE_SERVER_NAME", data) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - n, err := name.GetIntegrationServerName() - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - if n != data { - t.Errorf("Expected name=%v, got name=%v", data, n) - } -} diff --git a/internal/trace/trace_handler.go b/internal/trace/trace_handler.go deleted file mode 100644 index 0bbe76f..0000000 --- a/internal/trace/trace_handler.go +++ /dev/null @@ -1,433 +0,0 @@ -/* -© Copyright IBM Corporation 2021 - -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 trace contains code to collect trace files -package trace - -import ( - "archive/zip" - "bytes" - "crypto/sha256" - "crypto/subtle" - "crypto/tls" - "crypto/x509" - "errors" - "io" - "net/http" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - - "github.com/ot4i/ace-docker/common/logger" -) - -var log logger.LoggerInterface - -var traceDir = "/home/aceuser/ace-server/config/common/log" -var credsDir = "/home/aceuser/initial-config/webusers" - -var tlsEnabled = os.Getenv("ACE_ADMIN_SERVER_SECURITY") == "true" -var caPath = os.Getenv("ACE_ADMIN_SERVER_CA") -var certFile = os.Getenv("ACE_ADMIN_SERVER_CERT") -var keyFile = os.Getenv("ACE_ADMIN_SERVER_KEY") - -var credentials []Credential - -type includeFile func(string) bool -type zipFunction func(ZipWriterInterface) error - -type ReqBody struct { - Type string -} - -type ZipWriterInterface interface { - Create(string) (io.Writer, error) - Close() error -} - -type FileReaderInterface interface { - ReadFile(string) ([]byte, error) -} - -type FileReader struct{} - -func (fr *FileReader) ReadFile(path string) ([]byte, error) { - return os.ReadFile(path) -} - -type Credential struct { - Username [32]byte - Password [32]byte -} - -type ServerInterface interface { - Start(address string, mux *http.ServeMux) - StartTLS(address string, mux *http.ServeMux, caCertPool *x509.CertPool, certPath string, keyPath string) -} - -type Server struct{} - -func (s *Server) Start(address string, mux *http.ServeMux) { - server := &http.Server{ - Addr: address, - Handler: mux, - } - go func() { - err := server.ListenAndServe() - if err != nil { - log.Println("Tracing: Trace API server terminated with error " + err.Error()) - } else { - log.Println("Tracing: Trace API server terminated") - } - }() -} - -func (s *Server) StartTLS(address string, mux *http.ServeMux, caCertPool *x509.CertPool, certFile string, keyFile string) { - server := &http.Server{ - Addr: address, - Handler: mux, - TLSConfig: &tls.Config{ - ClientCAs: caCertPool, - ClientAuth: tls.RequireAndVerifyClientCert, - }, - } - go func() { - err := server.ListenAndServeTLS(certFile, keyFile) - if err != nil { - log.Println("Tracing: Trace API server terminated with error " + err.Error()) - } else { - log.Println("Tracing: Trace API server terminated") - } - }() -} - -func StartServer(logger logger.LoggerInterface, portNumber int) error { - log = logger - - err := readBasicAuthCreds(credsDir, &FileReader{}) - if err != nil { - log.Println("Tracing: No web admin users have been found. The trace APIs will be run without credential verification.") - } - return startTraceServer(&Server{}, portNumber) -} - -func startTraceServer(server ServerInterface, portNumber int) error { - address := ":" + strconv.Itoa(portNumber) - - mux := http.NewServeMux() - serviceTraceHandler := http.HandlerFunc(serviceTraceRouterHandler) - userTraceHandler := http.HandlerFunc(userTraceRouterHandler) - mux.Handle("/collect-service-trace", basicAuthMiddlware(serviceTraceHandler)) - mux.Handle("/collect-user-trace", basicAuthMiddlware(userTraceHandler)) - - if tlsEnabled { - caCertPool, err := getCACertPool(caPath, &FileReader{}) - if err != nil { - return err - } - server.StartTLS(address, mux, caCertPool, certFile, keyFile) - } else { - server.Start(address, mux) - } - return nil -} - -func userTraceRouterHandler(res http.ResponseWriter, req *http.Request) { - traceRouteHandler(res, req, zipUserTrace) -} - -func serviceTraceRouterHandler(res http.ResponseWriter, req *http.Request) { - traceRouteHandler(res, req, zipServiceTrace) -} - -func traceRouteHandler(res http.ResponseWriter, req *http.Request, zipFunc zipFunction) { - if req.Method != http.MethodPost { - res.WriteHeader(http.StatusMethodNotAllowed) - return - } - - res.Header().Set("Transfer-Encoding", "chunked") - res.Header().Set("Content-Disposition", "attachment; filename=\"trace.zip\"") - - zipWriter := zip.NewWriter(res) - defer zipWriter.Close() - - err := zipFunc(zipWriter) - - if err != nil { - http.Error(res, err.Error(), http.StatusInternalServerError) - } -} - -func zipUserTrace(zipWriter ZipWriterInterface) error { - log.Println("Tracing: Collecting user trace") - err := zipDir(traceDir, zipWriter, func(fileName string) bool { - return strings.Contains(fileName, ".userTrace.") - }) - if err != nil { - log.Error("Tracing: Failed to collect user trace. Error: " + err.Error()) - return err - } - log.Println("Tracing: Finished collecting user trace") - return nil -} - -func zipServiceTrace(zipWriter ZipWriterInterface) error { - log.Println("Tracing: Collecting service trace") - err := zipDir(traceDir, zipWriter, func(fileName string) bool { - return strings.Contains(fileName, ".trace.") || strings.Contains(fileName, ".exceptionLog.") - }) - if err != nil { - log.Error("Tracing: Failed to collect service trace and exception logs. Error: " + err.Error()) - return err - } - - err = addEnvToZip(zipWriter, "env.txt") - if err != nil { - log.Error("Tracing: Failed to get integration server env. Error: " + err.Error()) - return err - } - - _ = runOSCommand(zipWriter, "ps eww.txt", "ps", "eww") - - log.Println("Tracing: Finished collecting service trace") - return nil -} - -func addEnvToZip(zipWriter ZipWriterInterface, filename string) error { - log.Println("Tracing: Adding environment variables to zip") - - var envVars []byte - for _, element := range os.Environ() { - element += "\n" - envVars = append(envVars, element...) - } - - err := addEntryToZip(zipWriter, filename, envVars) - if err != nil { - log.Error("Tracing: Unable to add env vars to zip. Error: " + err.Error()) - return err - } - - return nil -} - -func runOSCommand(zipWriter ZipWriterInterface, filename string, command string, arg ...string) error { - log.Println("Tracing: Collecting output of command: " + command) - cmd := exec.Command(command, arg...) - var out bytes.Buffer - cmd.Stdout = &out - - err := cmd.Run() - if err != nil { - log.Error("Tracing: Unable to run command " + command + ": " + err.Error()) - return err - } - - outBytes := out.Bytes() - - err = addEntryToZip(zipWriter, filename, outBytes) - if err != nil { - log.Error("Tracing: Unable to add output of command: " + command + " to zip. Error: " + err.Error()) - return err - } - - return nil -} - -func addEntryToZip(zipWriter ZipWriterInterface, filename string, fileContents []byte) error { - zipEntry, err := zipWriter.Create(filename) - if err != nil { - log.Error("Tracing: Failed to write header for " + filename) - return err - } - - if _, err := zipEntry.Write(fileContents); err != nil { - log.Error("Tracing: Failed to add " + filename + " to archive") - return err - } - - return nil -} - -func zipDir(traceDir string, zipWriter ZipWriterInterface, testFunc includeFile) error { - log.Println("Tracing: Creating archive of " + traceDir) - stat, err := os.Stat(traceDir) - if err != nil { - log.Error("Tracing: Directory " + traceDir + " does not exist") - return err - } - - if !stat.Mode().IsDir() { - log.Error("Tracing: " + traceDir + " is not a directory") - return errors.New(traceDir + " is not a directory") - } - - return filepath.Walk(traceDir, func(path string, fileInfo os.FileInfo, err error) error { - if fileInfo.Mode().IsDir() { - return nil - } - if testFunc(fileInfo.Name()) { - return zipFile(path, zipWriter) - } - return nil - }) -} - -func zipFile(path string, zipWriter ZipWriterInterface) error { - file, err := os.Open(path) - if err != nil { - return err - } - defer file.Close() - - if fileInfo, err := file.Stat(); err == nil { - log.Println("Tracing: Adding " + fileInfo.Name() + " to archive") - - zipEntry, err := zipWriter.Create(fileInfo.Name()) - if err != nil { - log.Error("Tracing: Failed to write header for " + fileInfo.Name()) - return err - } - - if _, err := io.Copy(zipEntry, file); err != nil { - log.Error("Tracing: Failed to add " + fileInfo.Name() + " to archive") - return err - } - } - - return nil -} - -func readBasicAuthCreds(credsDir string, fileReader FileReaderInterface) error { - stat, err := os.Stat(credsDir) - if err != nil { - return err - } - - if !stat.Mode().IsDir() { - return errors.New(credsDir + " is not a directory") - } - - return filepath.Walk(credsDir, func(path string, fileInfo os.FileInfo, err error) error { - if fileInfo.Mode().IsDir() { - return nil - } - - fileName := fileInfo.Name() - - if fileName == "admin-users.txt" || fileName == "operator-users.txt" { - file, err := fileReader.ReadFile(path) - if err != nil { - return err - } - - fileString := strings.TrimSpace(string(file)) - - lines := strings.Split(fileString, "\n") - for _, line := range lines { - if line != "" && !strings.HasPrefix(line, "#") { - fields := strings.Fields(line) - if len(fields) != 2 { - return errors.New("Tracing: Unable to parse " + fileName) - } - // using hashes means that the length of the byte array to compare is always the same - credentials = append(credentials, Credential{ - Username: sha256.Sum256([]byte(fields[0])), - Password: sha256.Sum256([]byte(fields[1])), - }) - } - } - - log.Println("Tracing: Added credentials from " + fileName + " to trace router") - } - return nil - }) -} - -func basicAuthMiddlware(next http.Handler) http.Handler { - return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - if len(credentials) > 0 { - username, password, ok := req.BasicAuth() - - if ok { - usernameHash := sha256.Sum256([]byte(username)) - passwordHash := sha256.Sum256([]byte(password)) - - for _, credential := range credentials { - // subtle.ConstantTimeCompare takes the same amount of time to run, regardless of whether the slices match or not - usernameMatch := subtle.ConstantTimeCompare(usernameHash[:], credential.Username[:]) - passwordMatch := subtle.ConstantTimeCompare(passwordHash[:], credential.Password[:]) - if usernameMatch+passwordMatch == 2 { - next.ServeHTTP(res, req) - return - } - } - } - - http.Error(res, "Unauthorized", http.StatusUnauthorized) - } else { - next.ServeHTTP(res, req) - } - }) -} - -func getCACertPool(caPath string, fileReader FileReaderInterface) (*x509.CertPool, error) { - caCertPool := x509.NewCertPool() - - stat, err := os.Stat(caPath) - - if err != nil { - log.Printf("Tracing: %s does not exist", caPath) - return nil, err - } - - if stat.IsDir() { - // path is a directory load all certs - log.Printf("Tracing: Using CA Certificate folder %s", caPath) - filepath.Walk(caPath, func(cert string, info os.FileInfo, err error) error { - if strings.HasSuffix(cert, "crt.pem") { - log.Printf("Tracing: Adding Certificate %s to CA pool", cert) - binaryCert, err := fileReader.ReadFile(cert) - if err != nil { - log.Printf("Tracing: Error reading CA Certificate %s", err.Error()) - return nil - } - ok := caCertPool.AppendCertsFromPEM(binaryCert) - if !ok { - log.Printf("Tracing: Failed to parse Certificate %s", cert) - } - } - return nil - }) - } else { - log.Printf("Tracing: Using CA Certificate file %s", caPath) - caCert, err := fileReader.ReadFile(caPath) - if err != nil { - log.Errorf("Tracing: Error reading CA Certificate %s", err) - return nil, err - } - ok := caCertPool.AppendCertsFromPEM(caCert) - if !ok { - log.Error("Tracing: Failed to parse root CA Certificate") - return nil, errors.New("failed to parse root CA Certificate") - } - } - - return caCertPool, nil -} diff --git a/internal/trace/trace_handler_test.go b/internal/trace/trace_handler_test.go deleted file mode 100644 index d63272c..0000000 --- a/internal/trace/trace_handler_test.go +++ /dev/null @@ -1,859 +0,0 @@ -/* -© Copyright IBM Corporation 2021 - -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 trace - -import ( - "archive/zip" - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/pem" - "errors" - "io" - "math/big" - "net/http" - "net/http/httptest" - "os" - "strings" - "testing" - - "github.com/ot4i/ace-docker/common/logger" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var testLogger, _ = logger.NewLogger(os.Stdout, true, true, "test") - -func TestStartTraceServer(t *testing.T) { - log = testLogger - - t.Run("Error creating cert pool", func(t *testing.T) { - tlsEnabled = true - caPath = "capath" - err := startTraceServer(&Server{}, 7891) - require.Error(t, err) - }) - - t.Run("TLS server when ACE_ADMIN_SERVER_SECURITY is true", func(t *testing.T) { - defer func() { - os.RemoveAll("capath") - }() - tlsEnabled = true - caPath = "capath" - certFile = "certfile" - keyFile = "keyfile" - - err := os.MkdirAll("capath", 0755) - require.NoError(t, err) - - startTraceServer(&TestServer{ - t: t, - expectedAddress: ":7891", - expectedCertFile: certFile, - expectedKeyFile: keyFile, - }, 7891) - }) - - t.Run("HTTP server when ACE_ADMIN_SERVER_SECURITY is not true", func(t *testing.T) { - tlsEnabled = false - startTraceServer(&TestServer{ - t: t, - expectedAddress: ":7891", - }, 7891) - }) -} - -func TestUserTraceRouterHandler(t *testing.T) { - log = testLogger - - handler := http.HandlerFunc(userTraceRouterHandler) - url := "/collect-user-trace" - - t.Run("Sends an error if not a POST request", func(t *testing.T) { - request, _ := http.NewRequest("GET", url, nil) - response := httptest.NewRecorder() - - handler.ServeHTTP(response, request) - assert.Equal(t, http.StatusMethodNotAllowed, response.Code) - }) - - t.Run("Sends an error if the trace can't be collected", func(t *testing.T) { - traceDir = "test/trace" - request, _ := http.NewRequest("POST", url, nil) - response := httptest.NewRecorder() - - handler.ServeHTTP(response, request) - assert.Equal(t, http.StatusInternalServerError, response.Code) - }) - - t.Run("Streams a zip file for user trace", func(t *testing.T) { - defer restoreUserTrace() - setUpUserTrace(t) - - request, _ := http.NewRequest("POST", url, nil) - response := httptest.NewRecorder() - - handler.ServeHTTP(response, request) - require.Equal(t, http.StatusOK, response.Code) - - body, _ := io.ReadAll(response.Body) - zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) - require.NoError(t, err) - - files := checkZip(t, zipReader) - assert.Len(t, files, 1) - assert.Contains(t, files, "test.userTrace.txt") - }) -} - -func TestServiceTraceRouteHandler(t *testing.T) { - log = testLogger - - handler := http.HandlerFunc(serviceTraceRouterHandler) - url := "/collect-service-trace" - - t.Run("Sends an error if not a POST request", func(t *testing.T) { - request, _ := http.NewRequest("GET", url, nil) - response := httptest.NewRecorder() - - handler.ServeHTTP(response, request) - assert.Equal(t, http.StatusMethodNotAllowed, response.Code) - }) - - t.Run("Sends an error if the trace can't be collected", func(t *testing.T) { - traceDir = "test/trace" - - request, _ := http.NewRequest("POST", url, nil) - response := httptest.NewRecorder() - - handler.ServeHTTP(response, request) - assert.Equal(t, http.StatusInternalServerError, response.Code) - }) - - t.Run("Streams a zip file for service trace", func(t *testing.T) { - defer restoreServiceTrace() - setUpServiceTrace(t) - - request, _ := http.NewRequest("POST", url, nil) - response := httptest.NewRecorder() - - handler.ServeHTTP(response, request) - require.Equal(t, http.StatusOK, response.Code) - - body, _ := io.ReadAll(response.Body) - zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) - require.NoError(t, err) - - files := checkZip(t, zipReader) - assert.Contains(t, files, "test.trace.txt") - assert.Contains(t, files, "test.exceptionLog.txt") - assert.Contains(t, files, "env.txt") - }) -} - -func TestZipUserTrace(t *testing.T) { - log = testLogger - - defer restoreUserTrace() - setUpUserTrace(t) - - t.Run("Builds a zip with user trace", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - - err := zipUserTrace(zipWriter) - require.NoError(t, err) - - zipWriter.Close() - - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - filesInZip := checkZip(t, zipReader) - assert.Len(t, filesInZip, 1) - assert.Contains(t, filesInZip, "test.userTrace.txt") - }) - - t.Run("Returns an error when it can't collect user trace", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := FailOnFileNameZipWriter{ - failOnFileName: "test.userTrace.txt", - zipWriter: zip.NewWriter(&buffer), - } - err := zipUserTrace(zipWriter) - assert.EqualError(t, err, "Unable to write test.userTrace.txt") - }) -} - -func TestZipServiceTrace(t *testing.T) { - log = testLogger - - defer restoreServiceTrace() - setUpServiceTrace(t) - - t.Run("Builds a zip with service trace, exception logs, env, and ps eww output", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - - err := zipServiceTrace(zipWriter) - require.NoError(t, err) - - zipWriter.Close() - - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - files := checkZip(t, zipReader) - assert.Contains(t, files, "test.trace.txt") - assert.Contains(t, files, "test.exceptionLog.txt") - assert.Contains(t, files, "env.txt") - }) - - t.Run("Failure test cases", func(t *testing.T) { - failureTestCases := []string{ - "test.trace.txt", - "env.txt", - } - - for _, fileName := range failureTestCases { - t.Run(fileName, func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := FailOnFileNameZipWriter{ - failOnFileName: fileName, - zipWriter: zip.NewWriter(&buffer), - } - err := zipServiceTrace(zipWriter) - assert.EqualError(t, err, "Unable to write "+fileName) - }) - } - }) -} - -func TestRunOSCommand(t *testing.T) { - log = testLogger - - t.Run("Returns an error if it can't run the command", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - err := runOSCommand(zipWriter, "file.txt", "asdasdd") - assert.Error(t, err) - zipWriter.Close() - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - assert.Len(t, checkZip(t, zipReader), 0) - }) - - t.Run("Returns an error if there is an error when creating a file in the zip", func(t *testing.T) { - zipWriter := CreateFailureZipWriter{} - err := runOSCommand(zipWriter, "file.txt", "echo", "hello world") - assert.Error(t, err) - }) - - t.Run("Returns an error if the command output can't be written to the zip", func(t *testing.T) { - zipWriter := WriteFailureZipWriter{} - err := runOSCommand(zipWriter, "file.txt", "echo", "hello world") - assert.Error(t, err) - }) - - t.Run("Adds the command output to the zip if successful", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - - err := runOSCommand(zipWriter, "file.txt", "echo", "hello world") - assert.NoError(t, err) - - zipWriter.Close() - - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - - files := checkZip(t, zipReader) - assert.Len(t, files, 1) - assert.Contains(t, files, "file.txt") - }) -} - -func TestZipDir(t *testing.T) { - log = testLogger - - // Create directories and files which will be archived - err := os.MkdirAll("subdir/parent/child", 0755) - require.NoError(t, err) - defer os.RemoveAll("subdir") - - files := []string{"subdir/parent/file1.txt", "subdir/parent/child/file2.txt", "subdir/parent/file3.txt"} - - for _, fileName := range files { - file, err := os.Create(fileName) - require.NoError(t, err) - _, err = file.WriteString("This is a test") - require.NoError(t, err) - } - - t.Run("Calls zipFile for each file and only adds files which pass the test function ", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - err := zipDir("subdir", zipWriter, func(fileName string) bool { - return !strings.Contains(fileName, "1") - }) - zipWriter.Close() - require.NoError(t, err) - - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - - files := checkZip(t, zipReader) - assert.Len(t, files, 2) - assert.Contains(t, files, "file2.txt") - assert.Contains(t, files, "file3.txt") - }) - - t.Run("Returns an error if the directory does not exist", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - err := zipDir("does-not-exist", zipWriter, func(string) bool { return true }) - zipWriter.Close() - assert.Error(t, err) - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - assert.Len(t, checkZip(t, zipReader), 0) - }) - - t.Run("Returns an error if passed a file that is not a directory", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - err := zipDir("subdir/parent/file1.txt", zipWriter, func(string) bool { return true }) - zipWriter.Close() - assert.EqualError(t, err, "subdir/parent/file1.txt is not a directory") - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - assert.Len(t, checkZip(t, zipReader), 0) - }) - - t.Run("Creates an empty zip if there are no files which pass the test function", func(t *testing.T) { - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - err := zipDir("subdir", zipWriter, func(fileName string) bool { - return !strings.Contains(fileName, "file") - }) - zipWriter.Close() - assert.NoError(t, err) - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - assert.Len(t, checkZip(t, zipReader), 0) - }) -} - -func TestZipFile(t *testing.T) { - log = testLogger - fileNameToZip := "fileToZip.txt" - - testSetup := func() { - // Create file which will be archived - fileToZip, err := os.Create(fileNameToZip) - require.NoError(t, err) - _, err = fileToZip.WriteString("This is a test") - require.NoError(t, err) - } - - t.Run("Returns an error when the file cannot be opened", func(t *testing.T) { - err := zipFile("badPath", nil) - assert.Error(t, err) - }) - - t.Run("Returns an error when it fails to create the file in the zip", func(t *testing.T) { - defer os.Remove(fileNameToZip) - testSetup() - - zipWriter := CreateFailureZipWriter{} - - err := zipFile(fileNameToZip, zipWriter) - assert.EqualError(t, err, "Failed to create") - }) - - t.Run("Returns an error when it fails to add the file to the zip", func(t *testing.T) { - defer os.Remove(fileNameToZip) - testSetup() - - zipWriter := WriteFailureZipWriter{} - - err := zipFile(fileNameToZip, zipWriter) - assert.EqualError(t, err, "Failed to write") - }) - - t.Run("Returns with no error when the header and file are successfully written to the zip", func(t *testing.T) { - defer os.Remove(fileNameToZip) - testSetup() - - var buffer bytes.Buffer - zipWriter := zip.NewWriter(&buffer) - err := zipFile("fileToZip.txt", zipWriter) - assert.NoError(t, err) - zipWriter.Close() - - zipReader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(len(buffer.Bytes()))) - require.NoError(t, err) - files := checkZip(t, zipReader) - assert.Equal(t, 1, len(files)) - assert.Contains(t, files, "fileToZip.txt") - }) -} - -func TestReadBasicAuthCreds(t *testing.T) { - credsDir := "credentials" - - setupDir := func() { - err := os.MkdirAll(credsDir, 0755) - require.NoError(t, err) - } - - cleanUpDir := func() { - err := os.RemoveAll(credsDir) - require.NoError(t, err) - } - - t.Run("Returns an error if the directory does not exist", func(t *testing.T) { - err := readBasicAuthCreds("does/not/exist", &FileReader{}) - require.Error(t, err) - }) - - t.Run("Returns an error if the input parameter is not a directory", func(t *testing.T) { - setupDir() - defer cleanUpDir() - - _, err := os.Create(credsDir + "/emptyFile.txt") - require.NoError(t, err) - - err = readBasicAuthCreds(credsDir+"/emptyFile.txt", &FileReader{}) - require.EqualError(t, err, "credentials/emptyFile.txt is not a directory") - }) - - t.Run("Returns an error if there is an error reading a file", func(t *testing.T) { - setupDir() - defer cleanUpDir() - - _, err := os.Create(credsDir + "/admin-users.txt") - require.NoError(t, err) - - err = readBasicAuthCreds(credsDir, &ErrorFileReader{}) - require.EqualError(t, err, "Unable to read file") - }) - - t.Run("Returns an error if it fails to parse the credentials file", func(t *testing.T) { - setupDir() - defer cleanUpDir() - - file, err := os.Create(credsDir + "/admin-users.txt") - require.NoError(t, err) - _, err = file.WriteString("This is a test") - require.NoError(t, err) - - err = readBasicAuthCreds(credsDir, &FileReader{}) - require.EqualError(t, err, "Tracing: Unable to parse admin-users.txt") - }) - - t.Run("Returns nil if the credentials have all been read and parsed - single line files", func(t *testing.T) { - log = testLogger - - setupDir() - defer cleanUpDir() - - credentials = []Credential{} - - file, err := os.Create(credsDir + "/admin-users.txt") - require.NoError(t, err) - _, err = file.WriteString("user1 pass1") - require.NoError(t, err) - - file, err = os.Create(credsDir + "/operator-users.txt") - require.NoError(t, err) - _, err = file.WriteString("user2 pass2") - require.NoError(t, err) - - err = readBasicAuthCreds(credsDir, &FileReader{}) - require.NoError(t, err) - - require.Len(t, credentials, 2) - assert.Equal(t, sha256.Sum256([]byte("user1")), credentials[0].Username) - assert.Equal(t, sha256.Sum256([]byte("pass1")), credentials[0].Password) - assert.Equal(t, sha256.Sum256([]byte("user2")), credentials[1].Username) - assert.Equal(t, sha256.Sum256([]byte("pass2")), credentials[1].Password) - }) - - t.Run("Returns nil if the credentials have all been read and parsed - multi line files, trailing spaces and comments", func(t *testing.T) { - log = testLogger - - setupDir() - defer cleanUpDir() - - credentials = []Credential{} - - file, err := os.Create(credsDir + "/admin-users.txt") - require.NoError(t, err) - _, err = file.WriteString("user1 pass1\nuser2 pass2\n") - require.NoError(t, err) - - file, err = os.Create(credsDir + "/operator-users.txt") - require.NoError(t, err) - _, err = file.WriteString("# this shouldn't cause an error \n# nor should this\nuser3 pass3 \n ") - require.NoError(t, err) - - err = readBasicAuthCreds(credsDir, &FileReader{}) - require.NoError(t, err) - - require.Len(t, credentials, 3) - assert.Equal(t, sha256.Sum256([]byte("user1")), credentials[0].Username) - assert.Equal(t, sha256.Sum256([]byte("pass1")), credentials[0].Password) - assert.Equal(t, sha256.Sum256([]byte("user2")), credentials[1].Username) - assert.Equal(t, sha256.Sum256([]byte("pass2")), credentials[1].Password) - assert.Equal(t, sha256.Sum256([]byte("user3")), credentials[2].Username) - assert.Equal(t, sha256.Sum256([]byte("pass3")), credentials[2].Password) - }) - - t.Run("does not add viewer, auditor or editor users", func(t *testing.T) { - log = testLogger - - setupDir() - defer cleanUpDir() - - credentials = []Credential{} - - file, err := os.Create(credsDir + "/auditor-users.txt") - require.NoError(t, err) - _, err = file.WriteString("user1 pass1") - require.NoError(t, err) - - file, err = os.Create(credsDir + "/operator-users.txt") - require.NoError(t, err) - _, err = file.WriteString("user2 pass2") - require.NoError(t, err) - - file, err = os.Create(credsDir + "/editor-users.txt") - require.NoError(t, err) - _, err = file.WriteString("user3 pass3") - require.NoError(t, err) - - file, err = os.Create(credsDir + "/viewer-users.txt") - require.NoError(t, err) - _, err = file.WriteString("user4 pass4") - require.NoError(t, err) - - err = readBasicAuthCreds(credsDir, &FileReader{}) - require.NoError(t, err) - - require.Len(t, credentials, 1) - assert.Equal(t, sha256.Sum256([]byte("user2")), credentials[0].Username) - assert.Equal(t, sha256.Sum256([]byte("pass2")), credentials[0].Password) - }) -} - -func TestBasicAuthMiddlware(t *testing.T) { - setCredentials := func() { - credentials = []Credential{{ - Username: sha256.Sum256([]byte("user1")), - Password: sha256.Sum256([]byte("pass1")), - }, { - Username: sha256.Sum256([]byte("user2")), - Password: sha256.Sum256([]byte("pass2")), - }, - } - } - - t.Run("No credentials defined", func(t *testing.T) { - credentials = []Credential{} - response := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://testing", nil) - - var called bool - - handlerToTest := basicAuthMiddlware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - called = true - })) - handlerToTest.ServeHTTP(response, req) - - assert.True(t, called) - }) - - t.Run("No basic auth credentials in request", func(t *testing.T) { - setCredentials() - response := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://testing", nil) - - handlerToTest := basicAuthMiddlware(nil) - handlerToTest.ServeHTTP(response, req) - - assert.Equal(t, 401, response.Result().StatusCode) - }) - - t.Run("Invalid basic auth credentials", func(t *testing.T) { - setCredentials() - response := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://testing", nil) - req.SetBasicAuth("invaliduser", "invalidpass") - - handlerToTest := basicAuthMiddlware(nil) - handlerToTest.ServeHTTP(response, req) - - assert.Equal(t, 401, response.Result().StatusCode) - }) - - t.Run("Matching username", func(t *testing.T) { - setCredentials() - response := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://testing", nil) - req.SetBasicAuth("user1", "invalidpass") - - handlerToTest := basicAuthMiddlware(nil) - handlerToTest.ServeHTTP(response, req) - - assert.Equal(t, 401, response.Result().StatusCode) - }) - - t.Run("Matching password", func(t *testing.T) { - setCredentials() - response := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://testing", nil) - req.SetBasicAuth("invaliduser", "pass1") - - handlerToTest := basicAuthMiddlware(nil) - handlerToTest.ServeHTTP(response, req) - - assert.Equal(t, 401, response.Result().StatusCode) - }) - - t.Run("Matches first credentials", func(t *testing.T) { - setCredentials() - response := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://testing", nil) - req.SetBasicAuth("user1", "pass1") - - var called bool - - handlerToTest := basicAuthMiddlware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - called = true - })) - handlerToTest.ServeHTTP(response, req) - - assert.True(t, called) - }) - - t.Run("Matches second credentials", func(t *testing.T) { - setCredentials() - response := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://testing", nil) - req.SetBasicAuth("user2", "pass2") - - var called bool - - handlerToTest := basicAuthMiddlware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - called = true - })) - handlerToTest.ServeHTTP(response, req) - - assert.True(t, called) - }) -} -func TestGetCACertPool(t *testing.T) { - log = testLogger - - defer func() { - os.RemoveAll("cert-dir") - }() - - err := os.MkdirAll("cert-dir", 0755) - require.NoError(t, err) - - _, err = os.Create("cert-dir/empty-crt.pem") // empty file for parsing errors - require.NoError(t, err) - - file, err := os.Create("cert-dir/valid-crt.pem") - require.NoError(t, err) - - cert := createValidCA() - _, err = file.Write(cert) - require.NoError(t, err) - - t.Run("Returns an error if caPath is not a file", func(t *testing.T) { - _, err := getCACertPool("does-not-exist", &FileReader{}) - require.Error(t, err) - }) - - t.Run("Returns an error if the ca file cannot be read", func(t *testing.T) { - _, err := getCACertPool("cert-dir/valid-crt.pem", &ErrorFileReader{}) - require.Error(t, err) - }) - - t.Run("Returns an error if the ca file cannot be parsed", func(t *testing.T) { - _, err := getCACertPool("cert-dir/empty-crt.pem", &FileReader{}) - require.Error(t, err) - }) - - t.Run("Returns a caPool with one cert if the ca file is added successfully", func(t *testing.T) { - caCertPool, err := getCACertPool("cert-dir/valid-crt.pem", &FileReader{}) - require.NoError(t, err) - assert.Len(t, caCertPool.Subjects(), 1) - }) - - t.Run("Does not return an error when a file reading error occurs in a directory", func(t *testing.T) { - caCertPool, err := getCACertPool("cert-dir", &ErrorFileReader{}) - require.NoError(t, err) - assert.Len(t, caCertPool.Subjects(), 0) // both files won't be read - }) - - t.Run("Does not return an error when files can't be parsed", func(t *testing.T) { - caCertPool, err := getCACertPool("cert-dir", &FileReader{}) - require.NoError(t, err) - assert.Len(t, caCertPool.Subjects(), 1) // the empty file will fail to parse - }) -} - -func checkZip(t *testing.T, zipReader *zip.Reader) []string { - var files []string - for _, f := range zipReader.File { - files = append(files, f.FileHeader.Name) - } - return files -} - -func setUpUserTrace(t *testing.T) { - traceDir = "test/trace" - - err := os.MkdirAll(traceDir, 0755) - require.NoError(t, err) - - files := []string{ - traceDir + "/test.userTrace.txt", - traceDir + "/no-match.txt", - } - - for _, fileName := range files { - file, err := os.Create(fileName) - require.NoError(t, err) - _, err = file.WriteString("This is a test") - require.NoError(t, err) - } -} - -func restoreUserTrace() { - os.RemoveAll("test") -} - -func setUpServiceTrace(t *testing.T) { - traceDir = "test/trace" - - err := os.MkdirAll(traceDir, 0755) - require.NoError(t, err) - - files := []string{ - "/test.trace.txt", - "/test.exceptionLog.txt", - "/no-match.txt", - } - - for _, fileName := range files { - file, err := os.Create(traceDir + fileName) - require.NoError(t, err) - _, err = file.WriteString("This is a test") - require.NoError(t, err) - } -} - -func restoreServiceTrace() { - os.RemoveAll("test") -} - -func createValidCA() []byte { - privKey, _ := rsa.GenerateKey(rand.Reader, 4096) - ca := &x509.Certificate{ - SerialNumber: &big.Int{}, - IsCA: true, - } - - caBytes, _ := x509.CreateCertificate(rand.Reader, ca, ca, &privKey.PublicKey, privKey) - - caPEM := new(bytes.Buffer) - _ = pem.Encode(caPEM, &pem.Block{ - Type: "CERTIFICATE", - Bytes: caBytes, - }) - - return caPEM.Bytes() -} - -type CreateFailureZipWriter struct{} - -func (zw CreateFailureZipWriter) Create(filename string) (io.Writer, error) { - return nil, errors.New("Failed to create") -} -func (zw CreateFailureZipWriter) Close() error { - return nil -} - -type WriteFailureZipWriter struct{} - -func (zw WriteFailureZipWriter) Create(filename string) (io.Writer, error) { - zipEntry := WriteFailureZipEntry{} - return zipEntry, nil -} -func (zw WriteFailureZipWriter) Close() error { - return nil -} - -type WriteFailureZipEntry struct{} - -func (ze WriteFailureZipEntry) Write(p []byte) (n int, err error) { - return 0, errors.New("Failed to write") -} - -type FailOnFileNameZipWriter struct { - failOnFileName string - zipWriter ZipWriterInterface -} - -func (zw FailOnFileNameZipWriter) Create(filename string) (io.Writer, error) { - if filename == zw.failOnFileName { - return nil, errors.New("Unable to write " + zw.failOnFileName) - } - return zw.zipWriter.Create(filename) -} -func (zw FailOnFileNameZipWriter) Close() error { - return zw.zipWriter.Close() -} - -type ErrorFileReader struct{} - -func (fr *ErrorFileReader) ReadFile(string) ([]byte, error) { - return nil, errors.New("Unable to read file") -} - -type TestServer struct { - t *testing.T - expectedAddress string - expectedCertFile string - expectedKeyFile string -} - -func (s *TestServer) Start(address string, mux *http.ServeMux) { - assert.Equal(s.t, s.expectedAddress, address) -} - -func (s *TestServer) StartTLS(address string, mux *http.ServeMux, caCertPool *x509.CertPool, certFile string, keyFile string) { - assert.Equal(s.t, s.expectedAddress, address) - assert.Equal(s.t, s.expectedCertFile, certFile) - assert.Equal(s.t, s.expectedKeyFile, keyFile) -} diff --git a/internal/webadmin/testdata/initial-config/webusers/admin-users.txt b/internal/webadmin/testdata/initial-config/webusers/admin-users.txt deleted file mode 100644 index bc07495..0000000 --- a/internal/webadmin/testdata/initial-config/webusers/admin-users.txt +++ /dev/null @@ -1 +0,0 @@ -ibm-ace-dashboard-admin 1758F07A-8BEF-448C-B020-C25946AF3E94 \ No newline at end of file diff --git a/internal/webadmin/testdata/initial-config/webusers/audit-users.txt b/internal/webadmin/testdata/initial-config/webusers/audit-users.txt deleted file mode 100644 index 2926f91..0000000 --- a/internal/webadmin/testdata/initial-config/webusers/audit-users.txt +++ /dev/null @@ -1 +0,0 @@ -ibm-ace-dashboard-audit 929064C2-0017-4B34-A883-219A4D1AC944 \ No newline at end of file diff --git a/internal/webadmin/testdata/initial-config/webusers/editor-users.txt b/internal/webadmin/testdata/initial-config/webusers/editor-users.txt deleted file mode 100644 index d4073aa..0000000 --- a/internal/webadmin/testdata/initial-config/webusers/editor-users.txt +++ /dev/null @@ -1 +0,0 @@ -ibm-ace-dashboard-editor 28DBC34B-C0FD-44BF-8100-99DB686B6DB2 \ No newline at end of file diff --git a/internal/webadmin/testdata/initial-config/webusers/operator-users.txt b/internal/webadmin/testdata/initial-config/webusers/operator-users.txt deleted file mode 100644 index 20c5008..0000000 --- a/internal/webadmin/testdata/initial-config/webusers/operator-users.txt +++ /dev/null @@ -1 +0,0 @@ -ibm-ace-dashboard-operator 68FE7808-8EC2-4395-97D0-A776D2A61912 \ No newline at end of file diff --git a/internal/webadmin/testdata/initial-config/webusers/server.conf.yaml b/internal/webadmin/testdata/initial-config/webusers/server.conf.yaml deleted file mode 100644 index b2b8775..0000000 --- a/internal/webadmin/testdata/initial-config/webusers/server.conf.yaml +++ /dev/null @@ -1,4 +0,0 @@ -RestAdminListener: - authorizationEnabled: true - authorizationMode: file - basicAuth: true \ No newline at end of file diff --git a/internal/webadmin/testdata/initial-config/webusers/viewer-users.txt b/internal/webadmin/testdata/initial-config/webusers/viewer-users.txt deleted file mode 100644 index e93014e..0000000 --- a/internal/webadmin/testdata/initial-config/webusers/viewer-users.txt +++ /dev/null @@ -1 +0,0 @@ -ibm-ace-dashboard-viewer EF086556-74B8-4FB0-ACF8-CC59E1F3DB5F \ No newline at end of file diff --git a/internal/webadmin/webadmin.go b/internal/webadmin/webadmin.go deleted file mode 100644 index 0d810db..0000000 --- a/internal/webadmin/webadmin.go +++ /dev/null @@ -1,222 +0,0 @@ -package webadmin - -import ( - "crypto/rand" - "crypto/sha512" - b64 "encoding/base64" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/ot4i/ace-docker/common/logger" - "golang.org/x/crypto/pbkdf2" - "gopkg.in/yaml.v2" -) - -var ( - readFile = os.ReadFile - mkdirAll = os.MkdirAll - writeFile = os.WriteFile - readDir = os.ReadDir - processWebAdminUsers = processWebAdminUsersLocal - applyFileAuthOverrides = applyFileAuthOverridesLocal - outputFiles = outputFilesLocal - unmarshal = yaml.Unmarshal - marshal = yaml.Marshal - readWebUsersTxt = readWebUsersTxtLocal - version string = "12.0.0.0" - homedir string = "/home/aceuser/" - webusersDir string = "/home/aceuser/initial-config/webusers/" -) - -func ConfigureWebAdminUsers(log logger.LoggerInterface) error { - serverConfContent, err := readServerConfFile() - if err != nil { - log.Errorf("Error reading server.conf.yaml: %v", err) - return err - } - - webAdminUserInfo, err := processWebAdminUsers(log, webusersDir) - if err != nil { - log.Errorf("Error processing WebAdmin users: %v", err) - return err - } - serverconfYaml, err := applyFileAuthOverrides(log, webAdminUserInfo, serverConfContent) - if err != nil { - log.Errorf("Error applying file auth overrides: %v", err) - return err - } - err = writeServerConfFile(serverconfYaml) - if err != nil { - log.Errorf("Error writing server.conf.yaml: %v", err) - return err - } - - for webAdminUserName, webAdminPass := range webAdminUserInfo { - m := map[string]string{ - "password": keyGen(webAdminPass), - "role": webAdminUserName, - "version": version, - } - err := outputFiles(log, m) - if err != nil { - log.Errorf("Error writing WebAdmin files: %v", err) - return err - } - } - return nil -} - -func processWebAdminUsersLocal(log logger.LoggerInterface, dir string) (map[string]string, error) { - userInfo := map[string]string{} - - fileList, err := readDir(dir) - if err != nil { - log.Errorf("Error reading directory: %v", err) - return nil, err - } - - for _, file := range fileList { - if filepath.Ext(file.Name()) == ".txt" { - username, password, err := readWebUsersTxt(log, dir+file.Name()) - if err != nil { - log.Errorf("Error reading WebAdmin users.txt file: %v", err) - return nil, err - } - userInfo[username] = password - } - } - return userInfo, nil -} - -func applyFileAuthOverridesLocal(log logger.LoggerInterface, webAdminUserInfo map[string]string, serverconfContent []byte) ([]byte, error) { - serverconfMap := make(map[interface{}]interface{}) - err := unmarshal([]byte(serverconfContent), &serverconfMap) - if err != nil { - log.Errorf("Error unmarshalling server.conf.yaml content: %v", err) - return nil, err - } - - permissionsMap := map[string]string{ - "admin": "read+:write+:execute+", - "operator": "read+:write-:execute+", - "editor": "read+:write+:execute-", - "audit": "read+:write-:execute-", - "viewer": "read+:write-:execute-", - } - - if serverconfMap["Security"] == nil { - serverconfMap["Security"] = map[interface{}]interface{}{} - security := serverconfMap["Security"].(map[interface{}]interface{}) - if security["DataPermissions"] == nil && security["Permissions"] == nil { - security["DataPermissions"] = map[interface{}]interface{}{} - dataPermissions := security["DataPermissions"].(map[interface{}]interface{}) - security["Permissions"] = map[interface{}]interface{}{} - permissions := security["Permissions"].(map[interface{}]interface{}) - if _, ok := webAdminUserInfo["ibm-ace-dashboard-admin"]; ok { - dataPermissions["admin"] = permissionsMap["admin"] - permissions["admin"] = permissionsMap["admin"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-operator"]; ok { - permissions["operator"] = permissionsMap["operator"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-editor"]; ok { - permissions["editor"] = permissionsMap["editor"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-audit"]; ok { - permissions["audit"] = permissionsMap["audit"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-viewer"]; ok { - permissions["viewer"] = permissionsMap["viewer"] - } - } - } else { - security := serverconfMap["Security"].(map[interface{}]interface{}) - if security["DataPermissions"] == nil && security["Permissions"] == nil { - security["DataPermissions"] = map[interface{}]interface{}{} - dataPermissions := security["DataPermissions"].(map[interface{}]interface{}) - security["Permissions"] = map[interface{}]interface{}{} - permissions := security["Permissions"].(map[interface{}]interface{}) - if _, ok := webAdminUserInfo["ibm-ace-dashboard-admin"]; ok { - dataPermissions["admin"] = permissionsMap["admin"] - permissions["admin"] = permissionsMap["admin"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-operator"]; ok { - permissions["operator"] = permissionsMap["operator"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-editor"]; ok { - permissions["editor"] = permissionsMap["editor"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-audit"]; ok { - permissions["audit"] = permissionsMap["audit"] - } - if _, ok := webAdminUserInfo["ibm-ace-dashboard-viewer"]; ok { - permissions["viewer"] = permissionsMap["viewer"] - } - } - } - - serverconfYaml, err := marshal(&serverconfMap) - if err != nil { - log.Errorf("Error marshalling server.conf.yaml overrides: %v", err) - return nil, err - } - - return serverconfYaml, nil - -} - -func keyGen(password string) string { - salt := make([]byte, 16) - rand.Read(salt) - dk := pbkdf2.Key([]byte(password), salt, 65536, 64, sha512.New) - return fmt.Sprintf("PBKDF2-SHA-512:%s:%s", b64EncodeString(salt), b64EncodeString(dk)) -} - -func readServerConfFile() ([]byte, error) { - return readFile(homedir + "ace-server/overrides/server.conf.yaml") - -} - -func writeServerConfFile(content []byte) error { - return writeFile(homedir+"ace-server/overrides/server.conf.yaml", content, 0644) -} - -func outputFilesLocal(log logger.LoggerInterface, files map[string]string) error { - dir := homedir + "ace-server/config/registry/integration_server/CurrentVersion/WebAdmin/user/" - webadminDir := dir + files["role"] - err := mkdirAll(webadminDir, 0755) - if err != nil { - log.Errorf("Error creating directories: %v", err) - return err - } - - for fileName, fileContent := range files { - // The 'role' is populated from the users.txt files in initial-config e.g. admin-users.txt we need to trim this to the actual role which would be 'admin' - fileContent = strings.TrimPrefix(fileContent, "ibm-ace-dashboard-") - err := writeFile(webadminDir+"/"+fileName, []byte(fileContent), 0660) - if err != nil { - log.Errorf("Error writing files: %v %s", err, fileName) - return err - } - - } - - return nil -} - -func b64EncodeString(data []byte) string { - return b64.StdEncoding.EncodeToString(data) -} - -func readWebUsersTxtLocal(log logger.LoggerInterface, filename string) (string, string, error) { - out, err := readFile(filename) - if err != nil { - log.Errorf("Error reading WebAdmin users.txt file: %v", err) - return "", "", err - } - - credentials := strings.Fields(string(out)) - return credentials[0], credentials[1], nil -} diff --git a/internal/webadmin/webadmin_test.go b/internal/webadmin/webadmin_test.go deleted file mode 100644 index b95cc5b..0000000 --- a/internal/webadmin/webadmin_test.go +++ /dev/null @@ -1,490 +0,0 @@ -package webadmin - -import ( - b64 "encoding/base64" - "errors" - "os" - "strings" - "testing" - - "github.com/ot4i/ace-docker/common/logger" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v2" -) - -// Allows us to read server.conf.yaml out into a struct which makes it easier check the values -type ServerConf struct { - RestAdminListener struct { - AuthorizationEnabled bool `yaml:"authorizationEnabled"` - } `yaml:"RestAdminListener"` - Security struct { - LdapAuthorizeAttributeToRoleMap struct { - RandomField string `yaml:"randomfield"` - } `yaml:"LdapAuthorizeAttributeToRoleMap"` - DataPermissions struct { - Admin string `yaml:"admin"` - } `yaml:"DataPermissions"` - Permissions struct { - Admin string `yaml:"admin"` - Audit string `yaml:"audit"` - Editor string `yaml:"editor"` - Operator string `yaml:"operator"` - Viewer string `yaml:"viewer"` - } `yaml:"Permissions"` - } `yaml:"Security"` -} -type ServerConfWithoutSecurity struct { - RestAdminListener struct { - AuthorizationEnabled bool `yaml:"authorizationEnabled"` - } `yaml:"RestAdminListener"` -} - -type ServerConfNoPermissions struct { - RestAdminListener struct { - AuthorizationEnabled bool `yaml:"authorizationEnabled"` - } `yaml:"RestAdminListener"` - Security struct { - LdapAuthorizeAttributeToRoleMap struct { - RandomField string `yaml:"randomfield"` - } `yaml:"LdapAuthorizeAttributeToRoleMap"` - } `yaml:"Security"` -} - -func Test_ConfigureWebAdminUsers(t *testing.T) { - log, _ := logger.NewLogger(os.Stdout, true, false, "testloger") - oldReadFile := readFile - oldProcessWebAdminUsers := processWebAdminUsers - oldApplyFileAuthOverrides := applyFileAuthOverrides - oldWriteFile := writeFile - oldOutputFiles := outputFiles - t.Run("Golden path - all functions are free from errors and ConfigureWebAdminUsers returns nil", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return nil, nil - } - - processWebAdminUsers = func(logger.LoggerInterface, string) (map[string]string, error) { - usersMap := map[string]string{ - "ibm-ace-dashboard-admin": "12AB0C96-E155-43FA-BA03-BD93AA2166E0", - } - - return usersMap, nil - } - - applyFileAuthOverrides = func(log logger.LoggerInterface, webAdminUserInfo map[string]string, serverconfContent []byte) ([]byte, error) { - - return nil, nil - } - writeFile = func(name string, data []byte, perm os.FileMode) error { - return nil - } - outputFiles = func(logger.LoggerInterface, map[string]string) error { - return nil - } - err := ConfigureWebAdminUsers(log) - assert.NoError(t, err) - readFile = oldReadFile - processWebAdminUsers = oldProcessWebAdminUsers - applyFileAuthOverrides = oldApplyFileAuthOverrides - writeFile = oldWriteFile - outputFiles = oldOutputFiles - - }) - - t.Run("readServerConfFile returns an error unable to read server.conf.yaml file", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return nil, errors.New("Unable to read server.conf.yaml") - } - err := ConfigureWebAdminUsers(log) - assert.Error(t, err) - assert.Equal(t, "Unable to read server.conf.yaml", err.Error()) - readFile = oldReadFile - - }) - t.Run("processAdminUsers returns an error unable to process webadmin users", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return nil, nil - } - - processWebAdminUsers = func(log logger.LoggerInterface, dir string) (map[string]string, error) { - return nil, errors.New("Unable to process web admin users") - } - - err := ConfigureWebAdminUsers(log) - assert.Error(t, err) - assert.Equal(t, "Unable to process web admin users", err.Error()) - readFile = oldReadFile - processWebAdminUsers = oldProcessWebAdminUsers - - }) - - t.Run("applyFileAuthOverrides returns an error unable to apply file auth overrides", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return nil, nil - } - - processWebAdminUsers = func(log logger.LoggerInterface, dir string) (map[string]string, error) { - return nil, nil - } - - applyFileAuthOverrides = func(log logger.LoggerInterface, webAdminUserInfo map[string]string, serverconfContent []byte) ([]byte, error) { - return nil, errors.New("Unable to apply file auth overrides") - } - - err := ConfigureWebAdminUsers(log) - assert.Error(t, err) - assert.Equal(t, "Unable to apply file auth overrides", err.Error()) - readFile = oldReadFile - processWebAdminUsers = oldProcessWebAdminUsers - applyFileAuthOverrides = oldApplyFileAuthOverrides - - }) - - t.Run("writeServerConfFile error writing server.conf.yaml overrides back into the file", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return nil, nil - } - - processWebAdminUsers = func(log logger.LoggerInterface, dir string) (map[string]string, error) { - return nil, nil - } - - applyFileAuthOverrides = func(log logger.LoggerInterface, webAdminUserInfo map[string]string, serverconfContent []byte) ([]byte, error) { - return nil, nil - } - writeFile = func(name string, data []byte, perm os.FileMode) error { - return errors.New("Error writing server.conf.yaml back after overrides") - } - err := ConfigureWebAdminUsers(log) - assert.Error(t, err) - assert.Equal(t, "Error writing server.conf.yaml back after overrides", err.Error()) - readFile = oldReadFile - processWebAdminUsers = oldProcessWebAdminUsers - applyFileAuthOverrides = oldApplyFileAuthOverrides - writeFile = oldWriteFile - - }) - - t.Run("writeServerConfFile error writing server.conf.yaml overrides back into the file", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return nil, nil - } - - processWebAdminUsers = func(logger.LoggerInterface, string) (map[string]string, error) { - usersMap := map[string]string{ - "ibm-ace-dashboard-admin": "12AB0C96-E155-43FA-BA03-BD93AA2166E0", - } - - return usersMap, nil - } - - applyFileAuthOverrides = func(log logger.LoggerInterface, webAdminUserInfo map[string]string, serverconfContent []byte) ([]byte, error) { - - return nil, nil - } - writeFile = func(name string, data []byte, perm os.FileMode) error { - return nil - } - outputFiles = func(logger.LoggerInterface, map[string]string) error { - return errors.New("Error outputting files during password generation") - } - err := ConfigureWebAdminUsers(log) - assert.Error(t, err) - assert.Equal(t, "Error outputting files during password generation", err.Error()) - readFile = oldReadFile - processWebAdminUsers = oldProcessWebAdminUsers - applyFileAuthOverrides = oldApplyFileAuthOverrides - writeFile = oldWriteFile - outputFiles = oldOutputFiles - }) -} - -func Test_processWebAdminUsers(t *testing.T) { - log, _ := logger.NewLogger(os.Stdout, true, false, "testloger") - t.Run("readDir returns error reading directory", func(t *testing.T) { - oldReadDir := readDir - readDir = func(name string) ([]os.DirEntry, error) { - return nil, errors.New("Error reading directory") - } - _, err := processWebAdminUsers(log, "dir") - assert.Error(t, err) - assert.Equal(t, "Error reading directory", err.Error()) - - readDir = oldReadDir - }) - t.Run("Golden path - processWebAdminUsers loops over fileList and falls readWebUsersTxt for each .txt file ", func(t *testing.T) { - webAdminUsers, err := processWebAdminUsers(log, "testdata/initial-config/webusers/") - assert.NoError(t, err) - assert.Equal(t, "1758F07A-8BEF-448C-B020-C25946AF3E94", webAdminUsers["ibm-ace-dashboard-admin"]) - assert.Equal(t, "68FE7808-8EC2-4395-97D0-A776D2A61912", webAdminUsers["ibm-ace-dashboard-operator"]) - assert.Equal(t, "28DBC34B-C0FD-44BF-8100-99DB686B6DB2", webAdminUsers["ibm-ace-dashboard-editor"]) - assert.Equal(t, "929064C2-0017-4B34-A883-219A4D1AC944", webAdminUsers["ibm-ace-dashboard-audit"]) - assert.Equal(t, "EF086556-74B8-4FB0-ACF8-CC59E1F3DB5F", webAdminUsers["ibm-ace-dashboard-viewer"]) - }) - - t.Run("readWebUsersTxt fails to read files", func(t *testing.T) { - oldReadWebUsersTxt := readWebUsersTxt - readWebUsersTxt = func(logger logger.LoggerInterface, filename string) (string, string, error) { - return "", "", errors.New("Error reading WebAdmin users txt file") - } - _, err := processWebAdminUsers(log, "testdata/initial-config/webusers") - assert.Error(t, err) - assert.Equal(t, "Error reading WebAdmin users txt file", err.Error()) - - readWebUsersTxt = oldReadWebUsersTxt - }) - -} - -func Test_applyFileAuthOverrides(t *testing.T) { - log, _ := logger.NewLogger(os.Stdout, true, false, "testloger") - usersMap := map[string]string{ - "ibm-ace-dashboard-admin": "12AB0C96-E155-43FA-BA03-BD93AA2166E0", - "ibm-ace-dashboard-operator": "12AB0C96-E155-43FA-BA03-BD93AA2166E0", - "ibm-ace-dashboard-editor": "12AB0C96-E155-43FA-BA03-BD93AA2166E0", - "ibm-ace-dashboard-audit": "12AB0C96-E155-43FA-BA03-BD93AA2166E0", - "ibm-ace-dashboard-viewer": "12AB0C96-E155-43FA-BA03-BD93AA2166E0", - } - t.Run("Golden path - server.conf.yaml is populated as expected", func(t *testing.T) { - // Pass in server.conf.yaml with some fields populated to prove we don't remove existing overrides - servConf := &ServerConfWithoutSecurity{} - servConf.RestAdminListener.AuthorizationEnabled = true - servConfByte, err := yaml.Marshal(servConf) - assert.NoError(t, err) - - serverConfContent, err := applyFileAuthOverrides(log, usersMap, servConfByte) - assert.NoError(t, err) - - serverconfMap := make(map[interface{}]interface{}) - err = yaml.Unmarshal(serverConfContent, &serverconfMap) - assert.NoError(t, err) - // This struct has the security tab so that it can parse all the information out to checked in assertions - var serverConfWithSecurity ServerConf - err = yaml.Unmarshal(serverConfContent, &serverConfWithSecurity) - if err != nil { - t.Log(err) - t.Fail() - - } - - assert.Equal(t, "read+:write+:execute+", serverConfWithSecurity.Security.DataPermissions.Admin) - assert.Equal(t, "read+:write+:execute+", serverConfWithSecurity.Security.Permissions.Admin) - - assert.Equal(t, "read+:write-:execute+", serverConfWithSecurity.Security.Permissions.Operator) - assert.Equal(t, "read+:write+:execute-", serverConfWithSecurity.Security.Permissions.Editor) - assert.Equal(t, "read+:write-:execute-", serverConfWithSecurity.Security.Permissions.Audit) - assert.Equal(t, "read+:write-:execute-", serverConfWithSecurity.Security.Permissions.Viewer) - - }) - t.Run("server.conf.yaml has a Security entry but no entry for DataPermissions or Permissions - to prove we still change permissions if security exists in yaml", func(t *testing.T) { - - servConf := &ServerConfNoPermissions{} - servConf.Security.LdapAuthorizeAttributeToRoleMap.RandomField = "randomstring" - servConfByte, err := yaml.Marshal(servConf) - assert.NoError(t, err) - - serverConfContent, err := applyFileAuthOverrides(log, usersMap, servConfByte) - assert.NoError(t, err) - - serverconfMap := make(map[interface{}]interface{}) - err = yaml.Unmarshal(serverConfContent, &serverconfMap) - assert.NoError(t, err) - - // If the Permissions or DataPermissions do not exist we create them and therefore the below struct is to parse them into to make the `assert.Equal` checks easy - var serverConfWithSecurity ServerConf - err = yaml.Unmarshal(serverConfContent, &serverConfWithSecurity) - if err != nil { - t.Log(err) - t.Fail() - } - - assert.Equal(t, "read+:write+:execute+", serverConfWithSecurity.Security.DataPermissions.Admin) - assert.Equal(t, "read+:write+:execute+", serverConfWithSecurity.Security.Permissions.Admin) - - assert.Equal(t, "read+:write-:execute+", serverConfWithSecurity.Security.Permissions.Operator) - assert.Equal(t, "read+:write+:execute-", serverConfWithSecurity.Security.Permissions.Editor) - assert.Equal(t, "read+:write-:execute-", serverConfWithSecurity.Security.Permissions.Audit) - assert.Equal(t, "read+:write-:execute-", serverConfWithSecurity.Security.Permissions.Viewer) - - }) - - t.Run("Unable to unmarhsall server conf into map for parsing", func(t *testing.T) { - oldUnmarshal := unmarshal - unmarshal = func(in []byte, out interface{}) (err error) { - return errors.New("Unable to unmarshall server conf") - } - - _, err := applyFileAuthOverrides(log, usersMap, []byte{}) - assert.Error(t, err) - assert.Equal(t, "Unable to unmarshall server conf", err.Error()) - unmarshal = oldUnmarshal - }) - - t.Run("Unable to marshall server conf after processing", func(t *testing.T) { - oldMarshal := marshal - marshal = func(in interface{}) (out []byte, err error) { - return nil, errors.New("Unable to marshall server conf") - } - _, err := applyFileAuthOverrides(log, usersMap, []byte{}) - assert.Error(t, err) - assert.Equal(t, "Unable to marshall server conf", err.Error()) - marshal = oldMarshal - }) -} -func Test_KeyGen(t *testing.T) { - // Result is of the format ALGORITHM:SALT:ENCRYPTED-PASSWORD - result := keyGen("afc6dd77-ee58-4a51-8ecd-26f55e2ce2fb") - splitResult := strings.Split(result, ":") - - // Decode salt - decodedString, err := b64.StdEncoding.DecodeString(splitResult[1]) - if err != nil { - t.Log(err) - t.Fail() - } - - // Decode password - decodedPasswordString, err := b64.StdEncoding.DecodeString(splitResult[2]) - if err != nil { - t.Log(err) - t.Fail() - } - assert.Equal(t, "PBKDF2-SHA-512", splitResult[0]) - assert.Equal(t, 16, len(decodedString)) - assert.Equal(t, 64, len(decodedPasswordString)) -} -func Test_readServerConfFile(t *testing.T) { - readFile = func(name string) ([]byte, error) { - serverConfContent := []byte("this is a fake server.conf.yaml file") - return serverConfContent, nil - } - serverConfContent, err := readServerConfFile() - assert.NoError(t, err) - assert.Equal(t, "this is a fake server.conf.yaml file", string(serverConfContent)) -} - -func Test_writeServerConfFile(t *testing.T) { - writeFile = func(name string, data []byte, perm os.FileMode) error { - return nil - } - serverConfContent := []byte{} - err := writeServerConfFile(serverConfContent) - assert.NoError(t, err) -} - -func Test_outputFiles(t *testing.T) { - log, _ := logger.NewLogger(os.Stdout, true, false, "testloger") - m := map[string]string{ - "password": "password1234", - "role": "ibm-ace-dashboard-admin", - "version": "12.0.0.0", - } - t.Run("Golden path scenario - Outputting all files is successful and we get a nil error", func(t *testing.T) { - oldmkdirAll := mkdirAll - oldwriteFile := writeFile - mkdirAll = func(path string, perm os.FileMode) error { - return nil - } - writeFile = func(name string, data []byte, perm os.FileMode) error { - /* - This UT will fail if the code to trim 'ibm-ace-dashboard-' from the contents of the 'role' file gets removed. - This contents gets read in dfrom users.txt file e.g. admin-users.txt with 'ibm-ace-dashboard- PASSWORD' as the format. - Example - 'ibm-ace-dashboard-admin 08FDD35A-6EA0-4D48-A87D-E6373D414824' - We need to trim the 'ibm-ace-dashboard-admin' down to 'admin' as that is the role that is used in the server.conf.yaml overrides. - */ - if strings.Contains(name, "role") { - if strings.Contains(string(data), "ibm-ace-dashboard-") { - t.Log("writeFile should be called with only the role e.g. 'admin' and not 'ibm-ace-dashboard-admin'") - t.Fail() - } - } - return nil - } - err := outputFiles(log, m) - assert.NoError(t, err) - mkdirAll = oldmkdirAll - writeFile = oldwriteFile - }) - - t.Run("mkdirAll fails to create the directories for WebAdmin users", func(t *testing.T) { - oldmkdirAll := mkdirAll - mkdirAll = func(path string, perm os.FileMode) error { - return errors.New("mkdirAll fails to create WebAdmin users text") - } - outputFiles(log, m) - mkdirAll = oldmkdirAll - }) - - t.Run("Writing the file to disk fails", func(t *testing.T) { - oldmkdirAll := mkdirAll - mkdirAll = func(path string, perm os.FileMode) error { - return nil - } - oldwriteFile := writeFile - writeFile = func(name string, data []byte, perm os.FileMode) error { - return errors.New("Unable to write files to disk") - } - err := outputFiles(log, m) - assert.Error(t, err) - mkdirAll = oldmkdirAll - writeFile = oldwriteFile - }) - -} -func Test_b64EncodeString(t *testing.T) { - type args struct { - data []byte - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Base64 the following text - hello", - args: args{data: []byte("hello")}, - want: "aGVsbG8=", - }, - { - name: "Base64 the following text - randomtext", - args: args{data: []byte("randomtext")}, - want: "cmFuZG9tdGV4dA==", - }, - { - name: "Base64 the following text - afc6dd77-ee58-4a51-8ecd-26f55e2ce2fb", - args: args{data: []byte("afc6dd77-ee58-4a51-8ecd-26f55e2ce2fb")}, - want: "YWZjNmRkNzctZWU1OC00YTUxLThlY2QtMjZmNTVlMmNlMmZi", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := b64EncodeString(tt.args.data); got != tt.want { - t.Errorf("b64EncodeString() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_readWebUsersTxt(t *testing.T) { - log, _ := logger.NewLogger(os.Stdout, true, false, "testloger") - t.Run("readWebUsersTxt success scenario - returns the username and password with nil error", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return []byte("ibm-ace-dashboard-admin afc6dd77-ee58-4a51-8ecd-26f55e2ce2f"), nil - } - - username, password, err := readWebUsersTxt(log, "admin-users.txt") - assert.NoError(t, err) - assert.Equal(t, "ibm-ace-dashboard-admin", username) - assert.Equal(t, "afc6dd77-ee58-4a51-8ecd-26f55e2ce2f", password) - - }) - t.Run("readWebUsersTxt failure scenario - returns empty username and password with error", func(t *testing.T) { - readFile = func(name string) ([]byte, error) { - return []byte{}, errors.New("Error reading file") - } - - username, password, err := readWebUsersTxt(log, "admin-users.txt") - assert.Equal(t, "", username) - assert.Equal(t, "", password) - assert.Error(t, err) - }) -} diff --git a/licenses/LICENSE b/licenses/LICENSE new file mode 100644 index 0000000..7b0e98d --- /dev/null +++ b/licenses/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 IBM Corporation + + 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. diff --git a/licenses/non_ibm_license b/licenses/non_ibm_license deleted file mode 100644 index 55d92577db2be7053c6c3ec1d937787d4b13f2f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57010 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≧?NyyuOxoF*tGmZF0$z&yOTeIr#P0Cu zGiMzSkLfPBS$CbTy+Ep-OOH$Df!~3H_vinx`DbNc_rV1BbaUIieyjQBK z$dud=eR|J{kpN$VUE({kDiaNY-HSYrIlU+>?0oLXPv=VAj1k?n-NoheirIh4p_^GZvLjXpA0)$ACzy`*fSF|kfk7X-(a80 z0)9Q>EPX$ceoL;^0(ka5TdN(My^NJCSLT_q`eo?#FZ^dLJDz#Q2%NUW`YLw(u-gF{p0C^O z9gO20NYn>V65ew+ft`wZ|M%<{##(_`L9}x<_i62)94nyTI_Tt&w1srAt9P0m{N zK9AFXs9EKWC+qT-E+UfNVPB%qqo<~-B|}4#U=hEXjtTaaE`jtmy6IDlb(!7)dGcg@ z{3X0c+r9RO1hgqeW>#_qGI@9MC(G+SAaJ6e#$S#*CCCTpNx`Ew=x4NX^Hjgl`9<#$ z<9xG#JtXQUJ5S0U?XAV>!MWowHWb|U45Qade|79aoTvA-cEm0<6giyQ(fjajvTur# zXZ3u9x9Z=61s{4+yf0UFw)N&ByU*&p_M_+BAJf870Ey&!A69Tk(qpDdmr z_bqiyCU@6jk5%WKi#5jE86V6B*9hF(&eJk3Kn#^%H)`>;fdNtH$IO zcWbUO2HmZz`h9*^7B5Br7T*XZ_(GomNgmdoDpa?1b*$q$dmnhe&O7l6&zF&(R%Z8X z$?UGw8ClZ^jbx{0wj3eW_H{n>LLK=n^TGo(o7XBy-+7A#j~M~JoYiuUlHufjM(QN; zVepXq+sQve9oY!yTz-+&c+Yx}ajnjz)0f>|O);7`67(Ac5;vj2*kv|~&owf+?*nSZ zP5K;j#%L^nuFkbmt3*Tl`JE`zyRUcV5MLORbJ|nJr?kInOXgjm8e;z&b6^LY=#;qoWu?|(sx_5 zRf8xx|NN#7YafBch~XeTHh~^V?8T{Y3*{dt2)U zXIKzu(TZo>j?d3cof2OH_sQKDi)?n5P;w?c?Tof$RQ3$fpxIndXTB&+@jET;8T)F( zDlXTr*WsPV`&!BB>_Is4pVziPl3rQC{{^LfXB?7(U%rMl%ONn-=ops${6B3b#OvY!)Ld9 zz7@N9Py0g79HT|wE6Q`H*k^_w@pMniFK2aWtor9&8BoJ~u3d+IE2h-_$ck@buaORR z_-c8I*oq{AIi$0G7Cv4YkA+`UOJFK-)1EuuM^3Y+$jFTpFq9bN$XojW)oa@3UPmfO zJdID^g7#pnpDYi}50}^znWbXWu0cIE`sbEp^BX=u1bRgBbTPFuBN<#^C1#9c7j<6Q z(#J*8`aQ0H6%kiHul}s*mqCI#DAKlzPkL>aIesSQ!@`R09U=@($QV1U`wON zB3_W$;|avk_Nno~8?ui%_7iG+y~cL(j}JtvE-YJMaH+ zpLMWKR>9x>wlHMS*qYzOLrh6F+}z-O;3Sp8h@bE-62^*VtYA_(Nmb(9%pqRE^Jbbx z?}ZNnwc*ZAArbJwa(t%Q@k4dIK>TPVY&7!84P<)hyNP!2!LtYKyCDgD22WY<9*&;< zxK!6vjkXx7nAJ?jnw&24r^^wnT0O%LQMV(nGou@>W<__56s={jvR>Jrqek07M{dS) z1~VdP1RcDo_EPOHxKe)`ZM)*0b$1cYgCJux`lry`Qp6Lgjc?M*!uOt$zSE9ysg6kzmD3dcg9_&MTt9GNjRd*d;D^8>~(zsDG zffIBN+PUwL8PHR9-jKn>66ULkt;|nl^7zHrZ&xRYd+iy=dkC^0hS`}Tvybu6+@E;a zdI_~_iOu=vC;{lia<_XGu({*ACZq`2!66z4k@4+qJ&qp9B(7`2YRxq`tR0I&2kgH| zU7-A0zo2f`*bkQbdXzW0d{(Z;Y@P8yDN)mV{c<-9J_@O3)go5yN+=c@oogshoF8{o=s&HnUCre0QS3pehSsdR2<|ykcRxJ1G1B5jB;8s779K>Ie0Rg?_JWdKTyEgT$5vbHVS0*$x04w zIaaKC&8e{wpYkf%+=!;R-ZPJwyI4UUa?p=MpVqmK4D^49h^Z*h`Tjeukl^^{t1FA0 zt-S~+vsV=>)XTGG=GwQP(W`w!GRaq?-?+&t0ILh4fur`3x+_SKKC$&C*yrWo*zpO8zQwk4jNzW1l(bQ`0cCa#wXQtzhEy>L+ z6@C7CJyric&%o%|j`wH;W04D=jZJ({E`xU#b7Wq=Q)jj#=rv*8&3=~>j`rk| zYWn4SkBmrNVO&F^NYGfKMAT`zMtBBrwbx-9`zxcTPF=S44zxjjF*XZ!eG^-6|; zl<|Y~ZpdSlHZjP|1dXkAk!#0_0Is>-h)c)2{mq==;_d9QGIruHYb|Sj@)S4fT%QOV z#B4JOQPG}Wy{5Ym4M4ecc7`#_hwfACJh98#oh!zD>T)&4i$nx4z;V|v`U#L6cjbW` z2v0WW(E63V>{{f+KE_OE%n&^*nGW?x6^-nawW{@YN+^Z3g+qkA8YVQ;;z3$q% z4(sUT?cIHw@5uzKH&3-r#C}jQiYx9F7VrRBSJgXpSK3*_@r3&urDy z>tifCKT|}DzhC&FCkS7Ti2OkcxdIscbECqindqg*JWk1s`R~lbzMiomvN^7s^|_{d zou~FLYP~fhwBNm79+@@A?rxa320>KMR3hJ%6d#wI*e{Y*JUGF3sp0gUT=kDn7|+qO zQPE~^;@(+E!~B7W1_~HsFJ~To|M8r8yE{hxGY>eUW_DF(Ey+JM4+d+og6_?j+x$J- z12=!q_N;0+cUZvpk?q|cZ1K^HKK#j2VQpjA)?brj86WY~d-1{wYK~xb zk+9yZvpg^E@J=03&WhLTn2_Ay%^Kn5V(H9(XRXPAUZ-f_V6$IQccz%~^50 zyW68qlZVdZnFF;SLncIF&a$JBY(9}|ma5pb8=*&RPRLUCeeXJo8K;N>-h;9JAD*b+@VQ&BA6<*l&+9ypsPxil7*B;CO)O- z`RD7ZjO)4ZnEN)EZ_c|yrLmZ}Ht$StO(y#$AMnl;N7|3B=p$Jla)0@(+c#Q+xwS9V zqlmogi(b2TgH*>|8tLx7Uz&$^s0ZC3-QDye0^6e|FlU^*wxGove!tE@he#QzClAg$ z$i@yHz7IKLCv)#E-#jjEl0)e!rLHSBEI`ZJW_VpeiFNvhbX8J&S zi(_#6#xlRCB{m?(bPTXbE7xdVd3F?R3q(@L0Q6J$+H=rWV-aTe%p&%X4EIgPQr8qB z+ZlJFI(z;jWqQ%j9IGYe=5+_zkrN~ARDdD7YGZYhUd`+kFTQeNcP4!js+Gjf z&Ux^mIm2~`^DHqGb2k1DyNMq+zh+luf<*^KE2+#*C(TvaM8{FL#<;dhPH1$zEE1->w}L m?=SC_r9yq9p54szuK%_EdZ#%5$2!Il&b)r%Gw)pJ%>M^OrP^Bn diff --git a/licenses/notices b/licenses/notices deleted file mode 100644 index e303f935502d07756ce7cd601762271ac1ea0c83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 685218 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

ghqV*h2F{ z@Yzb*9B2F#B;YA_?27zMKLY89N`)_W74gv?1?RoY>Rf{Gtf+#-V?5vq&O9Meep8fi zC;7YdY-y>gxW`@S>$5_;+v_WO-#d)iSZ2I4@ha<}WSXoWS+8(i9|6a8zpQN~_b-f5 z3$>Dx6=i0w2s_hnFICqH;9QL{3fioBkc|5)Kw|f=jGgQKXHsiT<1j^~y^`8ibL2=P z=8&yHtC2K1pYeQHTcf2ZlW}FG)(enMG)s;`mVo>cvBcu^mwRbq|91bu<&sE^O}(ex zj-$-zGor6ojYC=X-#Y)?rtlK=EXPD#y5F251edYftI;}Y=wdfp35R4U*}vPzD|6eN z)w;EFvomt_oVSx$x%Qdj13hIoC-^d2d13k(E0INd3)FY;gvH?**#oX66vC}52P>p4 z7jUe`84t^aLV33*K_0+WSLdQKXlg6gdRf;~x8VSOwh^>Ph>W6*klV>+cCKJ?Skk{V zo9Syp*P^z*7#&*^fDqnYyUCKRW@IddH`6okdR?-fxiNEze&&%YvyD8}dVQo5SqHIW zS+4fy+`IT5_(O)W)$TfeY$Y9k4fTDMuAQxSNUig>vbjFygkn&puk?|CJ3zo1tj+ut z4#>>iD=D@x#$1kd!k9IfXDZ+)*$u&aK^dj5nS;}Oo+lZN)MO-~_~S+e`||OwJMB8M zj9`H|@p@m|({?>|UBCJo$s=*+#Z$F3>!`E#H`s%xVpUadW1+X2ocQT88{DDaNWppg zi&HV_BHS^Lo*?#s!rkZ)AqOBNcOvl1!!|k z|6Ie7KD^Zl9q1H4`&qLxd$b?i_^^}t`@>#kP;~7(q4Ka-)LxyjA}+6RWV;vSE2*6&7%UV21e)cA<{fPbPdIOG|%iCLmCeo>jWnF%L(6H&n8_+MXP`Yj`8zhYfE zN9)R+poT1v-e~P%tT+{~gS&J{snp9t=QE6wo!18a3WD}zwE*H<(?q5fd+~Xsg1*Ip zP*`0!B)yNRSazPBVs#)-j3LFfElZEut}|lPHan#aixt=aQr;a<3LO5g`?7UFbd9_J9J5)r4UN71Rpj6I6&?50WP;ca{ z;^Cg|VmIrq)16nADsqXqWFFuS96WBn87+Rj=~*4Wdz1pbpk%6WQMu@zhJ#+Isv=Wz zL-gr0Cq@FW2EPP5^2|dti0z(o9&_?xJnT4k$J|CT1W@LF&H!G?j&WDr=T{ z9!cDiYqda>@;O_4!aM2Ck6fT#tRj^1b8kob+KxuwL6=%_xGl8I=)qBCi{yDTePYR9 z+h62IAGBx-vLXe&**rqN$>@&T47$OJ=_e2wh)Fy%g=R}?ecb|zGpD-nqyT|Lr%e|T&+@-BNb*JN*_ zr$?B}I18E}VtYPq>7|eH+?g|DMN#N2JdI}unMWIe(~?--b*E{R-44j`xNf&kF!oO% zQ6HG^pd;p!M>`ec{_hbN`dWclL9}x<*LVHBbCkAs+I%DOV0lCZ>=^E6<9bKlITxKD zmqYw)T}xu`tld&OtSiaF1~W@Fe{3#%`>a_9-s768=oPLa<3ef?8XG#k>V0hX@3k?s zWKP!QEnP$;(P1u8=n<*Oz{t?hB(?~yrelJCrEZtrvTphmeO)FxAWu%l2QT40+OD-f zB%r-9GP9B^kjcA~KUrSw0fCeLJcQ~yfZB9T;ZYy-Z?tj$Qor%=Q=bvzdb5C;C5n@k zCuIk~*5gDNo;ZvT#cpec9<|b6?Yj`i^uE@Pwo45~4ySfRAKtO?mqy9Uc0R&;?Z4-X zeTbx>FLze9b$gNB7wx*jQRw=IZ~Q-IxzlF-VUrt}$F3=Sig!YMzx{l7`Hx*xcQ1c` zdvr0=+nWt3{AeWw&Qe4QZ;y)W+RYf%UN%c2y1dtV25|6RLz-ljSRN=4=^nFrt~*<- z84S<<33JxS=Rrd;?B6pcKncDl1LW=}EpzoLi{yNII(3GT$m=V+D32Za0=YnhIf9Ap zg?v8QMJ`7d(=56C>loj}m-*9$$GK)tVV{+ZxD(0iY2)(;@X+@W$2*DF&zf}e4j*z; zbcu9{BYaQX@U%?3a;ZPSC^ANRdC1Q!ull&bbmPvp_Z?hU&z^UfQN+Q%BR~l~o?n?m9-G8D(bHUvUB%Q5h$SklztU$T>@E+WWdw5^Wr_4}HQJ z$Pus>=82CcMmit)S#Kxk7n{jd&T2MssvW>h>|{o@Sz+;qRKk6&8;&)~sa#>p_yzt! zEtRZowhn}x;njW!*H+$R7lK(2`{tuXvPd{cH_x=}1|jY1NVG8Qe4)8?`FkKniL-+^u`dW`%be`mg(iv4HbN4VgN)5C0>Y zAr0n8t){isPT$gqR@0vWQu*u5%EZPkf1yi@I^MMgVYj37`D;Zr=G6Lm_Y_u9pQMB3 z`ZHHmOiU)5tL3%gTA$I^GmLU5AD{}_$~hsfeq|S5?6~Tt{@L<*HGEx-QnTbeIpmcy z-N0X8K~VElF96H4CgffveF%S%C3;le^BI-Zo*tDR6`P%h7t4rH=^J{LTeO6f!42kB zQ?6kcUqux@&&mVW9rru=B!i;g`Ss#|t|#PKbS&}p_N})XwuPb_8AA97! zMyjYhX1zucW<5oVbQ_GQ+CZ$Omt^=oHKJb4A4XkVUp~Etkj)(7b210M)8qUYzgcT> z&CLF1*_obc9GT66ma|ZI0M)pqaY;zyINY6`6$L96% zHWob6Rci#~BGROnt@D--T7J&?Xt&yafWK{hhIHJ+Syt1oYrUfn+-sR#c2#Ee>!Q&I z5;D{J-04T{JG$>gC3g?>z5wr8|Mm0!O1{Mc(6qcqR_F>`8#8P%UONRj*ki)@NS4(u z#sOu)zDUK2jXh7C@XxNr>>^R-eMHAb{<>JE&j6trRsr-Ski}m5y6fMMan|M`O5lgo z2Jg53dQ9T8tL)^UD+!)}e?&EV3wPUJ#MOS+$@drj`#m4}-yZ6~m8iO1QLoAut#Mu6 z!#@ zVsX{i`j{Lfqng)oK`ME_PRHkzJRSQmp9EcdJMJmPPhPZd-=rna^2h61aeTM;n8>fU zX>B5F!CQQf$KuW9H~ZG$cr*QjD%2ePleV{-f3TA6U80_GIRq(U~zpI#sL z8rEui{{&U=u-G|Kg4z0dE@;lRBS+|vOp3^D{2#yH&uP8WwX?o&&m4Y7{y_}uqa)vv zdCzwt^R1F3`uVm85VWV?C>BcwJw5Abi=(sdn8-BOE|iYhf3m8ol1rm#KiLB^A0yK! z#pG1!ewKBBNn6UR6Ga`YcCf0#0j-K}kqxv+>)I!+fUpg-NGe9HSQ-4W^+s4RNLF&* zEa`Xcud7uWW7Hh|xsP>94;rw6b&ZGBV#)Ngf(<2!yX`Zs@ChIHn6NkZy|zR5y>=4A zoi^&IgL`c(_A_I{SPknY#A(nkyOI0Vit%n@0a!%l`{LEuW}}amarC(4i_Fd(v8`GY z3+jL~Qty?{x6Ts!)%s9LE=P3N_A4L7>#+;I<$wIgojk1D8`J!|*MjI2d^YwGt%6WK zL*)1CNH6yFogp+;T$}&UlN;?Sxj>JZkrX4W?KU_ND&33sdaGI2i7b8hr{b#i>#mIL z-rw-34J!wHY+IMwPv-<`t+cLH`8$I5c(0%FftGyeN^vuQBl>sGsi2iQB`QK7O!2Fyr|#$MP9 zf5yY9^;v6C5f6@5UpZu(k<)`rNHK+Fkf7rT!yg_G9*`q}YWC>-PwIT!W?$S?J53B&A z$b)LlEc;49DWqkj^>iP5q~Ustnq%!oSGC7Wwez}YBt7PC#NT~x#RE>25zV!j)87wV z`_n2*aRp0G$bik_u_*GYJfC<>rdVw1t@L0VQ_H5u-rLt!()G-+I)bibSk=YjyL3by zpS+3+ht7~zf{#@08{8Z#zqbnAA zy_k8o`N&_}$aFV}&r}^oYV=WWh+TKE6Xo;s$)%p4t*ltg7<;v56n}Gb{M5*^&V{%C>XsyZey8Q%&kPbAqLj`Xf23_kD$5WCzM5w$t`E^P-f+ZxRgh}=JRaZJfsODzG7881JU!c0zpl#kCts-9*j-jI z&BMSrPV%y!f-L8q*jbc!Tziw|#L#XpF4j&K((l(<7Dto9v$9 zdnrwC0y}~#J4B;4{LJgCI(I}p70=Gv6tae2IM9mrtlSavuYTbBlBn&8qF*#!TlGS{ z@*c4lkDTw4wyR|XOgEoVw4ZepmLmzbhu(uO$_3jy(yuy?RHBalv7%Fyr^V>Y5$d5&`L1 zyE0(BNp9SC6xaK4^eC*(efo~tb8ZDikAY658I6_%;zd%R9CATA_$fVX=Bn;auIyvJ z+`ZYnzI54Z+0$%J*?}1Q4!!8o^?f~U+9tiNd46F|C15qC;jx&Px2A> zn&Raku+I{XQj6s!BX|>*J`0Tc|0CJYgcB?9-Z2m(%$fPGA7Y=e3utjK%?hkfC@&J;vumae}*3V0PxAQJrkwU-`ZbES zgsxvVJG5e$$pVkYvyE>1D-GB)Iso6JD`c}zeK6&$X=l5tTwLb5{Pe_<_)*_Mo@rUM zUFWVm!2^}+&IX^2Jw5MFFK~PYiZiXS>Em5{?B20M<4j)G1H?`;1nd@T${8wB(y!%? zm2bR``+bwD9oEsN$RgI#I81-!?ivk|89MENrczs@RrUX%s`iQWPoN*C3%|yG{0SnI ze;AKO~uqc?TB3_e5#D4Ya5ynorZq+}_mY_Wk5gWo@|4od34PBn*Cg%pNh<+~@|Hku( z7HH0znM^TH%wWOD_;g9+J=6NzZ(C$E`+dP;?3p@m1(8yyH`StY)sArr3Bq&nQf7M{a^O z_4JHuE2phSZuz|mKUy$at##q+*Iztk66q@T`uL3SLVRX9R|_NL}sivm}Zc_IiI zy?qh5uFAue{*Lde+yA zm0{)SfoiR%;vQMgTC*Rv(f;Np@1VKPj>xK4K3nQjcrTpV#?QWb*Jt{ua6KA&kL@+3 zb*e+MpzC&{trPl;Q9my}Pu)ecmOW2wRlm01^@hd7`eL!$DqN3CMg%$_fBDQ`TMtM~ z?menH2}lYIr3T~KDsmdDAG^QVtvqHkp|N5P_iXKcy$f{A+{V0YL|>>%x8wC_T+@(M zhIII#nz@z3_G-VQ@3fAitdWSq*Ry1FrX>4l|28S0L$L0WQdvhPWF5xaoN2w*IIH*K z(J_phXS1>T!r31G*2o9iNCA|kE)TWqH*5KhSv(FJ$-*<+cfl75OOsW_t4_riu2=SJ zqygpeXuB%*5#yo316D4{MQSAmH0gKc7nYv#gkw>2s2)xS_w^!2bx5xNbW_QHExnJL zOo)+p7wHnQw$=4#?Xz`t4T6}96A9PPIVLCIJG#Z}zsH8Zo~+h=+QuQX_KAw(mOS8P z`yB0}D`+75WLG?SclEBparT~pEXT9VLz&&x)~8%sr4NB+#iF;F!8QY;`hR!fujJ@w z_yZd7S?yFDku^03oW_!kie_m@3A?WhovYGAoN3U!jj@sEAKI*abwyUmd0l*ZwNq`t z`t{lc@jP@|%ViCoZjCG0+tma{l7q4fYO{-1XaIGUXE|~*NGcyHHEeHfq0+c774Vi- z7Bg$RO;nah3mNw|%~-^h+@Vk+w)%*vt;7RJh8r{^+J!Yg}`@(nSL?w++CRN1LT+kO6>>w28h&WU-mJ|Hp^fit_=R(A0wBdT{^N-NSY z-N93#{)~$12-H0Dec@wGc+q$+^cXA686HU&b5NI(lVOo&4`z`wB$6k475GEs%gl_F zY{70!Fai6YpEW(}eyex>&?_BNX0Dy% z=$B8B)cPtuqk?6nK~MHtGc7#JbpXC|#|$Iks~{ntA#pxKQ~0J?1Z$ssx~3oWanA$D zz!l5{()pLT2rbO*XO2N`4BjIjEF3GwN8kWFj)bfFHa|L?kvR5s>qYt2=8|XbTN`Kz z8tsR=7~slj#o;54>QdL<`gCBtg}0GSba;S0Mev`_i~A#qLf&K zjEG$9=*FA*9m`O5=mhR-r4DN&)?tZP&E)7L;(7R>Hk4e_-U^ie&o(nNLWf?b-4gTM z7r{4s5`|KzN2Vf$vCmZz`QF;mes<2OyMJ?&d73$K^WZ7Cm7*`#aaS`s;I_8C~H& zh#q}C+(`G)Vkb~zTtnXC^Oi=m(Q|v=xTwA1H`_DYx7xk#oggY&hrnZUc)Vx63t^vo zB!iqs3$sUr+`^kHisroO)v04MS`Y+{Zm~#O*VmT4h&n%5&U9v8&()Kjl77%MxhmK^ z(!8aRju0NzqjP+;PNp2Iq?uHyM9lXNSGr@4`R2PWSO6z-frRVc%J^1N&;SXQFO?>H zD-~}N>#$Axjrunk!>^aMcn~71gkp5Csx-8f|7cgo5#i0r56j)>lU$X^q6OudidcGo zpK)>P#l4>*rM^>A_#^+Iu$eA4-1iifKHdwSC%%olxUSjT|?1Wb^)l7#}VF`nI?qEx#w0{dl2@dKue_WscY$EGjHG zm9E+96=%&8`=4Cm9=f||(px_}Lw;-%o zeA9BS`&^Gvoc?&89!29sBhgL|H#Swza70bJgZ13!`W%b1OFXLH4;5B$)e5Y9%6O)H zvCY0ht2yd!F+1lo#-6X@&(T3e(mppbLZzdz_4nGB9Lwbns$KRp`J3^htkl@LwX8j) zAe)7w*RgK=3G05)Xws{b{~yX`Uq6l=u8!BSj4UE@-1ieCJo2E;f~AZ=#Ol}SUo^?t zkIc8|n)sMrhG@)H*2HLNPaD=J#@J+mqi#=$BIr}JW*)kfSRFamUt2A}-};rvK$Lb5 z@w(SKe-f+w>1vjf4C$iLS4|&d!eZ-P3)De-Ml4sZaEKK~6)I%GJ~V>a$Jc)2~%*IqkbQWdm51 zc_npwzvgioDvsIW<4di1Z{p(Cf6e($^$0oddneUpz*mD)=mxpEibTdDzVj(GRFy16 zXhi&C6fF*aJw3kWuB%ie$ViQ=F{FOFBtkhE&#|XQ8F_}z)|&fWzf@&dvv^$cCKtnc z_^wA8cM#Y$ul^Bo#cPw-K(*f%>Zbofr8IhdkKWSZzve>vTCIV_11b+-4lF>b%+&O3 zFv}A#syYfc`|s~{c5#d>p13ux>>huQi+CCo>DS1Le@2BadhfO4oY+}iBcAEZ6Ud1~ z>x>Nv9j7RDCmq2&_oW_s%@8|~rO0s_h0!N7Xou;)aDUGq(PC{i@d`SZ%#41+q59k6 zLcGIOjv#I9;6L14Th#3v|HsYo{GQsktWPrYaTy-gR3AmD9kOS=P$v|Q&+S?Q=L+pk(ShwmY^%G7vTOa4X#KZOE%;^;DnR=U%gjZE12 z^Cc3Q-IDruUhPWZ^?N3F;DZ~b>trY^>a9=@q+U#tZ|`n@uveQC{PS$LlO zkFfwGx__y`py1Va#-;P;84!PN(PY-0O{NY4AQgOb%!JE_pSSNu>T`+$9joIOi_H_! zt$magyJx6#m4pa+8a7l{>#Rv}+kc{31$*mle%R4^Kh=L~^nBbtcl86LebDX#Kd@*j z#D7_AornQ~uq)-a%O~Vi%<%uTe?d_^f+w$;f$P{orI)}Xa^q@y0FM`bu@b5L{PKRj zXU|OD90Z=$NUV7i{pfr{8Kb{k>L+aJ4{dxtL6+7pc+R66|8*JDt1oW+zis?KG@9MD z@L$`v^&fZFU-jGe^ZEo^ecGt>8Me#}#udKaZ|8ITQ~Nvd6o0o40dr!3g)QjGZey&` z=TVa>iK6h_uln@1`1am}I)4W!{_u8aczZMP##8!qciQgIyKN2Q-PR@NZJ}?Eg7w=@ zJ!2O;AX52MQ)Lcoq3BiO z=DhjG-5bAXpOFJ$`B-)BIifp7zVuaFA)QNCbbZo%`BCftKW-yU4v`LO+VXSx2o{d=>G!pzkPlWum$ul3C8 z-mL{)s0aRb(L0o-E61Hm5Pq^hD)}5&_E-0Jc_*4c+GH=v*7yzHA%AjYesk9kGl2Q5 zztBzX*BtC%kPlfuRxcCOG1gk+Qn@D%)x4?3!Oz6sJ|;nDqU57SHFStjg<(A!h1dLq z-wzgP?eVJJ=ZG$6 zau#a?E0ek2*c0eZ7xG|t2gHv;-J!Y27RZ#d+QU7_w=xwfjoLvwmt|!Mk$!%ng&(}q z0$!imYb_x%)Uv@_jS74rJe($BZ8@vt`+O5wbM5nC9_(~q-`F2f7`u)|kKN1TboN>M z@$nAxfK~SXN;OIs_2JCL3%t0 z-^!B%r`W|dZyt99Ar-rL58Kb`$Q{<(4;L9cY_w48%k5`Pde+6g&w(>}6BEBNn{lA` z3a$URcA4S0&Gp{EEMgHEIufK-*M_JQ$PLDBiM>7Ff>ia?)n)XfOBwZ2-P(lXQng_J zNRCK)So%NSj6C-mP?soMeJr#5oKA%&5Gm`dK`?CWT4QhSrJSu*&DQMJPmGBC@nAd# zF4xfxDtmt_nu4m-P(HCxJG-+-;Q8>RJ6r?-61T`Qh@qv?Q5J>1*v8kFmXV!|PctjZ8sI zya!xI6TAfinbrD)h)U!K-x3kDCp3Z=66flhx>vvcW21GJk*ztyr(Fx;U#L*4U`?Xm z>%`Ka47|DnjqHhvd`@4^sF`KSk?hBVWI-OzY%gl@4?QK^nFBCyYz)(lvsvWy(t8LztzZacZz)b zm=~;jGv1BFSnDM_GSa?W?7iv$ZQUqWcBH(KYi}DOCYD{DALQS!S}?}d;~no~LRwE3 zJ2sAyQ_<-G^ReV!O1)LX8NLiBx!$wMKn@}|-on3$+1g9+nY{y%4s~XK?g=B{8SBjT z)LuC>BN2R`vBou5K7kWt=n5k_5uPk&6hHPEp=9*v;=RwnDx#a6R6K<9!?Ec7%Nljh z{(3E}Ya*=;B!bY!97v##=`e^PS4+xkkNsWl2cHWG9DqK91hk57G>--M<`bTswyP$|;=(`ysL? zqh$_gSlu+rHVt>hC=H$u9t4&-9dLMUE&YB*Oe?<5*y$~UqZfMK6$Du;MT6l z=hlsnTIBvemmW;%1}V5Mntd0@L{F*}eD#Shv(`nlVI~ph#WI31XiMc>R$Cmgf7oL^ zlzq}Tgfo13drz`R?lsC@T=4aPp@kG^Ry=WSJflkyl&XNAHQCUmSD%O!V zo^Xo9l2@!_FV+zhtIh^{Y~O7j;mQD(4N4t*S00#dP6DE%v-KM7@p>(&(IeYcWHd$) zqL@-b%jIimpt2NE!<9Uc4*a0+L(J=~dY!=>mphUZ!9*`5$(|nM+3&orJi@27@noJG zCwI)dbHUE!FN~z6y=Yffm#gg15&AU@^bwnR(#Gnu=xr|^)KN0WI->T|D+YU4{?5*g zM+?WZ)d_e3Df)L~VVCifpeFV?@6e5>8XcN2ovAc$~ctXh+ zOJqaSUY%G$uNnWYPw8)JXXedbN8V8_f>DF)|(J?a~`ps>x@Z3 zBIN+BK2-=d;7P>MoLmyLGxv&*)$^ebn)MuuX+-7p#Am(>WqQ_37qJko%KWEjVAg>U zvG6uVnnkVNdotTP$IjhQ)T8L3N~Co8`J&6mjbpk3$nLkz>R3%NrkmxAx+~2^Q+d80 zyQk(BQwsS<8%h2>+6gP+zS||$qxk}!Cy&IEilTgu4>1dOfb={Q5jh>#3e>9UTg?`+ z>G&rI(xVaiwiy;QB}WcMNjYz#so5*efDV0Sc~$T2sr+5jP(O?L)a$PY_c>TuJvarm zeZNR~A$$6p#KU7*r&+66Vjm?P>EAO<^jmuz_T=>Md1Wwwe#o22EB9pm-OMES^Exnz zSZS^uRO)+Edz@Y^^W<%cqAOXxF_<`E2dPKAKO?QTDmw9`~+8J((Ch z!X4eo5tYUq%DeupXSId<^SpLna(ySXa%iLHo+XUBB|AmGMAlKZM8?$d+qhFY#!|>g z_U8NfqEDTF+T@h^AM@OF{tksQlhCwbCbo_HzM)0?IE+q93jUD@RP>ykW~5T7P%zIuJL z=S^?Era7ed>(nx*NdNk^NH5iIsuscf=_KMW>zZUwFW?9$*w-pVux+)nH^PXM_;LLA z&D1Khs`HNvhqDe+P~kLcD}vo>IDeJO<&gEI8`JlP4#&1aoxc4sP-y(o3p?8-I zv9Ao16Fg}&(K%pcGiwWns!dl5SaCaRyt8+Ju5N!$Z}^(8Z*M6~pXBV)_m4{zd1{U6 z_Ro4uTbrmHBH0UZqvwKNhwB)}cX!qK@?>&U@vfotnc@EEbLk{g>dzo7ZjawnH2Eyp zOfFy}`Jj7EdM}8bUZwk0y`q{MtIvK8vs)3J6AWV=%;kFQ*iY+g7}v^t-{td0^ZI_q zJ}wizEW91Ju5ZGo@Ry7`$|-9g?%iW0^Uv-7>V!#s;$mg!$N3O@j_pwtf6eKC+hoj6 z9%W@5W@dEutM;jT-qtnmDF}F6OHxap)++nuVxzT6g~!(Uq}DTHKGf2e?BB~uFA>{! zS1fyX>958L>Qk$XyzF5=iefM}Ct#uglsQn^hcom|FFW57(Ztd#ovU(*ZdokE~wE zpZ${cN?=LIh?jS2qR(2Ri-f8s#nY%?>A%Jwu}van_Vg&}>afRi*4HU_ILprE>KM#0 zaNkO>G5+V2*Y*3uTwgfeFAlFG4z2N;6$bJFGZnqFI;f&j$^DR)@K2-l_Sro0_Rtd~ z(htD5(N>e;Q89~9`_?hy?Rc&;w)n?3x|ZgsL~x^?m1Js=@yuOv?bEuVIYm0!F#f@h z7;29#IkVCYPsfnGGJb3rN_$-)QMJ>r2Qh=End2q7j|`dTuCZ!P=E%w)lJIO2b}iR4 z3TSbbL+S?^bzgJL9WEaaoa=G$g3BVKYP*Hz9r^HZKb`cwOlcmd7i)zG5cv3_Jp)b=~B zClauuf$zwnIYBPDGjA8GxxjWnLv{dN1~f3$C6tL(V7@@Dqr`TQ2FHB$Os5F#(~d(ouU z%T`Qr1nZhy#b-SRANS-)q4$9;wvV=8#wJ(&se0DX>?L)_y_p=*i|7=DG2Udp%9`Jn z9c1;r>^7@4*i_I3scR>_d zyrHJ&HmGw7O?pe|YmXjtg;Wb%PyCK$eiK^m5j`3z?e0esvNfMcy}RJ(EPm=w^GfaK zO_qhqFWYGB2%GH+3sr>!;C4SB_!Di+XRB&-^zmcJG5<0G5XXGBH4=KhcwcfdW-NNi8xnI;w~QU-86V8#en_$= ztDjlrs81{~G**R&#lLP8?X9oxF7z}Z(|(WGOqSS-xezvNR@cXiP(Mb; zhHak>gX!>}3LJ#JR^ggeqKd(povPm4DC+~CK+1fOk z=<_+oX60#ojtb5HVDSrl9{WiLKpiDBM)H;29@7)qTi?5>j!N<@dJD#2YRf&z?^_*o zyKZssG9KqSMl$NFTXFC{mPH)L?tIG=C`;{+4F5-yV7RC~WYHj;dR`ei}3Ch;IZF{rWr^I-acPp6weou7C&Z5>WHRR(|jE zMZ8e2HP2mV*YJPX3aGb^>$cBU)+g3nO|SD9IJe&(Z_UJ`nPa*<&QV7Cw`{FX7UTkl zkpoZC;F>+7gk#o-8ZI4+QnkE?9X@bfm)Xh&**xD_9tpqRA%)U z?AnhQ^Gu2UQ>5npX3s|?DsUnIxsvgopKDGdGp?@HPCg||;L}vz?nX1mz_VgEHOCm+ z)-$a$%Uf;iK8rHDGm3;_`}3J;J)+r+^g&mjGtTqM_3mqT{~pd3j6d_0gFVWUy|{|2 z%&KSDNA{kFQ#jcAVYM(0$ZXUmljmJ@Kf^3&zSTW7myt4?0GKIPU^bD5w0 zfQ;mIA1{CLJKpnI`;;-BEGOn}=?*`P^UHSqKep>1H%hqo-sQbga<85D3$6Fs$egna zrBB-5%=@T)1ATnTos5CpK5c*V`zKRcxZ_d#|DsWx(|o15mo1~h9b9mozahU_JJIp zw%?z%f1fP!iF6*fzaO_#5Re0uC=dSy;o%F~Jn#6yLMwYr(BMC|&(-Zk8>O!Or#92s zB5`bwYtR`KpereTw0z3B$l*IPohrB34oHaoK<)XZEj?W3i`5(-F;)&Wz5E|6KBC3Q z2mEMdaD$G}f)ucW7D3Y`;NZ|28tGiw<8%$eBmnfhsfrN_pD0J{36}w**H$Tc`q;ko^DMXu^Kg z%Dwg-_N<1$9jy3iqjnD)XVTdsvfWK&@od4J$4v`J01Mb&ovsph)yXpRd?67(v8y@- z#Hre)W;T*;cVG3AdD~k_B|$w#tp0Ty!FP%?0G>_NY=a~8aj2? zj%O*UWqY)Aw#ZEnTb~`GaSe>l6#YR@-ltbE?v8^tayNdC=U0(+!87~ZlP|KPgRIF; z9kskyd0rotKi0z=(>`ybrGJd>>NKO53u>}gA{p9W+C2~2n8$h_b{61)dVIS(1v=wd zr;oQc)|x`>2YXDu4_Ccx%i4R%i363(^V{cb3^Q)~hCJEg3E=KJ!n1hG05f0(oM{)C z!{3;h&+ndfd2THlVf219EOk!?V+{!|1OuuD*_&U^4{1w%Mq|gTyz9)L(U02$^2WB& z5Y{RZN+a^xe#fW1?|_j=7a!=o3^b9B;3iiDKh-hyUcdgmjs@G<4^gIdr4|?K%0A~B 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¼(UUTiLbMj}WQOqqTve1k5di}|}0RA-e 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 diff --git a/sample/dashboards/ibm-ace-grafana-dashboard.json b/sample/dashboards/ibm-ace-grafana-dashboard.json deleted file mode 100644 index bac6945..0000000 --- a/sample/dashboards/ibm-ace-grafana-dashboard.json +++ /dev/null @@ -1,2305 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.2.0" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "5.0.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "5.0.0" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "description": "", - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "iteration": 1539896079460, - "links": [], - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 27, - "panels": [], - "title": "Msgflow Invocations", - "type": "row" - }, - { - "aliasColors": { - "acedemois.Echo": "#f2c96d", - "demo2is.Echo": "#7eb26d" - }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 10, - "x": 0, - "y": 1 - }, - "id": 6, - "interval": "", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatDirection": "v", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(delta(ibmace_msgflow_messages_total{server=~'$server', application=~'$application', msgflow=~'$msgflow'}[5m])) by (server, application, msgflow)", - "format": "time_series", - "hide": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{ msgflow }}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Messages", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transparent": false, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 7, - "x": 10, - "y": 1 - }, - "id": 23, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "Total Msgflow Invocations", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "link": false, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "short" - }, - { - "alias": "Server", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflow_messages_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "title": "Msgflow Invocations by Server", - "transform": "timeseries_aggregations", - "type": "table" - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 7, - "x": 17, - "y": 1 - }, - "id": 24, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "Total Msgflow Errors", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "link": false, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "short" - }, - { - "alias": "Server", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflow_errors_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "title": "Msgflow Errors by Server", - "transform": "timeseries_aggregations", - "type": "table" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 10 - }, - "id": 29, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 11 - }, - "id": 12, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(increase(ibmace_msgflow_cpu_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}[5m])) by (server)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Msgflow CPU Time by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "CPU Time", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 11 - }, - "id": 18, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "avg(ibmace_msgflow_cpu_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}/ibmace_msgflow_messages_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Average Msgflow CPU Time by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "CPU Time", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 11 - }, - "id": 16, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "CPU Time", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "ms" - }, - { - "alias": "Server", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflow_cpu_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "title": "Total Msgflow CPU Time by Server", - "transform": "timeseries_aggregations", - "type": "table" - } - ], - "title": "Msgflow CPU Time", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 11 - }, - "id": 31, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 21 - }, - "id": 19, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(increase(ibmace_msgflow_elapsed_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}[5m])) by (server)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Msgflow Elapsed Time by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "Elapsed Time", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 21 - }, - "id": 20, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "avg(ibmace_msgflow_elapsed_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}/ibmace_msgflow_messages_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Average Msgflow Elapsed Time by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "Elapsed Time", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 21 - }, - "id": 21, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "Server", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Elapsed Time", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "ms" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflow_elapsed_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "title": "Total Msgflow Elapsed Time by Server", - "transform": "timeseries_aggregations", - "type": "table" - } - ], - "title": "Msgflow Elapsed Time", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 12 - }, - "id": 33, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 31 - }, - "id": 25, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(increase(ibmace_msgflow_messages_bytes_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}[5m])) by (server)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Msgflow Bytes Processed by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "bytes", - "label": "Bytes Processed", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 31 - }, - "id": 35, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "avg(ibmace_msgflow_messages_bytes_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}/ibmace_msgflow_messages_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Average Msgflow Bytes Processed by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "bytes", - "label": "Bytes Processed", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 31 - }, - "id": 37, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "Server", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Bytes", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflow_messages_bytes_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "title": "Total Msgflow Bytes Processed by Server", - "transform": "timeseries_aggregations", - "type": "table" - } - ], - "title": "Msgflow Bytes", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 13 - }, - "id": 39, - "panels": [ - { - "aliasColors": { - "acedemois.Echo": "#f2c96d", - "demo2is.Echo": "#7eb26d" - }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 10, - "x": 0, - "y": 41 - }, - "id": 40, - "interval": "", - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeatDirection": "v", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(delta(ibmace_msgflownode_messages_total{server=~'$server', application=~'$application', msgflow=~'$msgflow'}[5m])) by (server, application, msgflow, msgflownode)", - "format": "time_series", - "hide": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{ msgflow }}.{{msgflownode}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Messages", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "transparent": false, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 7, - "x": 10, - "y": 41 - }, - "id": 42, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "Total Msgflownode Invocations", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "link": false, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "short" - }, - { - "alias": "Msgflownode", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflownode_messages_total{server=~'$server', application=~'$application', msgflow=~'$msgflow'}) by (server, application, msgflow, msgflownode)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{msgflow}}.{{msgflownode}}", - "refId": "A" - } - ], - "title": "Invocations by Msgflownodes", - "transform": "timeseries_aggregations", - "type": "table" - } - ], - "title": "Msgflownode Invocations", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 44, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 51 - }, - "id": 45, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(increase(ibmace_msgflownode_cpu_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}[5m])) by (server, application, msgflow, msgflownode)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{msgflow}}.{{msgflownode}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Msgflownode CPU Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "CPU Time", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 51 - }, - "id": 46, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "avg(ibmace_msgflownode_cpu_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}/ibmace_msgflownode_messages_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server, application, msgflow, msgflownode)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{msgflow}}.{{msgflownode}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Average Msgflownode CPU Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "CPU ms", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 51 - }, - "id": 47, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "Msgflownode", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "CPU Time", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "ms" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflownode_cpu_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server,application,msgflow,msgflownode)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{msgflow}}.{{msgflownode}}", - "refId": "A" - } - ], - "title": "CPU Time by Msgflownode", - "transform": "timeseries_aggregations", - "type": "table" - } - ], - "title": "Msgflownode CPU Time", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 15 - }, - "id": 49, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 0, - "y": 61 - }, - "id": 50, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(increase(ibmace_msgflownode_elapsed_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}[5m])) by (server, application, msgflow, msgflownode)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{msgflow}}.{{msgflownode}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Msgflownode Elapsed Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "Elapsed Time", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 9, - "x": 9, - "y": 61 - }, - "id": 51, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "avg(ibmace_msgflownode_elapsed_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}/ibmace_msgflownode_messages_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server, application, msgflow, msgflownode)", - "format": "time_series", - "instant": false, - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{msgflow}}.{{msgflownode}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Average Msgflownode Elapsed Time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": "Elapsed ms", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "columns": [ - { - "text": "Current", - "value": "current" - } - ], - "datasource": "prometheus", - "fontSize": "100%", - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 61 - }, - "id": 52, - "links": [], - "pageSize": null, - "scroll": true, - "showHeader": true, - "sort": { - "col": 1, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "date" - }, - { - "alias": "Msgflownode", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Metric", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Elapsed Time", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "Current", - "thresholds": [], - "type": "number", - "unit": "ms" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "decimals": 2, - "pattern": "/.*/", - "thresholds": [], - "type": "number", - "unit": "short" - } - ], - "targets": [ - { - "expr": "sum(ibmace_msgflownode_elapsed_time_seconds_total{server=~'$server',application=~'$application',msgflow=~'$msgflow'}) by (server,application,msgflow,msgflownode)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}.{{msgflow}}.{{msgflownode}}", - "refId": "A" - } - ], - "title": "Elapsed Time by Msgflownode", - "transform": "timeseries_aggregations", - "type": "table" - } - ], - "title": "Msgflownode Elapsed Time", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 16 - }, - "id": 54, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 8, - "x": 0, - "y": 62 - }, - "id": 59, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(ibmace_jvm_global_gcs_total{server=~'$server'}) by (server)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Global GCs by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 8, - "x": 8, - "y": 62 - }, - "id": 56, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(ibmace_jvm_summary_gcs_total{server=~'$server'}) by (server)", - "format": "time_series", - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Total Scavenger GCs by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 8, - "x": 16, - "y": 62 - }, - "id": 57, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(ibmace_jvm_summary_used_memory_bytes{server=~'$server'}) by (server)", - "format": "time_series", - "interval": "300s", - "intervalFactor": 1, - "legendFormat": "{{server}}.heap", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "JVM Heap Used by Server", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "title": "JVM Resource", - "type": "row" - } - ], - "refresh": false, - "schemaVersion": 16, - "style": "dark", - "tags": [ - "App Connect Enterprise", - "ACE" - ], - "templating": { - "list": [ - { - "allValue": ".*", - "current": {}, - "datasource": "prometheus", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "server", - "options": [], - "query": "label_values(ibmace_msgflow_messages_total, server)", - "refresh": 1, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": {}, - "datasource": "prometheus", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "application", - "options": [], - "query": "label_values(ibmace_msgflow_messages_total, application)", - "refresh": 1, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": ".*", - "current": {}, - "datasource": "prometheus", - "hide": 0, - "includeAll": true, - "label": null, - "multi": false, - "name": "msgflow", - "options": [], - "query": "label_values(ibmace_msgflow_messages_total, msgflow)", - "refresh": 1, - "regex": "", - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "utc", - "title": "IBM App Connect Enterprise", - "uid": "2GhmoPJik", - "version": 36 -} diff --git a/sample/dashboards/ibm-ace-kibana5-dashboard.json b/sample/dashboards/ibm-ace-kibana5-dashboard.json deleted file mode 100644 index c0ae6b9..0000000 --- a/sample/dashboards/ibm-ace-kibana5-dashboard.json +++ /dev/null @@ -1,132 +0,0 @@ -[ - { - "_id": "2ec556f0-d14a-11e8-a912-33b3894d5781", - "_type": "dashboard", - "_source": { - "title": "App-Connect-Enterprise", - "hits": 0, - "description": "", - "panelsJSON": "[{\"col\":7,\"id\":\"fedd8120-d148-11e8-a912-33b3894d5781\",\"panelIndex\":1,\"row\":6,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"ff69d810-d147-11e8-a912-33b3894d5781\",\"panelIndex\":3,\"row\":3,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":7,\"id\":\"4b05c540-d148-11e8-a912-33b3894d5781\",\"panelIndex\":4,\"row\":3,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"id\":\"c1db8600-d148-11e8-a912-33b3894d5781\",\"panelIndex\":5,\"row\":9,\"size_x\":12,\"size_y\":4,\"type\":\"visualization\"},{\"col\":1,\"id\":\"94b593d0-d24e-11e8-a999-556a245d56b8\",\"panelIndex\":7,\"row\":6,\"size_x\":6,\"size_y\":3,\"type\":\"visualization\"},{\"col\":1,\"columns\":[\"message\",\"ibm_serverName\",\"type\",\"loglevel\"],\"id\":\"9a320c80-d145-11e8-a912-33b3894d5781\",\"panelIndex\":8,\"row\":13,\"size_x\":12,\"size_y\":18,\"sort\":[\"@timestamp\",\"asc\"],\"type\":\"search\"},{\"size_x\":12,\"size_y\":2,\"panelIndex\":9,\"type\":\"visualization\",\"id\":\"395e6600-d312-11e8-849b-6df49d41b7f0\",\"col\":1,\"row\":1}]", - "optionsJSON": "{\"darkTheme\":false}", - "uiStateJSON": "{\"P-1\":{\"vis\":{\"colors\":{\"ERROR\":\"#BF1B00\",\"FATAL\":\"#962D82\",\"INFO\":\"#629E51\",\"WARN\":\"#E0752D\"}}},\"P-3\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-4\":{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}},\"P-7\":{\"vis\":{\"colors\":{\"Start Resource\":\"#64B0C8\",\"Start Server\":\"#629E51\",\"Stop Resource\":\"#E0752D\",\"Stop Server\":\"#BF1B00\"}}}}", - "version": 1, - "timeRestore": false, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}}}],\"highlightAll\":true,\"version\":true}" - } - } - }, - { - "_id": "9a320c80-d145-11e8-a912-33b3894d5781", - "_type": "search", - "_source": { - "title": "App-Connect-Enterprise", - "description": "", - "hits": 0, - "columns": [ - "message", - "ibm_serverName", - "type", - "loglevel" - ], - "sort": [ - "@timestamp", - "asc" - ], - "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"index\":\"logstash-*\",\"highlightAll\":true,\"version\":true,\"query\":{\"query_string\":{\"query\":\"ibm_product:\\\"IBM App Connect Enterprise\\\"\",\"analyze_wildcard\":true}},\"filter\":[]}" - } - } - }, - { - "_id": "ff69d810-d147-11e8-a912-33b3894d5781", - "_type": "visualization", - "_source": { - "title": "App-Connect-Enterprise-Top5-Hosts-Generating-Log-Events", - "visState": "{\"title\":\"App-Connect-Enterprise-Top5-Hosts-Generating-Log-Events\",\"type\":\"table\",\"params\":{\"perPage\":5,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"host.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", - "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", - "description": "", - "savedSearchId": "9a320c80-d145-11e8-a912-33b3894d5781", - "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[]}" - } - } - }, - { - "_id": "c1db8600-d148-11e8-a912-33b3894d5781", - "_type": "visualization", - "_source": { - "title": "App-Connect-Enterprise-Top-5-Log-Events", - "visState": "{\"title\":\"App-Connect-Enterprise-Top-5-Log-Events\",\"type\":\"histogram\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per 5 minutes\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"ibm_messageId.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", - "uiStateJSON": "{}", - "description": "", - "savedSearchId": "9a320c80-d145-11e8-a912-33b3894d5781", - "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[]}" - } - } - }, - { - "_id": "94b593d0-d24e-11e8-a999-556a245d56b8", - "_type": "visualization", - "_source": { - "title": "App-Connect-Enterprise-Start-Stop-Events", - "visState": "{\"title\":\"App-Connect-Enterprise-Start-Stop-Events\",\"type\":\"histogram\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per 5 minutes\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"filters\",\"schema\":\"group\",\"params\":{\"filters\":[{\"input\":{\"query\":{\"query_string\":{\"query\":\"ibm_messageId:\\\"1989I\\\"\",\"analyze_wildcard\":true}}},\"label\":\"Stop Server\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"ibm_messageId:\\\"1990I\\\"\",\"analyze_wildcard\":true}}},\"label\":\"Start Server\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"ibm_messageId:\\\"2155I\\\" AND message:\\\"Stop\\\"\",\"analyze_wildcard\":true}}},\"label\":\"Stop Resource\"},{\"input\":{\"query\":{\"query_string\":{\"query\":\"ibm_messageId:\\\"2155I\\\" AND message:\\\"Start\\\"\",\"analyze_wildcard\":true}}},\"label\":\"Start Resource\"}]}}],\"listeners\":{}}", - "uiStateJSON": "{}", - "description": "", - "savedSearchId": "9a320c80-d145-11e8-a912-33b3894d5781", - "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[]}" - } - } - }, - { - "_id": "fedd8120-d148-11e8-a912-33b3894d5781", - "_type": "visualization", - "_source": { - "title": "App-Connect-Enterprise-Log-Events-By-Level", - "visState": "{\"title\":\"App-Connect-Enterprise-Log-Events-By-Level\",\"type\":\"histogram\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per 5 minutes\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"loglevel.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}", - "uiStateJSON": "{}", - "description": "", - "savedSearchId": "9a320c80-d145-11e8-a912-33b3894d5781", - "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[]}" - } - } - }, - { - "_id": "4b05c540-d148-11e8-a912-33b3894d5781", - "_type": "visualization", - "_source": { - "title": "App-Connect-Enterprise-Top5-Servers-Generating-Error-Log-Events", - "visState": "{\"title\":\"App-Connect-Enterprise-Top5-Servers-Generating-Error-Log-Events\",\"type\":\"table\",\"params\":{\"perPage\":5,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"ibm_serverName.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"customLabel\":\"\"}}],\"listeners\":{}}", - "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", - "description": "", - "savedSearchId": "9a320c80-d145-11e8-a912-33b3894d5781", - "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[{\"meta\":{\"index\":\"logstash-*\",\"negate\":false,\"disabled\":false,\"alias\":null,\"type\":\"phrase\",\"key\":\"loglevel.keyword\",\"value\":\"ERROR\"},\"query\":{\"match\":{\"loglevel.keyword\":{\"query\":\"ERROR\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}]}" - } - } - }, - { - "_id": "395e6600-d312-11e8-849b-6df49d41b7f0", - "_type": "visualization", - "_source": { - "title": "App-Connect-Enterprise-Log-Events", - "visState": "{\"title\":\"App-Connect-Enterprise-Log-Events\",\"type\":\"histogram\",\"params\":{\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"@timestamp per 3 hours\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"valueAxis\":\"ValueAxis-1\",\"drawLinesBetweenPoints\":true,\"showCircles\":true}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", - "uiStateJSON": "{}", - "description": "", - "savedSearchId": "9a320c80-d145-11e8-a912-33b3894d5781", - "version": 1, - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{\"filter\":[]}" - } - } - } -] diff --git a/sample/initial-config/keystore/extra-key.crt b/sample/initial-config/keystore/extra-key.crt deleted file mode 100644 index 11a1c47..0000000 --- a/sample/initial-config/keystore/extra-key.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrzCCApcCCQC7U68DCojUcDANBgkqhkiG9w0BAQsFADCBlzELMAkGA1UEBhMC -R0IxEjAQBgNVBAgMCUhhbXBzaGlyZTEQMA4GA1UEBwwHSHVyc2xleTEUMBIGA1UE -CgwLSUJNIFVLIEx0ZC4xEDAOBgNVBAsMB0FDRSBJQ1AxFjAUBgNVBAMMDVRlc3Qg -Um9vdCBDQTIxIjAgBgkqhkiG9w0BCQEWE3Rlc3RlcjJAZXhhbXBsZS5jb20wHhcN -MTgxMDMwMTcwNzU4WhcNMzgxMDMwMTcwNzU4WjCBmjELMAkGA1UEBhMCR0IxEjAQ -BgNVBAgMCUhhbXBzaGlyZTEQMA4GA1UEBwwHSHVyc2xleTEUMBIGA1UECgwLSUJN -IFVLIEx0ZC4xDDAKBgNVBAsMA0FDRTEdMBsGA1UEAwwUbWFjaGluZTIuZXhhbXBs -ZS5jb20xIjAgBgkqhkiG9w0BCQEWE3Rlc3RlcjJAZXhhbXBsZS5jb20wggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6d4WygE2z2uWfbhNFDdp/pBzGmB6Z -KiqAdGvMsRriV8JQ4M2/2rskiWXhXe5jKRuedy1TZuyW9CZwmEg0TIIn2OA2JShP -xM+DIWqkg8ZyPNf3vxtchbTP2BCfy6TJoan4svTRDTKlFygtzuEjIFFH5etZKrnI -Olag1cv0+FiuwbU0Y/90BO3cce6KqHNNKe41nltljzg9RGeyg8iv8KVoM447a+yb -4uRLQWzPmFwS2mhTcEn54ScyfqvgL8s0wpRmjL5rAiwjngUNN5Cir8R21qGbDRq9 -S2S02job3Ifr35gzxt+OpfIhcGaiN5f8c+a6pHzKbVzEXVR9xnH9BptjAgMBAAEw -DQYJKoZIhvcNAQELBQADggEBAMT90o79LMS0Zt0ccE8SfSewtX+EgZQoDNvfClzt -T6fMNtSLoWNVj6R6WG/t/BzaMXBpXu5JOKbAXVcfj3kyHgOLvt2Mfpe3gb+zKu6g -HERP7ecTc1+SwlzQVxQH0fOUGcfX/CjGHxrzJGc0iS91UJpsruFY1Hjy98PTqEG4 -G1ANrZcUYs7RKIjKB5TUx70nJUkIfYjBVSRV/TrUhz+Fc0JkmJhljMa/i8X8LnEx -F5WmurPu96pEExtl4w9CSj/LnfIZ0IEqV39eHvMCf8m5TV3z91zlSvLLRnvajBvo -XHfhIivt5uNLqjL/cCA8shNXl3nctSSZcp1CfJABocKeF5I= ------END CERTIFICATE----- diff --git a/sample/initial-config/keystore/extra-key.key b/sample/initial-config/keystore/extra-key.key deleted file mode 100644 index 4139337..0000000 --- a/sample/initial-config/keystore/extra-key.key +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQI259pfAK40VwCAggA -MB0GCWCGSAFlAwQBAgQQ1N3YBP6dS3qRIBmsord4vgSCBNApgHCqfecEvW4UYwIm -IPLfFVMeKLSIsrih3auMumVoIAPIOGTxMbQoAayET2ZLwuX2hW4vFcT/xGuSASXN -bh7t4PDSTNllzV6Epj5Qk2qefOPwmG2o75LDGIbF+lfkZlKaMNmmxyWm5wceY4+W -wDIKFoQMURLH2/PaigjFmQem2XPacH6dnnmxITWjvq/cLuFGRgKi4M6Isj9cIBwM -PU/f/GGntF4lMJHp0bw7LNj80DSuCYi3iHnS+mqw1TXc4u2xTcWOmkefvHu3/vEq -htfSjOCeF3uy9sfLmO+AAzOaTxDKee1aI2/q9W49Y4cjBO5fcQ3kXMWUoFr2cz7v -OUMsySNj1PG0bOFFxxYKffu+LRvg7rqYCEB5LLUpt6doLUA1BSmytBXfsIXu3THK -ERY+dc26yzPuLE9BKs2B4nAzms0kqxyLc+NllOB1DERsbH4vJzIQ85xrHnHJTwoA -0vuA2dp4wFYtNlSASur73OTSKBBSVILywOjQctIoowsoHx6m1RDQL9CT5nngfI02 -J8lmxOv10UclLFKaist5Sc+NGAmcHPjH1IdSBIiGBgMnrumLCDZtgLfx0D5XBbKq -eJ+u8z6qafCwvQ9LE4ItT4ubK9qnTJWdrO0lb6IVcJYbDlw/7wpb5vG9DJjI4pd6 -rVuy4zjuuVY2wCPPvauNKlTeZ4J51qM35r4B93vCkHTq050dW4/+JR+iaoK99M0/ -WpS9JN3DZq0Dj92aRKPcjHQ1cUYdYCpqz1D5doliS6vWFHNnuDbxVWxe28qLyKm+ -xa5gb5k//OVla98YaLXwuYVsUaJvQVdikwx+Ri5B47mNsMydtgOAUZk+u7hK2HcX -gYB7Ub2HzfzhVcq8sK+nVrUkW6E89Op0mpexR/pIaqAkPNbtSNt9z1Th9M7Z/ugL -q/LQH/UceP1vvBops3GAJB43XbpPqnah4I6A2omrnOGJTIitFQl0unLeVewvJwFz -YzmdyQzavHWcHWRMHDt64HZaZ/nkCNr8uYwyxoOM7hwdaI3ioQuWtbKRH4Hku0rE -G/bvuTtz7Rxsy+pJ3wpJFsgpN9ga7f/shx8bU/A+S/gAel2wtyhoshUVmY9pjtFS -Cuq6KXxpa9+AF0Y7nAcSFUdmGmU25Rtz6Patq70YPk5sq5YmfM1yTrL/djY7itfi -wBZ+H2PSymOMLl9qIZlhuF95dtO4pI7TIwFDFO/UdH06EmplzdCNR9YwIZ7ho5Sm -cJ/gR2TIpSLGH3wU5XC1UnVzcWibiuL70R918ZPRLnbLN9/3BwnljL4CN73D3voS -ZZW0odChcOKcJRgBEwMcCRQpmBPpHoxpQiWEgp3/hF01lgWyEINkXOdy3sU8lDXL -rctd2tz8XsfvR6qIf2yVIoP68XdgvgWUPGBJo5LKCbuRWno+gz/Empb3I5FlkisC -/vrxcd19BM6Ik4b1ql4xv+WpkCL26W0Vdy8dSVvi6pEpABWdMjUbJJ5Jq8JwTxFC -jF6dOTG7dWiIhwJjInSvKGUXRWsFpKtKph8XWG+zZxr3Gt3ugUh0OsTfgl9hQalv -+wP+0IPxeZI9m5YJhbxehhqcoxm8oRZUr8lmNAgCuZ1B6WEm14HFhbkHeidOJoR0 -7q0nN48QJmB5qZU4ewh1GxnBSA== ------END ENCRYPTED PRIVATE KEY----- diff --git a/sample/initial-config/keystore/extra-key.pass b/sample/initial-config/keystore/extra-key.pass deleted file mode 100644 index 21c3f34..0000000 --- a/sample/initial-config/keystore/extra-key.pass +++ /dev/null @@ -1 +0,0 @@ -passrods diff --git a/sample/initial-config/keystore/mykey.crt b/sample/initial-config/keystore/mykey.crt deleted file mode 100644 index bc374d9..0000000 --- a/sample/initial-config/keystore/mykey.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrDCCApQCCQDNRiR1q5bTpTANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC -R0IxEjAQBgNVBAgMCUhhbXBzaGlyZTEQMA4GA1UEBwwHSHVyc2xleTEUMBIGA1UE -CgwLSUJNIFVLIEx0ZC4xEDAOBgNVBAsMB0FDRSBJQ1AxFTATBgNVBAMMDFRlc3Qg -Um9vdCBDQTEhMB8GCSqGSIb3DQEJARYSdGVzdGVyQGV4YW1wbGUuY29tMB4XDTE4 -MTAzMDE2NTMxM1oXDTM4MTAzMDE2NTMxM1owgZkxCzAJBgNVBAYTAkdCMRIwEAYD -VQQIDAlIYW1wc2hpcmUxEDAOBgNVBAcMB0h1cnNsZXkxFDASBgNVBAoMC0lCTSBV -SyBMdGQuMQwwCgYDVQQLDANBQ0UxHTAbBgNVBAMMFG1hY2hpbmUxLmV4YW1wbGUu -Y29tMSEwHwYJKoZIhvcNAQkBFhJ0ZXN0ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiAcYbiWT11r5abrC/NVPNCXKfLAXZ118t -igpA32UsBAJCwtWeipudbPQphW/mesZR8Aw1l4TqbDg5au59WF9PaLAf//Jeu/I6 -E9uE/8a+dOXZYHTNPs5E28Vnq89y/KdrB2+Woc71tadwOdsTYLBn4LbAVPK2/nhh -aoRWI//MIo+YhjDiftbiNh6t5B7JvxchQPKgCUDkHM3FXTsbFBZa8APYMnvPnCRs -shcCFQAhddE7Hj3E/DzNGo+J9B0/hrqSUY/InO3eHtv4m07F/XzHvZEcmr/z8Prw -90Npe80DRVU9NVQIl6z+ZfE56vv/l+bgkVhm3XtUEn6WtiYcbqCvAgMBAAEwDQYJ -KoZIhvcNAQELBQADggEBACPuF5la86PJ8+ffgcPhEJId72uEPCUL4PkcDV+8ipOU -Y8WwM/CSfmPVF19lDlQm863uWWkWVyzJhAyfNToQqtO3R+bpRqQKD0+3mwkRmCo/ -csyfRC3LLVWg0XKEaf+nrt1//VOj4yuCRhThMo8JbThV8ROpDq1ePtgQqxMojKNW -XrTSxSkInhAv023Yhq4XchZwwznC4kQVfSNbabgZMkI94C/g+E/h5PjUFRFG8ac/ -EFC/4DpkQUFmCASBpRx/nTYfV1lVQLIwP7Uyv6+PcjmoM2eGmgUTH2kNAZdi8uNB -ElKbk1bfIb9UXywxYXyCHQXmkqYJhPNN+qjTuDs1WDA= ------END CERTIFICATE----- diff --git a/sample/initial-config/keystore/mykey.key b/sample/initial-config/keystore/mykey.key deleted file mode 100644 index e40e6d0..0000000 --- a/sample/initial-config/keystore/mykey.key +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQI0jhb33E2+FACAggA -MB0GCWCGSAFlAwQBAgQQ0xVmu+JzxIO6ZG6JJ5Q2PgSCBNDXPo0GUvARv44TGMaF -DfHu1k/CYV8+5cA2vp2W90qRPfROG1jGgfIb0KMWgPLmSRVJMvdg/rxkBJVMKYzi -Y3VHuO3pdx06uWyG1ay8Ki8KDj2eg/mBidlmcdG1GtKtUGkVePX74CGlIFDStG+U -a/k5E4sfpoDBtXQ39gqF9TMu3LrC8Ze4LvkEyXuvJwqQZlJbdelG4Z2TSBewJiSL -VDM7yJ+iuZZyafzWhRXc3vUMnbP3pQYn39e86VmBks9ibrBa0uwvVZTs1lkGa1TA -Uu037oER28NG5AtmurGlQfSEUxfXCobXOwlCk4Di7kPMy2F3S/RbzvObrr6TM8p/ -X64nVua4lEOTy1ntfmcEoMMgTWpCiSQmU8pPykC1BwjI6CqU+riChkOvJMs9L1c4 -v6anAvawtmBV9gSfeWhPOtZGuVXDlTmo/DUWDFIEMZes2srs3y5Yhb9dhQqqZzQT -1b+bHpXnb0CE7E/sFLck/IKU2f4KfIEqsEuXFCMnK1Q9A3mdN+lW6+qhsFXcIShQ -6a9ESjTijUFilZWWotf8QLyCfk7dIlE6WaL1j8aTIspcqGnNlMh5XMPrlEOWAK1z -+yJRrICfZd5D8bgTEHL2klbULQ+w+FEU80rFap9HsVfuhl+7S3m2wWMXLcSVu0QU -qPyUTGfbGDZwnS8wkCend3eZIiIYgunFTYp0uY0Dk8ruyeEfn1bTESEnb8lvzrhe -Kq4Bdn72F2hqGcTIvNXg0Xl4OEYFVKoV7GC/0QxPmbXcJT/SaNeHhjFQZRcxu1Cc -rFvpBKf+YCF6ru9TiBaScAEMBjFhJOE1LAHVVlrN2BTLXeH2E6nbXBAHlXu5vGGr -iE3ngp7t6OgOLLreUGrkPllH2Gw1aLOSSHjuxgj8NCV4HijPx19ncap40Kut6hcb -O9+GoEokHAXGjYbJ342QY9x8SLnFLLITlL0ZgWLgPlv8fsdWnWvdDdH0jlrK/Uz+ -7kGmWSap1tNUlpGzc96Q9t21GmPas6NRbfupSm7ptM1L+VFyr7yzwKP4GRHK66Oo -FgIdHoFFMw4/8yXGIl2t/ltnkMOm0Usn4AK2XNyAsNYvzwrpTz3By7gBiDOo0lFL -tBDmoDBRICpSjQvjMFRjd/2Akc44kcoLlXp18hyi7oqH09jcB8Fskeu8aIsVPSoH -1V0nMgtNq7WIr9Kq3vGyZuSpGWG08bEczg/PaoAWpgnxPA2VUQgx8N7DLGqm7rOt -QhpWqnKB2CkjlLIdO2cJ2zNqATr9Z8iMQ7xRvvosuOEWqRnu3pQhTsndMh1NxE2s -0nL4npJfNkvRYo/AlCeikxdX1ylz8Pxe/4nxatBfRBxXw2Ol2wjr21oIsYrOF7uy -jR5GFEc43b7NKlSbht4upAlzSooOGme09fIFtwORLnIxxZoRv0uYujaOiIz2COWU -RKxU23lqw90EMDLzg/G9EJFMX2tFIbphJJIDf2SIOgmiIpefHvpi0uRt5ssZ7fco -rvuLveQE72THH53RTpKA6xz4PqZmOf4zG7gWGx76rb8sBbL/GwF07JzcYh5yIq21 -kwozZroxCLJSka72oABx6+HcjGS0am3+CBuSqdpc5P1X1DoCfZvIo+9k5kcGdFRC -i46A38SrjFgEJjKkL2aQ8vXsKQ== ------END ENCRYPTED PRIVATE KEY----- diff --git a/sample/initial-config/keystore/mykey.pass b/sample/initial-config/keystore/mykey.pass deleted file mode 100644 index 21c3f34..0000000 --- a/sample/initial-config/keystore/mykey.pass +++ /dev/null @@ -1 +0,0 @@ -passrods diff --git a/sample/initial-config/mqsc/config.mqsc b/sample/initial-config/mqsc/config.mqsc deleted file mode 100644 index 997f055..0000000 --- a/sample/initial-config/mqsc/config.mqsc +++ /dev/null @@ -1 +0,0 @@ -DEFINE QLOCAL('test') \ No newline at end of file diff --git a/sample/initial-config/odbcini/odbc.ini b/sample/initial-config/odbcini/odbc.ini deleted file mode 100644 index 3befcc7..0000000 --- a/sample/initial-config/odbcini/odbc.ini +++ /dev/null @@ -1,40 +0,0 @@ -;####################################### -;#### List of data sources stanza ###### -;####################################### - -[ODBC Data Sources] -ORACLEDB=DataDirect ODBC Oracle Wire Protocol - -;########################################### -;###### Individual data source stanzas ##### -;########################################### - -;# Oracle stanza -[ORACLEDB] -Driver=/opt/ibm/ace-12/server/ODBC/drivers/lib/UKora95.so -Description=DataDirect ODBC Oracle Wire Protocol -HostName=OracleServerMachineName -PortNumber=1521 -ServiceName=OracleServiceName -CatalogOptions=0 -EnableStaticCursorsForLongData=0 -ApplicationUsingThreads=1 -EnableDescribeParam=1 -OptimizePrepare=1 -WorkArounds=536870912 -ProcedureRetResults=1 -ColumnSizeAsCharacter=1 -LoginTimeout=0 -EnableNcharSupport=0 -;# Uncomment the next setting if you wish to use Oracle TIMESTAMP WITH TIMEZONE columns -;# EnableTimestampwithTimezone=1 - -;########################################## -;###### Mandatory information stanza ###### -;########################################## - -[ODBC] -InstallDir=/opt/ibm/ace-12/server/ODBC/drivers -UseCursorLib=0 -IANAAppCodePage=4 -UNICODE=UTF-8 diff --git a/sample/initial-config/policy/default.policyxml b/sample/initial-config/policy/default.policyxml deleted file mode 100644 index bf4a1f9..0000000 --- a/sample/initial-config/policy/default.policyxml +++ /dev/null @@ -1,29 +0,0 @@ - - - - 7843 - 0.0.0.0 - -1 - 100 - 200 - 1000 - 100 - false - * - false - Content-Type - -1 - GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS - Accept,Accept-Language,Content-Language,Content-Type - !RC4+RSA:HIGH:+MEDIUM:+LOW - false - 300 - 100 - mykey - - /home/aceuser/ace-server/keystore.jks - keystorepwd - /home/aceuser/ace-server/truststore.jks - truststorepwd - - diff --git a/sample/initial-config/policy/policy.descriptor b/sample/initial-config/policy/policy.descriptor deleted file mode 100644 index bdfa6f2..0000000 --- a/sample/initial-config/policy/policy.descriptor +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/sample/initial-config/serverconf/server.conf.yaml b/sample/initial-config/serverconf/server.conf.yaml deleted file mode 100644 index 67e64f0..0000000 --- a/sample/initial-config/serverconf/server.conf.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -ResourceManagers: - JVM: - truststoreType: 'JKS' - truststoreFile: '/home/aceuser/ace-server/truststore.jks' - truststorePass: 'setdbparms::truststore' - -# Defaults: -# Policies: -# HTTPSConnector: 'HTTPS' - -Defaults: - defaultApplication: '' # Name a default application under which independent resources will be placed - policyProject: 'DefaultPolicies' # Name of the Policy project that will be used for unqualified Policy references - Policies: - # Set default policy names, optionally qualified with a policy project as {policy project}:name - HTTPSConnector: 'HTTPS' # Default HTTPS connector policy diff --git a/sample/initial-config/setdbparms/setdbparms.txt b/sample/initial-config/setdbparms/setdbparms.txt deleted file mode 100644 index 39b3c54..0000000 --- a/sample/initial-config/setdbparms/setdbparms.txt +++ /dev/null @@ -1,3 +0,0 @@ -# resource user password -setdbparms::truststore "my username" truststorepwd -mqsisetdbparms -w /home/aceuser/ace-server -n salesforce::SecurityIdentity -u "my username" -p myPassword -c myClientID -s myClientSecret \ No newline at end of file diff --git a/sample/initial-config/truststore/cacert.crt b/sample/initial-config/truststore/cacert.crt deleted file mode 100644 index 697eb58..0000000 --- a/sample/initial-config/truststore/cacert.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDqDCCApACCQC/RYKb+/vOrjANBgkqhkiG9w0BAQsFADCBlTELMAkGA1UEBhMC -R0IxEjAQBgNVBAgMCUhhbXBzaGlyZTEQMA4GA1UEBwwHSHVyc2xleTEUMBIGA1UE -CgwLSUJNIFVLIEx0ZC4xEDAOBgNVBAsMB0FDRSBJQ1AxFTATBgNVBAMMDFRlc3Qg -Um9vdCBDQTEhMB8GCSqGSIb3DQEJARYSdGVzdGVyQGV4YW1wbGUuY29tMB4XDTE4 -MTAzMDE2NTE0NloXDTM4MTAzMDE2NTE0NlowgZUxCzAJBgNVBAYTAkdCMRIwEAYD -VQQIDAlIYW1wc2hpcmUxEDAOBgNVBAcMB0h1cnNsZXkxFDASBgNVBAoMC0lCTSBV -SyBMdGQuMRAwDgYDVQQLDAdBQ0UgSUNQMRUwEwYDVQQDDAxUZXN0IFJvb3QgQ0Ex -ITAfBgkqhkiG9w0BCQEWEnRlc3RlckBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAPbdh9B8KJVou0IisgISqDVFjlAQmpsdcKQi9P1s -9DzGIuDghEwb21v/JW25U4+xJ4CULjVx+moHXLapP4tNpBTE2x8p4qEuOJROA3xr -m99nW6UnOEnM4stjdlt14ihKxiiu1DvGluBsnz3P9BSNmLCLhxcSgPpRUUqstiiK -hca1HxZQUF5EdijhzXA/R/afVNM/TftDBDuIw61rNlxIp/33STPZ0nUWY0lubQ8J -sLpDX73jKx5itpJ2gi99uR1KLrOwTWYEr/mGXFqyZjHkHEiE5+nIPd0q9+1f4Vh4 -H2fTLrpAJkrIAZ6sRNrIaxlEtAHqJkMm36PHzO7JY4/lyaMCAwEAATANBgkqhkiG -9w0BAQsFAAOCAQEAX/0szi4ObvIUAuZwu2Lb7H0rNZZ8r0HsJ4NuaQ11VpeyBsx/ -MSW2JepdA34xM7dfvc9ef7hoC8xHGt/PKUu+pNoum/XuLTnWoulch8rg0IqqPX6S -/xjbi6laAhr0xn3uepp8NBLbzjHRdvAmmhkgUphKrPSGOG0ThrRJQGjk3dW6KEQl -NPT1kA5GaM1/4oxslO6VYACnj+CUwb6RsksEsKjh9CM0UbSr1gvzzqC/xmNIJTpU -T9ADO5QBrAjpmGvnTNkM+iBLUDsKLkREmn+1epdkKx8lnOq7AvPlJpVvwAQU94Dh -+zyKzULDHnExNSikkAKtJHYY7MIaP0/Lyuf2+Q== ------END CERTIFICATE----- diff --git a/sample/initial-config/truststore/extra-cert.crt b/sample/initial-config/truststore/extra-cert.crt deleted file mode 100644 index f4a4749..0000000 --- a/sample/initial-config/truststore/extra-cert.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrDCCApQCCQCNcazfuU39uTANBgkqhkiG9w0BAQsFADCBlzELMAkGA1UEBhMC -R0IxEjAQBgNVBAgMCUhhbXBzaGlyZTEQMA4GA1UEBwwHSHVyc2xleTEUMBIGA1UE -CgwLSUJNIFVLIEx0ZC4xEDAOBgNVBAsMB0FDRSBJQ1AxFjAUBgNVBAMMDVRlc3Qg -Um9vdCBDQTIxIjAgBgkqhkiG9w0BCQEWE3Rlc3RlcjJAZXhhbXBsZS5jb20wHhcN -MTgxMDMwMTcwNzEzWhcNMzgxMDMwMTcwNzEzWjCBlzELMAkGA1UEBhMCR0IxEjAQ -BgNVBAgMCUhhbXBzaGlyZTEQMA4GA1UEBwwHSHVyc2xleTEUMBIGA1UECgwLSUJN -IFVLIEx0ZC4xEDAOBgNVBAsMB0FDRSBJQ1AxFjAUBgNVBAMMDVRlc3QgUm9vdCBD -QTIxIjAgBgkqhkiG9w0BCQEWE3Rlc3RlcjJAZXhhbXBsZS5jb20wggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVUJRklaHFtUTFsfoUa7PoQolOzgKqtP0J -Mg1x9RUWUjVGClz0b19ak4Uk7+U6tczUWfy5MZw9hJp3mI96yJXsvwAkaSlVlCIk -Auz7VvrkHwj9o+4+C8IuFItU/W5ARtj9VLng/cEP6i7S7PbJ33AUL19PgCrK1Zpt -rSioTdZdy5yd2MZPGdaT6R9b+We9F7yF6VK0zB4U5fJOeiNpVPOfa8BIEOzYI2WG -/i7iEMjK92d2Ktb/9ZFyVYMs7TCNWavRAjATz6CFCkBxSa/Ly2JMskD4AFVUuYBI -GDY6q1OhOaHwsVO/nA8pDmTYZrdPjSjzMBbMHhSegOiJewm6whEbAgMBAAEwDQYJ -KoZIhvcNAQELBQADggEBAH4l6dQgMW/mu7YwwVy4ZCgY9VpJ8cSePxV5kS+L6oz6 -vhkKD9TEQ/X7fXSnL0BbgCG/YhzLg+7No0dRHMmEh4typSZjc5KrcV5ExHzWDd0B -V/zGetFTuqtE43I93isPP0b8nPhfcrQcfVIRLN1EvT8engI68pq2PoOz7HPo+YT7 -wp+0vTaUw6q7I5+ZXjEnykoytO7gExXnY/z4al2ey6VVyrGiFjz62Q+tQFNshmgR -F6Y50d+SvM3+RPe/oA8GqJWAhcn6oU7Jn3+snmCMGYH0nylSo8fvz86bbxqGMVJR -zIOCUiypnnyNsutqqUVVHb0E10TDBkSHD4VQCZ7TUKA= ------END CERTIFICATE----- diff --git a/sample/initial-config/webusers/admin-users.txt b/sample/initial-config/webusers/admin-users.txt deleted file mode 100644 index 77dd8fe..0000000 --- a/sample/initial-config/webusers/admin-users.txt +++ /dev/null @@ -1,2 +0,0 @@ -admin1 passwd1 -admin2 passwd2 diff --git a/sample/initial-config/webusers/viewer-users.txt b/sample/initial-config/webusers/viewer-users.txt deleted file mode 100644 index 42b294d..0000000 --- a/sample/initial-config/webusers/viewer-users.txt +++ /dev/null @@ -1,2 +0,0 @@ -viewer1 passwd1 -viewer2 passwd2 diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 0000000..8532151 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,9 @@ +# Samples + +The samples folder contains Dockerfiles that build a sample image containing sample applications. + +* [Update base package sample](updateBase/README.md) - This sample applies updates all the base OS packages. This can be used to resolve CVEs in the base packages +* [iFix sample](applyIfix/README.md) - This sample applies an iFix provided by IBM to an existing image. +* [Bar sample](bars/README.md) - This sample adds some bar files to an existing image. +* [Scripts sample](scripts/README.md) - This sample adds scripts which are run on startup. + diff --git a/samples/applyIfix/Dockerfile b/samples/applyIfix/Dockerfile new file mode 100644 index 0000000..5cce8a3 --- /dev/null +++ b/samples/applyIfix/Dockerfile @@ -0,0 +1,14 @@ +ARG FROMIMAGE=cp.icr.io/cp/appc/ace:12.0.4.0-r1 +ARG IFIX +FROM ${FROMIMAGE} + +ADD ifix/.tar.gz /home/aceuser/ifix + +USER root + +RUN cd /home/aceuser/ifix \ + && ./mqsifixinst.sh /opt/ibm/ace-12 install \ + && cd /home/aceuser \ + && rm -rf /home/aceuser/fix + +USER 1001 diff --git a/samples/applyIfix/README.md b/samples/applyIfix/README.md new file mode 100644 index 0000000..e886a6f --- /dev/null +++ b/samples/applyIfix/README.md @@ -0,0 +1,17 @@ + +# iFix Sample + +## What is in the sample? + +This sample applies an iFix provided by IBM to an existing image. + +## Building the sample + +- First [build the ACE image](../README.md#Building-a-container-image) or obtain one of the shipped images +- Download the appropriate iFix provided by IBM and place it into the fix directory +- Update the dockerfile to reference the iFix i.e. replace \ with the name of the ifix you've downloaded i.e. 12.0.3.0-ACE-LinuxX64-TFIT39515A +- In the `sample/scripts/applyIfix` folder: + + ```bash + docker build -t aceapp --build-arg FROMIMAGE=cp.icr.io/cp/appc/ace:12.0.4.0-r1 --file Dockerfile . + ``` diff --git a/samples/applyIfix/ifix/.gitignore b/samples/applyIfix/ifix/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/samples/applyIfix/ifix/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/samples/bars/CustomerDatabaseV1.bar b/samples/bars/CustomerDatabaseV1.bar new file mode 100644 index 0000000000000000000000000000000000000000..29f07cba156c31eefec8e00ae31741772269a260 GIT binary patch literal 92474 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 eH76eiCaB0vy&a=acUA$xygg; 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 z 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 > /tmp/deploys; done \ + && chmod -R ugo+rwx /home/aceuser/ + +USER 1001 \ No newline at end of file diff --git a/samples/bars/README.md b/samples/bars/README.md new file mode 100644 index 0000000..692ddca --- /dev/null +++ b/samples/bars/README.md @@ -0,0 +1,34 @@ + +# Bars Sample + +## What is in the sample? + +- **bars_aceonly** contains a BAR for sample applications that don't need MQ. These will be copied into the image (at build time) to `/home/aceuser/bars` and compiled. The Integration Server will pick up and deploy this files on start up. These set of BAR files will be copied when building an image with ACE only or ACE & MQ. + +## Building the sample + +First [build the ACE image](../README.md#Building-a-container-image) or obtain one of the shipped images + +In the `sample/bars` folder: + +```bash +docker build -t aceapp --build-arg FROMIMAGE=ace:12.0.4.0-r1 --file Dockerfile . +``` + +## Running the sample + +The sample application is a copy of one of the ACE samples called CustomerDB. This provides a RestAPI which can be queries which will return information about customers. + +To run the application launch the container using a command such as: + +```bash +`docker run -d -p 7600:7600 -p 7800:7800 -e LICENSE=accept aceapp` +``` + +To exercise the flow run a command such as: + +```bash +curl --request GET \ + --url 'http://96fa448ea76e:7800/customerdb/v1/customers?max=REPLACE_THIS_VALUE' \ + --header 'accept: application/json' +``` diff --git a/samples/bars/test.sh b/samples/bars/test.sh new file mode 100755 index 0000000..947e9b8 --- /dev/null +++ b/samples/bars/test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +docker ps | grep -q ace +i=0 + +while ! docker logs ace | grep -q "Integration server has finished initialization"; +do + if [ $i -gt 30 ]; + then + echo "Failed to start - logs:" + docker logs ace + exit 1 + fi + sleep 1 + echo "waited $i secods for startup to complete...continue waiting" + ((i=i+1)) +done +echo "Integration Server started" +echo "Sending test request" +status_code=$(curl --write-out %{http_code} --silent --output /dev/null --request GET --url 'http://localhost:7800/customerdb/v1/customers?max=1' --header 'accept: application/json') +if [[ "$status_code" -ne 200 ]] ; then + echo "IS failed to respond with 200, it responded with $status_code" +fi +echo "Successfuly send test request" diff --git a/samples/scripts/Dockerfile b/samples/scripts/Dockerfile new file mode 100644 index 0000000..d3e6f7b --- /dev/null +++ b/samples/scripts/Dockerfile @@ -0,0 +1,16 @@ +ARG FROMIMAGE=cp.icr.io/cp/appc/ace:12.0.4.0-r1 +FROM ${FROMIMAGE} + +USER root + +# Required for the setdbparms script to run +RUN microdnf update && microdnf install python3 && microdnf clean all \ + && ln -s /usr/bin/python3 /usr/local/bin/python + +COPY server.conf.yaml /home/aceuser/ace-server/ + +RUN mkdir -p /home/aceuser/initial-config/setdbparms/ +COPY ace_config_* /home/aceuser/initial-config/ +RUN chmod -R ugo+rwx /home/aceuser/ + +USER 1001 \ No newline at end of file diff --git a/samples/scripts/README.md b/samples/scripts/README.md new file mode 100644 index 0000000..ca496b1 --- /dev/null +++ b/samples/scripts/README.md @@ -0,0 +1,26 @@ + +# Scripts Sample + +## What is in the sample? + +This sample includes copying in a script which should run before the IntegrationServer starts up. These scripts are used to process a mounted file which contains some credentials. + +## Building the sample + +First [build the ACE image](../README.md#Building-a-container-image) or obtain one of the shipped images + +In the `sample/scripts` folder: + +```bash +docker build -t aceapp --build-arg FROMIMAGE=ace:12.0.4.0-r1 --file Dockerfile . +``` + +## Running the sample + +To run the application launch the container using a command such as: + +```bash +`docker run --name aceapp -d -p 7600:7600 -p 7800:7800 -v /setdbparms:/home/aceuser/initial-config/setdbparms -e LICENSE=accept aceapp` +``` + +On startup the logs will show that the configured scripts are run before starting the integration server. These credentials can then be used by flows referencing the appropriate resource diff --git a/ace_config_logging.sh b/samples/scripts/ace_config_logging.sh similarity index 58% rename from ace_config_logging.sh rename to samples/scripts/ace_config_logging.sh index ba390af..5f1577e 100755 --- a/ace_config_logging.sh +++ b/samples/scripts/ace_config_logging.sh @@ -7,25 +7,10 @@ # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v20.html -if [ -z "${ACE_SERVER_NAME}" ]; then - export ACE_SERVER_NAME=$(hostname | sed -e 's/[^a-zA-Z0-9._%/]//g') -fi - log() { MSG=$1 TIMESTAMP=$(date +%Y-%m-%dT%H:%M:%S.%3NZ%:z) - - if [ "${LOG_FORMAT}" == "json" ]; then - HOST=$(hostname) - PID=$$ - PROCESSNAME=$(basename $0) - USERNAME=$(id -un) - ESCAPEDMSG=$(echo $MSG | sed -e 's/[\"]/\\&/g') - # TODO: loglevel - echo "{\"host\":\"${HOST}\",\"ibm_datetime\":\"${TIMESTAMP}\",\"ibm_processId\":\"${PID}\",\"ibm_processName\":\"${PROCESSNAME}\",\"ibm_serverName\":\"${ACE_SERVER_NAME}\",\"ibm_userName\":\"${USERNAME}\",\"message\":\"${ESCAPEDMSG}\",\"ibm_type\":\"ace_containerlog\"}" - else - echo "${TIMESTAMP} ${MSG}" - fi + echo "${TIMESTAMP} ${MSG}" } # logAndExitIfError - if the return code given is 0 and the command outputed text, log it to stdout diff --git a/ace_config_setdbparms.sh b/samples/scripts/ace_config_setdbparms.sh similarity index 54% rename from ace_config_setdbparms.sh rename to samples/scripts/ace_config_setdbparms.sh index 0c40dda..6bb3217 100755 --- a/ace_config_setdbparms.sh +++ b/samples/scripts/ace_config_setdbparms.sh @@ -7,6 +7,21 @@ # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v20.html +function argStrings { + shlex() { + python -c $'import sys, shlex\nfor arg in shlex.split(sys.stdin):\n\tsys.stdout.write(arg)\n\tsys.stdout.write(\"\\0\")' + } + args=() + while IFS='' read -r -d ''; do + args+=( "$REPLY" ) + done < <(shlex <<<$1) + + log "${args[0]}" + log "${args[1]}" + log "${args[2]}" + +} + if [ -z "$MQSI_VERSION" ]; then source /opt/ibm/ace-12/server/bin/mqsiprofile fi @@ -26,17 +41,19 @@ if [ -s "/home/aceuser/initial-config/setdbparms/setdbparms.txt" ]; then continue fi IFS=${OLDIFS} - if [[ $line == mqsisetdbparms* ]]; then + if [[ $line == mqsisetdbparms* ]]; then log "Running suppplied mqsisetdbparms command" OUTPUT=`eval "$line"` else - - printf "%s" "$line" | xargs -n 1 printf "%s\n" > /tmp/creds - IFS=$'\n' read -d '' -r -a lines < /tmp/creds - - - log "Setting user and password for resource: ${lines[0]}" - cmd="mqsisetdbparms -w /home/aceuser/ace-server -n \"${lines[0]}\" -u \"${lines[1]}\" -p \"${lines[2]}\" 2>&1" + shlex() { + python -c $'import sys, shlex\nfor arg in shlex.split(sys.stdin):\n\tsys.stdout.write(arg)\n\tsys.stdout.write(\"\\0\")' + } + args=() + while IFS='' read -r -d ''; do + args+=( "$REPLY" ) + done < <(shlex <<<$line) + log "Setting user and password for resource: ${args[0]}" + cmd="mqsisetdbparms -w /home/aceuser/ace-server -n \"${args[0]}\" -u \"${args[1]}\" -p \"${args[2]}\" 2>&1" OUTPUT=`eval "$cmd"` echo $OUTPUT fi @@ -44,4 +61,4 @@ if [ -s "/home/aceuser/initial-config/setdbparms/setdbparms.txt" ]; then done fi -log "setdbparms configuration complete" +log "setdbparms configuration complete" \ No newline at end of file diff --git a/samples/scripts/server.conf.yaml b/samples/scripts/server.conf.yaml new file mode 100644 index 0000000..e5c1172 --- /dev/null +++ b/samples/scripts/server.conf.yaml @@ -0,0 +1,3 @@ +StartupScripts: + SetDBParms: + command: '/home/aceuser/initial-config/ace_config_setdbparms.sh' diff --git a/samples/scripts/setdbparms/setdbparms.txt b/samples/scripts/setdbparms/setdbparms.txt new file mode 100644 index 0000000..f833b10 --- /dev/null +++ b/samples/scripts/setdbparms/setdbparms.txt @@ -0,0 +1,6 @@ +# Lines starting with a "#" are ignored +# Each line which starts mqsisetdbparms will be run as written +# Alternatively each line should specify the , separated by a single space +# Each line will be processed by calling... +# mqsisetdbparms ${ACE_SERVER_NAME} -n -u -p +resource1 user1 password1 \ No newline at end of file diff --git a/samples/updateBase/Dockerfile b/samples/updateBase/Dockerfile new file mode 100644 index 0000000..7bcca1b --- /dev/null +++ b/samples/updateBase/Dockerfile @@ -0,0 +1,8 @@ +ARG FROMIMAGE=cp.icr.io/cp/appc/ace:12.0.4.0-r1 +FROM ${FROMIMAGE} + +USER root + +RUN RUN microdnf update && microdnf clean all + +USER 1001 \ No newline at end of file diff --git a/samples/updateBase/README.md b/samples/updateBase/README.md new file mode 100644 index 0000000..9354e5a --- /dev/null +++ b/samples/updateBase/README.md @@ -0,0 +1,14 @@ +# updateBase Sample + +## What is in the sample? + +This sample applies updates all the base OS packages. This can be used to resolve CVEs in the base packages + +## Building the sample + +- First [build the ACE image](../README.md#Building-a-container-image) or obtain one of the shipped images +- In the `sample/scripts/updateBase` folder: + + ```bash + docker build -t aceapp --build-arg FROMIMAGE=cp.icr.io/cp/appc/ace:12.0.4.0-r1 --file Dockerfile . + ``` diff --git a/test-and-coverage.sh b/test-and-coverage.sh deleted file mode 100755 index d07730d..0000000 --- a/test-and-coverage.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -e - -# Array of packages at 100% test coverage - Once a package has been fully covered it will be added to this list -COVERED_PACKAGES=('webadmin') -TEST_OUTPUT=$(go test $(go list ./... | grep -v /test/) -covermode=atomic -coverprofile cover.out) -COVERAGE_REGRESSION=false - -for i in ${COVERED_PACKAGES[@]}; do - COV=$(echo "$TEST_OUTPUT" | grep "$i" | awk '{ print $5 }') - if [[ "$COV" != "100.0%" ]]; then - echo "$i is not at 100% test coverage." - COVERAGE_REGRESSION=true - fi -done - -if [[ $COVERAGE_REGRESSION == true ]]; then - echo "Please address the coverage regression." - exit 1 -fi - - -# This is the current expected code coverage -CURRENT_COVERAGE=58.1 -LATEST=$(go tool cover -func cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}') -echo "Latest coverage: $LATEST" -echo "Expected Coverage: $CURRENT_COVERAGE" -result=$(echo "$LATEST >= $CURRENT_COVERAGE" | bc -l) -if [ $result -gt 0 ]; then - echo "PASSED - Coverage Check" -else - echo "Failed to meet required coverage value: $CURRENT_COVERAGE" - exit 1 - -fi -if [ "$LATEST" != "$CURRENT_COVERAGE" ]; then - echo "\nFAILED - You must update the CURRENT_COVERAGE in travis to match the new benchmark: $LATEST" - exit 1 -fi diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..947e9b8 --- /dev/null +++ b/test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +docker ps | grep -q ace +i=0 + +while ! docker logs ace | grep -q "Integration server has finished initialization"; +do + if [ $i -gt 30 ]; + then + echo "Failed to start - logs:" + docker logs ace + exit 1 + fi + sleep 1 + echo "waited $i secods for startup to complete...continue waiting" + ((i=i+1)) +done +echo "Integration Server started" +echo "Sending test request" +status_code=$(curl --write-out %{http_code} --silent --output /dev/null --request GET --url 'http://localhost:7800/customerdb/v1/customers?max=1' --header 'accept: application/json') +if [[ "$status_code" -ne 200 ]] ; then + echo "IS failed to respond with 200, it responded with $status_code" +fi +echo "Successfuly send test request" diff --git a/ubi/Dockerfile-legacy.aceonly b/ubi/Dockerfile-legacy.aceonly deleted file mode 100644 index 4648f0f..0000000 --- a/ubi/Dockerfile-legacy.aceonly +++ /dev/null @@ -1,113 +0,0 @@ -FROM golang:latest as builder - -WORKDIR /go/src/github.com/ot4i/ace-docker/ - -COPY go.mod . -COPY go.sum . -RUN go mod download - -COPY cmd/ ./cmd -COPY internal/ ./internal -COPY common/ ./common -RUN go version -RUN go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\"" ./cmd/runaceserver/ -RUN go build ./cmd/chkaceready/ -RUN go build ./cmd/chkacehealthy/ - -# Run all unit tests -RUN go test -v ./cmd/runaceserver/ -RUN go test -v ./internal/... -RUN go test -v ./common/... -RUN go vet ./cmd/... ./internal/... ./common/... - -ARG ACE_INSTALL=ace-12.0.1.0.tar.gz -ARG IFIX_LIST="" -WORKDIR /opt/ibm -COPY deps/$ACE_INSTALL . -COPY ./ApplyIFixes.sh /opt/ibm -RUN mkdir ace-12 -RUN tar -xzf $ACE_INSTALL --absolute-names --exclude ace-12.\*/tools --exclude ace-12.\*/server/bin/TADataCollector.sh --exclude ace-12.\*/server/transformationAdvisor/ta-plugin-ace.jar --strip-components 1 --directory /opt/ibm/ace-12 \ - && ./ApplyIFixes.sh $IFIX_LIST \ - && rm ./ApplyIFixes.sh - -FROM registry.access.redhat.com/ubi8/ubi-minimal - -ENV SUMMARY="Integration Server for App Connect Enterprise" \ - DESCRIPTION="Integration Server for App Connect Enterprise" \ - PRODNAME="AppConnectEnterprise" \ - COMPNAME="IntegrationServer" - -LABEL summary="$SUMMARY" \ - description="$DESCRIPTION" \ - io.k8s.description="$DESCRIPTION" \ - io.k8s.display-name="Integration Server for App Connect Enterprise" \ - io.openshift.tags="$PRODNAME,$COMPNAME" \ - com.redhat.component="$PRODNAME-$COMPNAME" \ - name="$PRODNAME/$COMPNAME" \ - vendor="IBM" \ - version="REPLACE_VERSION" \ - release="REPLACE_RELEASE" \ - license="IBM" \ - maintainer="Hybrid Integration Platform Cloud" \ - io.openshift.expose-services="" \ - usage="" - -# Add required license as text file in Liceses directory (GPL, MIT, APACHE, Partner End User Agreement, etc) -COPY /licenses/ /licenses/ - -RUN microdnf update && microdnf install findutils util-linux unzip python39 tar procps openssl && microdnf clean all \ - && ln -s /usr/bin/python3 /usr/local/bin/python \ - && mkdir /etc/ACEOpenTracing /opt/ACEOpenTracing /var/log/ACEOpenTracing && chmod 777 /var/log/ACEOpenTracing /etc/ACEOpenTracing - -# Force reinstall tzdata package to get zoneinfo files -RUN microdnf reinstall tzdata -y - -# Create OpenTracing directories, update permissions and copy in any library or configuration files needed -COPY deps/OpenTracing/library/* ./opt/ACEOpenTracing/ -COPY deps/OpenTracing/config/* ./etc/ACEOpenTracing/ - -WORKDIR /opt/ibm - -COPY --from=builder /opt/ibm/ace-12 /opt/ibm/ace-12 - -# Copy in PID1 process -COPY --from=builder /go/src/github.com/ot4i/ace-docker/runaceserver /usr/local/bin/ -COPY --from=builder /go/src/github.com/ot4i/ace-docker/chkace* /usr/local/bin/ - -# Copy in script files -COPY *.sh /usr/local/bin/ - -COPY ubi/generic_invalid/invalid_license.msgflow /home/aceuser/temp/gen -COPY ubi/generic_invalid/InvalidLicenseJava.jar /home/aceuser/temp/gen -COPY ubi/generic_invalid/application.descriptor /home/aceuser/temp - -# Create a user to run as, create the ace workdir, and chmod script files -RUN /opt/ibm/ace-12/ace make registry global accept license silently \ - && useradd -u 1000 -d /home/aceuser -G mqbrkrs,wheel aceuser \ - && mkdir -p /var/mqsi \ - && mkdir -p /home/aceuser/initial-config \ - && su - -c '. /opt/ibm/ace-12/server/bin/mqsiprofile && mqsicreateworkdir /home/aceuser/ace-server' \ - && chmod -R 777 /home/aceuser \ - && chmod -R 777 /var/mqsi \ - && su - -c '. /opt/ibm/ace-12/server/bin/mqsiprofile && echo $MQSI_JREPATH && chmod g+w $MQSI_JREPATH/lib/security/cacerts' \ - && chmod -R 777 /home/aceuser/temp \ - && chmod 777 /opt/ibm/ace-12/server/ODBC/dsdriver/odbc_cli/clidriver/license - -COPY git.commit /home/aceuser/ - -# Set BASH_ENV to source mqsiprofile when using docker exec bash -c -ENV BASH_ENV=/usr/local/bin/ace_env.sh - -# Expose ports. 7600, 7800, 7843 for ACE; 9483 for ACE metrics -EXPOSE 7600 7800 7843 9483 - -WORKDIR /home/aceuser - -ENV LOG_FORMAT=basic - -# Set user to prevent container running as root by default -USER 1000 - -# Set entrypoint to run management script - -ENTRYPOINT ["runaceserver"] diff --git a/ubi/Dockerfile.aceonly b/ubi/Dockerfile.aceonly deleted file mode 100644 index aa57586..0000000 --- a/ubi/Dockerfile.aceonly +++ /dev/null @@ -1,119 +0,0 @@ -FROM golang:latest as builder - -WORKDIR /go/src/github.com/ot4i/ace-docker/ - -COPY go.mod . -COPY go.sum . -RUN go mod download - -COPY cmd/ ./cmd -COPY internal/ ./internal -COPY common/ ./common -RUN go version -RUN go build -ldflags "-X \"main.ImageCreated=$(date --iso-8601=seconds)\"" ./cmd/runaceserver/ -RUN go build ./cmd/chkaceready/ -RUN go build ./cmd/chkacehealthy/ - -# Run all unit tests -RUN go test -v ./cmd/runaceserver/ -RUN go test -v ./internal/... -RUN go test -v ./common/... -RUN go vet ./cmd/... ./internal/... ./common/... - -ARG ACE_INSTALL=ace-12.0.1.0.tar.gz -ARG IFIX_LIST="" -WORKDIR /opt/ibm -COPY deps/$ACE_INSTALL . -COPY ./ApplyIFixes.sh /opt/ibm -RUN mkdir ace-12 -RUN tar -xzf $ACE_INSTALL --absolute-names --exclude ace-12.\*/tools --exclude ace-12.\*/server/bin/TADataCollector.sh --exclude ace-12.\*/server/transformationAdvisor/ta-plugin-ace.jar --strip-components 1 --directory /opt/ibm/ace-12 \ - && ./ApplyIFixes.sh $IFIX_LIST \ - && rm ./ApplyIFixes.sh \ - && rm -rf /opt/ibm/ace-12/fix-backups* - -# This is to delete any flash files which some customers consider a vulnerability - mostly used by googlelibs -RUN echo "Removing the following swf files" \ - && find . -name "*.swf" -type f \ - && find . -name "*.swf" -type f -delete - -FROM registry.access.redhat.com/ubi8/ubi-minimal - -ENV SUMMARY="Integration Server for App Connect Enterprise" \ - DESCRIPTION="Integration Server for App Connect Enterprise" \ - PRODNAME="AppConnectEnterprise" \ - COMPNAME="IntegrationServer" - -LABEL summary="$SUMMARY" \ - description="$DESCRIPTION" \ - io.k8s.description="$DESCRIPTION" \ - io.k8s.display-name="Integration Server for App Connect Enterprise" \ - io.openshift.tags="$PRODNAME,$COMPNAME" \ - com.redhat.component="$PRODNAME-$COMPNAME" \ - name="$PRODNAME/$COMPNAME" \ - vendor="IBM" \ - version="REPLACE_VERSION" \ - release="REPLACE_RELEASE" \ - license="IBM" \ - maintainer="Hybrid Integration Platform Cloud" \ - io.openshift.expose-services="" \ - usage="" - -# Add required license as text file in Liceses directory (GPL, MIT, APACHE, Partner End User Agreement, etc) -COPY /licenses/ /licenses/ - -RUN microdnf update && microdnf install findutils util-linux unzip tar procps openssl && microdnf clean all \ - && mkdir /etc/ACEOpenTracing /opt/ACEOpenTracing /var/log/ACEOpenTracing && chmod 777 /var/log/ACEOpenTracing /etc/ACEOpenTracing - -# Force reinstall tzdata package to get zoneinfo files -RUN microdnf reinstall tzdata -y - -# Create OpenTracing directories, update permissions and copy in any library or configuration files needed -COPY deps/OpenTracing/library/* ./opt/ACEOpenTracing/ -COPY deps/OpenTracing/config/* ./etc/ACEOpenTracing/ - -WORKDIR /opt/ibm - -COPY --from=builder /opt/ibm/ace-12 /opt/ibm/ace-12 - -# Copy in PID1 process -COPY --from=builder /go/src/github.com/ot4i/ace-docker/runaceserver /usr/local/bin/ -COPY --from=builder /go/src/github.com/ot4i/ace-docker/chkace* /usr/local/bin/ - -# Copy in script files -COPY *.sh /usr/local/bin/ - -COPY ubi/generic_invalid/invalid_license.msgflow /home/aceuser/temp/gen -COPY ubi/generic_invalid/InvalidLicenseJava.jar /home/aceuser/temp/gen -COPY ubi/generic_invalid/application.descriptor /home/aceuser/temp -COPY deps/CSAPI /home/aceuser/deps/CSAPI - -# Create a user to run as, create the ace workdir, and chmod script files -RUN /opt/ibm/ace-12/ace make registry global accept license silently \ - && useradd -u 1000 -d /home/aceuser -G mqbrkrs,wheel aceuser \ - && mkdir -p /var/mqsi \ - && mkdir -p /home/aceuser/initial-config \ - && su - -c '. /opt/ibm/ace-12/server/bin/mqsiprofile && mqsicreateworkdir /home/aceuser/ace-server' \ - && chmod -R 777 /home/aceuser \ - && chmod -R 777 /var/mqsi \ - && su - -c '. /opt/ibm/ace-12/server/bin/mqsiprofile && echo $MQSI_JREPATH && chmod g+w $MQSI_JREPATH/lib/security/cacerts' \ - && chmod -R 777 /home/aceuser/temp \ - && chmod 777 /opt/ibm/ace-12/server/ODBC/dsdriver/odbc_cli/clidriver/license - -COPY git.commit /home/aceuser/ - -# Set BASH_ENV to source mqsiprofile when using docker exec bash -c -ENV BASH_ENV=/usr/local/bin/ace_env.sh - -# Expose ports. 7600, 7800, 7843 for ACE; 9483 for ACE metrics -EXPOSE 7600 7800 7843 9483 - -WORKDIR /home/aceuser - -ENV LOG_FORMAT=basic - -# Set user to prevent container running as root by default -USER 1000 - -# Set entrypoint to run management script - -ENTRYPOINT ["runaceserver"] \ No newline at end of file diff --git a/ubi/Dockerfile.connectors b/ubi/Dockerfile.connectors deleted file mode 100644 index 6304726..0000000 --- a/ubi/Dockerfile.connectors +++ /dev/null @@ -1,17 +0,0 @@ -ARG BASE_IMAGE -FROM $BASE_IMAGE - -USER root -# Add in connectors to node_modules that are accessible from ace -RUN mkdir -p /opt/ibm/ace-12/node_modules -COPY deps/package-connectors.json /opt/ibm/ace-12/package.json -RUN export PATH=$PATH:/opt/ibm/ace-12/common/node/bin \ - && cd /opt/ibm/ace-12/node_modules \ - && npm install loopback-connector-mongodb loopback-connector-postgresql --save \ - && chown -R aceuser:mqbrkrs /opt/ibm/ace-12/node_modules \ - && echo "Removing the following swf files" \ - && find . -name "*.swf" -type f \ - && find . -name "*.swf" -type f -delete - - -USER 1000 \ No newline at end of file diff --git a/ubi/Dockerfile.ifix b/ubi/Dockerfile.ifix deleted file mode 100644 index 38c00d3..0000000 --- a/ubi/Dockerfile.ifix +++ /dev/null @@ -1,20 +0,0 @@ -ARG BASE_IMAGE -FROM ${BASE_IMAGE} - -USER root - -ARG IFIX_ID - -COPY ["${IFIX_ID}.tar.gz", "/tmp"] -RUN mkdir /tmp/fix && \ - cd /tmp/fix && \ - tar xzf /tmp/${IFIX_ID}.tar.gz && \ - ./mqsifixinst.sh /opt/ibm/ace-12 testinstall ${IFIX_ID} && \ - ./mqsifixinst.sh /opt/ibm/ace-12 install ${IFIX_ID} && \ - cd /tmp && \ - rm -rf /tmp/fix && \ - rm -rf /opt/ibm/ace-12/fix-backups* && \ - rm /opt/ibm/ace-12/mqsifixinst.log && \ - rm /opt/ibm/ace-12/mqsifixinst.sh - -USER 1000 diff --git a/ubi/Dockerfile.mqclient b/ubi/Dockerfile.mqclient deleted file mode 100644 index 5767966..0000000 --- a/ubi/Dockerfile.mqclient +++ /dev/null @@ -1,72 +0,0 @@ - -ARG BASE_IMAGE -FROM $BASE_IMAGE as truststore-builder - -USER root - -# The MQ packages to install - see install-mq.sh for default value -ARG MQ_URL -ARG MQ_URL_USER -ARG MQ_URL_PASS -ARG MQ_PACKAGES="MQSeriesRuntime*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesClient*.rpm" -ARG INSTALL_JRE=1 - -COPY ubi/install-mq.sh /usr/local/bin/ -COPY ubi/install-mq-client-prereqs.sh /usr/local/bin/ -COPY ubi/create-default-mq-kdb.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-*.sh /usr/local/bin/create-*.sh \ - && sleep 1 \ - && install-mq-client-prereqs.sh \ - && install-mq.sh \ - && chown -R 1001:root /opt/mqm/* - -RUN . /opt/ibm/ace-12/server/bin/mqsiprofile \ - && echo $MQSI_JREPATH \ - && /usr/local/bin/create-default-mq-kdb.sh - -# This is to delete any flash files which some customers consider a vulnerability - mostly used by googlelibs -RUN echo "Removing the following swf files" \ - && find . -name "*.swf" -type f \ - && find . -name "*.swf" -type f -delete - -FROM $BASE_IMAGE as clientimage - -USER root - -# The MQ packages to install - see install-mq.sh for default value -ARG MQ_URL -ARG MQ_URL_USER -ARG MQ_URL_PASS -ARG MQ_PACKAGES="MQSeriesRuntime*.rpm MQSeriesJava*.rpm MQSeriesJRE*.rpm MQSeriesGSKit*.rpm MQSeriesClient*.rpm" -ARG INSTALL_JRE=0 - -ARG MQM_UID=888 - -COPY ubi/install-mq.sh /usr/local/bin/ -COPY ubi/install-mq-client-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-*.sh \ - && sleep 1 \ - && install-mq-client-prereqs.sh $MQM_UID \ - && install-mq.sh $MQM_UID \ - && chown -R 1001:root /opt/mqm/* \ - && chown 1001:root /usr/local/bin/*mq* \ - && mkdir -p /var/mqm/data \ - && chown -R 1001:root /var/mqm \ - && chmod -R 777 /var/mqm - -# Always use port 1414 for MQ & 9157 for the metrics -ENV MQ_OVERRIDE_DATA_PATH=/var/mqm/data MQ_OVERRIDE_INSTALLATION_NAME=Installation1 MQ_USER_NAME="mqm" PATH="${PATH}:/opt/mqm/bin" -ENV AMQ_DIAGNOSTIC_MSG_SEVERITY=1 AMQ_ADDITIONAL_JSON_LOG=1 - -# Set the integration server to use it by default. A user provided server.conf.yaml will override this behaviour if the mqKeyRepository property is set. -RUN mkdir /home/aceuser/truststores -COPY --from=truststore-builder /tmp/mqcacerts.kdb /home/aceuser/truststores/mqcacerts.kdb -COPY --from=truststore-builder /tmp/mqcacerts.sth /home/aceuser/truststores/mqcacerts.sth -RUN chmod -R 777 /home/aceuser/truststores \ - && sed -i 's/#.*mqKeyRepository:.*/mqKeyRepository: \/home\/aceuser\/truststores\/mqcacerts/g' /home/aceuser/ace-server/server.conf.yaml - -ENV MQCERTLABL=aceclient - -USER 1000 diff --git a/ubi/create-default-mq-kdb.sh b/ubi/create-default-mq-kdb.sh deleted file mode 100755 index 68ac10c..0000000 --- a/ubi/create-default-mq-kdb.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# -*- mode: sh -*- -# © Copyright IBM Corporation 2022 -# -# -# 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. - -# Fail on any non-zero return code -set -ex - - -if [ -f "/opt/mqm/bin/runmqckm" ] -then - # - # Used if the downloaded package is the MQ client package from FixCentral. Example URL: - # - # https://ak-delivery04-mul.dhe.ibm.com/sdfdl/v2/sar/CM/WS/0a3ih/0/Xa.2/Xb.jusyLTSp44S0BnrSUlhcQXsmOX33PXiMu_opTWF4XkF7jFZV8UxrP0RFSE0/Xc.CM/WS/0a3ih/0/9.2.0.4-IBM-MQC-LinuxX64.tar.gz/Xd./Xf.LPR.D1VK/Xg.11634360/Xi.habanero/XY.habanero/XZ.m7uIgNXpo_VTCGzC-hylOC79m0eKS5pi/9.2.0.4-IBM-MQC-LinuxX64.tar.gz - # - # Also used if the downloaded package is the full MQ developer package. Example URL: - # - # https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev924_linux_x86-64.tar.gz - # - echo "Using runmqckm to create default MQ kdb from Java cacerts" - /opt/mqm/bin/runmqckm -keydb -convert -db $MQSI_JREPATH/lib/security/cacerts -old_format jks -new_format kdb -pw changeit -target /tmp/mqcacerts.kdb -stash -else - # - # Used if the downloaded package is the MQ redistributable client. Example URL: - # - # https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqdev/redist/9.2.0.4-IBM-MQC-Redist-LinuxX64.tar.gz - # - echo "Did not find runmqckm; using keytool and runmqakm to create default MQ kdb from Java cacerts" - $MQSI_JREPATH/bin/keytool -importkeystore -srckeystore $MQSI_JREPATH/lib/security/cacerts -srcstorepass changeit -destkeystore /tmp/java-cacerts.p12 -deststoretype pkcs12 -deststorepass changeit - /opt/mqm/bin/runmqakm -keydb -convert -db /tmp/java-cacerts.p12 -old_format p12 -new_format kdb -pw changeit -target /tmp/mqcacerts.kdb -stash -fi diff --git a/ubi/generic_invalid/ACE toolkit project interchange.zip b/ubi/generic_invalid/ACE toolkit project interchange.zip deleted file mode 100644 index ff08575a9f77924553c574b40a0e0aac5b77a6f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2888 zcmaKuc{tR09LI;kUQUw!J zxw#(pd3$O;Xluz&A}>;g5^%KSt_c!tIz|&vDm(Q^Qk)stli-?Hdo1UYJ?~reb2*gj49?2$cUg@+x1nyT5QN=#Og7l5lQV7AVEscV-`Thlo zvL6D+`iTNY@SZ&{JX#c8BgC zC~`NsvEH}J!Y&&qYz*l<4O7;3JXd&ISPjH&0=x@R_moMQX!QNEE&KRma2Uoi-tI(w z2E6Kp4p%o<;%`*r^C4kyJzmLS7)Uz#nC#Vog*8KwPlnV(TMx`2cMr=t$t6yHKC#c! zlRLI67Um7YH$FP)_6OgnLS`|u{*Il?!o%h3c~`4#Y^{<+#Q2DfLKk#~%q^N{B8smm zC2*v2Ijf{MW3kez3|Va?>^G5jNUsj=63aElZ~F!0DkLBmKZRT$ z>xx5^IW;drzxXqXXbD?1Fa7#J6;mJNJ^d*D%wbYzOnnPYu_R;I7%cUUp#=;lUiS-b zJK|(K9U&SUJ3Hj);@jgjZCWj#creQ{xk@AkZl;Crg*e*D%&ZRBGR<#3ZomC&uc)p- zwt|@@m;KO7n!J(Rt$F8(mDJR68nGorT}s1J0=PH9$LUxv<7cHt7*)o24bt~}v}mev z{=LaVZB4eAg!js4_qFV#;Zxrd$8&vxsLE z2G>vuXN|ln1vkcujvQf;)fV^1MAmh*LNy%I2NXxXHox!i)#SK?Dg%WpRV2?W474rFz6cB|U>a$_50c{!f(|58NqfwXydxkqaf&U`TLtYg z5&0vkRj*y4_j>F5*9qr&LVvN+T;<;Zn$Tw|N#FzkhPnR_Xvu#84RsH7S5Zdzx?`~z z_h2tds<~B+p>XVA#z*PaMY`R2)}FM8zi;<`ujy4pS8#6 zIttEDl4a7*NtAoQ{NYBNWLSvnx>`7^ynMNJWqV%zVzju%xknpKO;)kJ^Bd!o4rO^$ zjs2H25ylNq2h-DtCkye0@*h2fu60G{SXR+bffsbquc)u(!*BN&dujCZjOZ$jwoqFe zqPoZ>fk6X^tuL;holby<}R}N#8i(P-4z9`-mv$%))7J@ zx0yYQ%6Xrw9TxWeersTniW;&*pLosZJ#ryr^Acgi;qzwT89YjFdVta6KB`?mV=;v|@o@-BPg{jq) zb4^3nrE(8&JP1*XV>w+}DvrGiFPBWKQ%Ps=LYG`c2vfok39#rrd#Tm)S4f$`+hY&h z1>R>ZpHfp{(7wz`a1#qRId5)UZY$;vVwNGpQBMyr8F-bf$-bpE^UkjeT08#96y$Pj zu-#euU;iNg?VoLM0LGLZpvpdM5Gl460hxwEbeEM#(ZB|qntA5@bBj1iyL6gaP`nKK z`GLo8s}e(b^z5g&>bRZEHl@QO)HzgZV%*cY4IZZ`6 z9*hP(+~13jYZK8L@#k>k?QA#Ud9_Sm!#_J8GwN8w ziN2UdYT$S|GVI5t#{gJd4w3xG=U3gTv(PXuV4&n;5gj0OXg(0r`?QCZ=q=omA4p4j zvyoUDXH-{B?3rE{M}Am1iiC^au$7lcw?UI;+xUxCKhjco8U=0e?Qjgg!aNa-G(^u> zIdYY~x%Bn|A-Jg_xvx+Tlubz$*A3--TebYvX2sRhDow!UF=W`i}M3f7TDyxZNV( zC+5ze|5iV-pWP+$gR#F`^ZO{-$=Yu}=pQX`xDVs*X#fQ diff --git a/ubi/generic_invalid/InvalidLicenseJava.jar b/ubi/generic_invalid/InvalidLicenseJava.jar deleted file mode 100644 index 337b3f841b259e75c5f2ab671453f473a7f69fcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2470 zcma)8c{r3^8-MJ(#DpPRhJ+bgWypkN3B%A>vX3?Ej9v9gG5{|J(V^=wE4poscmvbPxmYuhSWn|LvWB3 zLBNLAmlwk05>g7sF8fwaTgve_Lp5BQqaadTdApO(swYM4^e@-ds>PQl{w=u8Uz(D6 z?8~1=`Ci3d?JAo601WIHxICTd@sO<*rQ9!pg=ob3=7ve(h)-=c$29bgGF zL`=k>6et|h(|A&(Lc&DCyrU#VrRVux)(KJ8<~=NiDHK|^TXX>Qy}vTHi1%phIJZhtPL;81-sJvwRpO>Yz1C^f=%fF}1OSE$zUwc*B3sm zB)81?bcZzaREB=`B8wJ5)v1QHA&%j7np2vVu@@z`rcdC5npw3s;%_f^XW{ytcV_iG zE7U!;1m0U*H>Y}D6>79igbhVGf(~>xI1m5fd0Sg}eOQukgc9%EUuY)-7qf|R3KB~W zmQ|ugGMYWO%xMK@ay=hwe64(5)q%Q-KL*Lldef?GJb9;W#lu2bkS8a_{jqu$r zXtc7=srwSs6Y9(BYQ3Mqh;`_p<-jcqcmH0o>O^9`!&Faq{xm=KH())yBX2X~+T7cePaA~Lv8{p{ zHATtT;KF2l4{WtydHLiyn&cm0p0Ld@WcZOo+Cy%w^mI)1ZyFj?SvK>NLWd8XR`M$g z^jMe-T$mtlh&eXUg(bv9DltzMm}Z?tiCg$!LgIC*`E=qn$^UK+Fm00Kvn| z;~cJX;1IVUnHu%+GLah6YFyjEV&D9xyKH_Or>rz3*VQQ*v%uWP41e(NB}0ade%W+K`NSnZB(RqCtR&3Gr?TlmFYKq-R0~ z6lpUELrZvXnzp=ZP(ZP?w3B%y2+>yRvx{Xf8015%cx4=dMRi$ttA-Mni1|-mzbP_y zSo2#l5Q?l9Czy?~AWWZE8-o@*e4V+2yWSa_Lm<1wmIfI1OpD zL%pD^;r6#BDEDlo9lF5P0cvdpe>*(~=(&NbfvNFRU&e46gAT0->Rig&^FQDN6g1*_e-w9tk#tplBv z-qRDT8ZkCC!|&k-N@h$;BT1(JW+(H@H7zw7JJT2efcqzQerI9j*=1}$3bbqQ ze}P zEQ)tN>mXzjp*K@TNx^I*f&l|%(a{W5)vL?=8v~=QTrr*RV(t$)95<?*8j1*=~5Dm;w2g$V`s@MR}f!=^m6*qLsSmSGAxS;Xy<^MzJ_5KrhJ za*h;%Y&&tLu(E~Eby#?IUK<|vFg&K;k);pfHHk{!e#sqsTqWyvJ&&MH&6^s#L6NL& z&-AA^yLi`f7Ne+%eEPlkka$R%%sQXkd|+F4e7qTUe?}w()^}I*1Wz~5y-0{Rr+4#V z1DiklgT(b6)O5|t97_u)Tvz1$BJ^j1&$Y{LkXm}ow$+LWhuD2kRV#LB6AwEW&@l7` ze%;jI{YxP?oCAF_iLBMJqxlHE2b?<_?`33-ee-seQX*(dPe%`8_}^^-jcGLF0p4kU z-G3P0>6{&Z}4vsW@zi4fd7j7Rn+!y8#M9% zN8)}){qDWrL-EoS;QxF5e~tO;U%D5wlxZ*K_nV-Mn3%tQXQquyv^)>cIx_(H51Shl AqW}N^ diff --git a/ubi/generic_invalid/application.descriptor b/ubi/generic_invalid/application.descriptor deleted file mode 100644 index 166bf9e..0000000 --- a/ubi/generic_invalid/application.descriptor +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ubi/generic_invalid/invalid_license.msgflow b/ubi/generic_invalid/invalid_license.msgflow deleted file mode 100644 index bb02f87..0000000 --- a/ubi/generic_invalid/invalid_license.msgflow +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/ubi/install-mq-client-prereqs.sh b/ubi/install-mq-client-prereqs.sh deleted file mode 100755 index 5e748f7..0000000 --- a/ubi/install-mq-client-prereqs.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/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. - -# Fail on any non-zero return code -set -ex - -test -f /usr/bin/microdnf && MICRODNF=true || MICRODNF=false -test -f /usr/bin/rpm && RPM=true || RPM=false - -if ($RPM); then - EXTRA_RPMS="bash bc ca-certificates file findutils gawk glibc-common grep ncurses-compat-libs passwd procps-ng sed shadow-utils tar util-linux which" - $MICRODNF && microdnf install ${EXTRA_RPMS} -else - $MICRODNF && microdnf install findutils -fi - -# Clean up cached files -$MICRODNF && microdnf clean all diff --git a/ubi/install-mq.sh b/ubi/install-mq.sh deleted file mode 100755 index cc8b077..0000000 --- a/ubi/install-mq.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/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. - -# Fail on any non-zero return code -set -ex - -# Download and extract the MQ files -DIR_TMP=/tmp/mq -mkdir -p ${DIR_TMP} -cd ${DIR_TMP} -if [ -z "$MQ_URL_USER" ] -then - curl -LO $MQ_URL -else - curl -LO -u ${MQ_URL_USER}:${MQ_URL_PASS} $MQ_URL -fi -tar -xzf ./*.tar.gz -rm -f ./*.tar.gz -ls -la ${DIR_TMP} - -# Check what sort of MQ package was downloaded -if [ -f "${DIR_TMP}/bin/genmqpkg.sh" ] -then - # Generate MQ package in INSTALLATION_DIR - # - # Used if the downloaded package is the MQ redistributable client. Example URL: - # - # https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqdev/redist/9.2.0.4-IBM-MQC-Redist-LinuxX64.tar.gz - # - echo "Detected genmqpkg.sh; installing MQ client components" - export genmqpkg_inc32=0 - export genmqpkg_incadm=1 - export genmqpkg_incamqp=0 - export genmqpkg_incams=0 - export genmqpkg_inccbl=0 - export genmqpkg_inccics=0 - export genmqpkg_inccpp=1 - export genmqpkg_incdnet=0 - export genmqpkg_incjava=1 - export genmqpkg_incjre=${INSTALL_JRE} - export genmqpkg_incman=0 - export genmqpkg_incmqbc=0 - export genmqpkg_incmqft=0 - export genmqpkg_incmqsf=0 - export genmqpkg_incmqxr=0 - export genmqpkg_incnls=0 - export genmqpkg_incras=1 - export genmqpkg_incsamp=0 - export genmqpkg_incsdk=0 - export genmqpkg_incserver=0 - export genmqpkg_inctls=1 - export genmqpkg_incunthrd=0 - export genmqpkg_incweb=0 - export INSTALLATION_DIR=/opt/mqm - - # Install requested parts - ${DIR_TMP}/bin/genmqpkg.sh -b ${INSTALLATION_DIR} - ls -la ${INSTALLATION_DIR} - - # Accept the MQ license - ${INSTALLATION_DIR}/bin/mqlicense -accept -else - # Check if should try install using RPM - test -f /usr/bin/rpm && RPM=true || RPM=false - if [ ! $RPM ]; then - echo "Did not find the rpm command; cannot continue MQ client install without rpm" - exit 9 - fi - # - # Used if the downloaded package is the MQ client package from FixCentral. Example URL: - # - # https://ak-delivery04-mul.dhe.ibm.com/sdfdl/v2/sar/CM/WS/0a3ih/0/Xa.2/Xb.jusyLTSp44S0BnrSUlhcQXsmOX33PXiMu_opTWF4XkF7jFZV8UxrP0RFSE0/Xc.CM/WS/0a3ih/0/9.2.0.4-IBM-MQC-LinuxX64.tar.gz/Xd./Xf.LPR.D1VK/Xg.11634360/Xi.habanero/XY.habanero/XZ.m7uIgNXpo_VTCGzC-hylOC79m0eKS5pi/9.2.0.4-IBM-MQC-LinuxX64.tar.gz - # - # Also used if the downloaded package is the full MQ developer package. Example URL: - # - # https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqadv/mqadv_dev924_linux_x86-64.tar.gz - # - echo "Did not find genmqpkg.sh; installing MQ client components using rpm" - $RPM && DIR_RPM=$(find ${DIR_TMP} -name "*.rpm" -printf "%h\n" | sort -u | head -1) - - # Find location of mqlicense.sh - MQLICENSE=$(find ${DIR_TMP} -name "mqlicense.sh") - - # Accept the MQ license - ${MQLICENSE} -text_only -accept - - # Install MQ using the rpm packages - $RPM && cd $DIR_RPM && rpm -ivh $MQ_PACKAGES - - # Remove tar.gz files unpacked by RPM postinst scripts - find /opt/mqm -name '*.tar.gz' -delete -fi - -rm -rf ${DIR_TMP} - -# Create the directory for MQ configuration files -install --directory --mode 2775 --owner 1001 --group root /etc/mqm -

J%2!~D1#p2%6eBeu;P^r3zZ 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 cKfOOj{K@!?Pq>ozCZ2cY^Ruq}Kb-qO{{R30 diff --git a/sample/Dockerfile.aceonly b/sample/Dockerfile.aceonly deleted file mode 100644 index cea0cce..0000000 --- a/sample/Dockerfile.aceonly +++ /dev/null @@ -1,16 +0,0 @@ -FROM ace-only:latest - -USER root - -COPY bars_aceonly /home/aceuser/bars -RUN chmod -R ugo+rwx /home/aceuser - -USER 1000 - -RUN ace_compile_bars.sh - -USER root - -RUN chmod -R ugo+rwx /home/aceuser - -USER 1000 \ No newline at end of file diff --git a/sample/PIs/CallHTTPSEcho.zip b/sample/PIs/CallHTTPSEcho.zip deleted file mode 100644 index 307dfa54b82185b4fe1b5c1d0e9f3911948116a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1770 zcmaJ?dpOf;9RIPJNl3;luII67Lzz!2 z!*|hGuNL_%jXY~zq&(Np$}2zGECh@&ytysYUHok=&fU2b9^%#A3T-t9YY1m^eI#Ua_ zEJhSYz(a+Lz)Pvz!c<>OU$pkxrDWx$uZqw2%5nH&1DCi)IdmY15%w?DCz` z-JhZ^ARETiN8{V)l6gCVQ?OIYV7Z2qHiNkRWAQr@H*w|%t@8JInqvM#{EXv^-Gvtj735HAG)nExD%&A$xB%9s!+dO=h_ z-$2rt`#4up76KYMao4POECw_Zq+O0>u`NgKq$M`3#iG#N&>_%H(@vP{iP7&bTCY#I?cfvICxFAb6@C{5>W9aH;Ks z>50?ur0WC8#ZL7^H2jtYLo;sU#YE0N!+sn3b7S6Vx~n@PX|2x+x2 z|AaDdfxW3i&)aSN_-+d{IvHnwut?EoL_oMqtGi!rHw_Lbqg_!${5f4xV%ktEd&9(Rr&@oe3Y3bIpBI zW6=qAjWXtd$8SWD@YUrx_8VC5RPxpgT&A2*T>B zH!!m|2>_Ib{+vYpcrK$Xx^Kk-O;s#nl{f&@LjKPJ8oyY;i%bsm_x7UslTPdT5UAc1 ze=?0kNlG@Cd8D?%>aK;aexa@a=*i46lQkQ0v}s=N75lQw7XOZ)w6N5BKJ?tMoyol; zcnZ1gEXJCdfksJmJ}W#knMoCD+oT`WBIPX*9D#!mmPAg zjL+xAwFU^Do#DJ!gQ(&~?1Q(yN%Z2}N@mf}fjeJECP^#r0z*toVwSspwY)L5V&YKX zd+M*Y{YvWph~NJKKXa_sZ6zSS1HP>0j~qY7#m`KuJ6+NFJ2tQVnd$ecV{F%gSE@kE NtAF{NS*`)jF)5`sWk`vu^1w8J8BL0CV+Uv4j)r49&~J#&FQa(BR)YGP~ygsEzRoQKa1RcPMY ze71V=>n9CT)5g7=`+7a{^T*OY98*Bw9bRtgQ#mAnGLE^w1}aO3)c3D+3Y|`WJfb6O zrqz?vUM0)|VjEgjla|N^IvE+B8Uorw@Oj)&;fJQ+{K_J8njTN6=lvHHTXatJV~`Lv#%XgJ+8ioNvf5ildB5ElJ_QioDgGpS z4yIxhlIpW&h1E~`E@P#(Mtr^2`{0i|WRHVF;<5(=Fewyq*HS}r*U!8s_o|CrRmOPxC+NRv+Cnu42`1F zX`b>kd+=WB+V~rtG+ljB!=cypk&J}rR$_03>73cOwT>mFcatA=DlZ@me&o9dw5jrV zVL$mtZ^R!ub8+h3^3=M2)+RU7ffvN`o7&F=0stvs008-qo{-q-3AR135!#{n)BYhi z;)|RUE|W0mx$z%+scUnhs)jj0>NRexP2>5xX5-T2OeI86CjUb9II})-gt7k;>ynNM zo;BWK=M-3YC)VE-a=ZMUw5!^+E7lJ+Ze_(t6U=XIou%}TUzMtdt1?KL_sSQM3VV1LQY_vgGuZFNg3 z1YdNV$KJEcF}ClMxIl)L6b}JguD`5`CyG<)NLNliV(iOP*o?gpMSeYg&i(E0Dk_a> zBPENL6!}332#aGvk&-Rs-rWbwzwRr_7&PiLf!Nr1GIAAyEe6%Z?<&g|>PBGa9I-&K zX(un!L$~qMBm9@l-o1{`@v~g`IXB4@&Nqa=$93BJCTiMcflBkIKt;LNFUN!1(Tj!9 z+=fJr!(fSLzDsreEq32bO-AyXgML2y`f6$Q>lb7A13$j!H!XUF7=t5Bb%FD>Wz5Mi zD@h|Ce8HmGL8+tHq9N#S#GI0I5B;Z2R9*D#E6zrWHUP?`?=aPn(+RxZly<{xwSyOu zk*{G*_PLMe*F}fX44X&cr-tTalQDB74VwvzQL&P|zslpQIBW#b$K?60qUIxY?o@wn zf4)R^uCZql~G{Uv2 zR}RG8mPD&RMsuqh{b49Hc!GN|t#zcZT!jZ$4@H`(tij?HWRCXek+?(=ZVmd$_;~XK zs4jV}Fz;ozLGhqwyFya}gq7M~)3O|A`y*vq14!o7ujf({j zQHxJPSgzhPDD_KuWzw@Eao8|}GF7y}Nc}a}L(2Ur$~9}vEe9RBl>!3tktb}~F$=07zo#WrCp|EQ;Trg2POQCh?sSrbMk5%sn4WqdWvH^$d3CA-z zt})*1v9@%!I1#iFYuMM-+}-U@1WoWrg0q5+jc5bZdDQnn{$X^F zNw)y&(u}~mQl3h^AX?R+seYQP);_MAl24tRvOjt{_FU+=x{&L|nzSdnD%P*Zeu2=4 z1^DP>R}Zml(W^QKRHEvm{!ngo``Wsfc{Uw$=L_qz{Jl1yJ<=Nywox%O1!iksfEB?i zwv-KQJ^$P#!32!v-Z&zB zWY*QLQvj3NH{#$s+4kr9&#{E4*lC1mjlFL~*vlBC?UfuWL*AjT`a|b4{}aF`1u;2W z+%lup5e_OXK=~p{!;gF+e#O~(%UFVt}FLbGe^f9Kp@OtxKr!Hjn|&i^y-?}XbwB-;bOU;^4fxVs~e U4%|HKBivgnb}L}QeC*x-0J%A2^#A|> diff --git a/sample/PIs/CallMQMultiEcho.zip b/sample/PIs/CallMQMultiEcho.zip deleted file mode 100644 index 91668b1ce0985aba4969bd0bf359e229c4c26b87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4471 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 diff --git a/sample/PIs/MQEcho.zip b/sample/PIs/MQEcho.zip deleted file mode 100644 index 58d57c0410aaceee581d0a462d90188d7672a389..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2115 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 U9Ccvl9RR?+vlMrc0Pf!Y1J3r6YybcN diff --git a/sample/README.md b/sample/README.md deleted file mode 100644 index fa7f592..0000000 --- a/sample/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Sample - -The sample folder contains Docker files that build a sample image containing sample applications and Integration Server configuration. This requires you to mount the sample `initial-config` folder as well. - -## Build the sample image - -You need to assign everyone with read+write permissions for all files that you ADD/COPY into your custom image. The same goes for any files generated from running a script inside your custom image. This is to support the container being run under the OCP "restricted" ID. - -### Sample based on the ACE only image - -First [build the ACE only image](../README.md#build-an-image-with-app-connect-enterprise-only). - -In the `sample` folder: - -``` -docker build -t aceapp --file Dockerfile.aceonly . -``` - -## Run the sample image -### ACE only image - -To run a container based on the ACE only image and these settings: -- ACE server name `ACESERVER` -- listener for ACE web ui on port `7600` -- listener for ACE HTTP on port `7600` -- ACE truststore password `truststorepwd` -- ACE keystore password `keystorepwd` - -And mounting `sample/initial-config` with the sample configuration into `/home/aceuser/initial-config`. - -> **Note**: Always mount any initial config to be processed on start up to `/home/aceuser/initial-config`. - -`docker run --name aceapp -p 7600:7600 -p 7800:7800 -p 7843:7843 --env LICENSE=accept --env ACE_SERVER_NAME=ACESERVER --mount type=bind,src=/{path to repo}/sample/initial-config,dst=/home/aceuser/initial-config --env ACE_TRUSTSTORE_PASSWORD=truststorepwd --env ACE_KEYSTORE_PASSWORD=keystorepwd aceapp:latest` - -## What is in the sample? - -- **bars_aceonly** contains BAR files for sample applications that don't need MQ. These will be copied into the image (at build time) to `/home/aceuser/bars` and compiled. The Integration Server will pick up and deploy this files on start up. These set of BAR files will be copied when building an image with ACE only or ACE & MQ. -- **dashboards** contains json defined sample grafana and kibana dashboards. This image has a prometheus exporter, which makes information available to prometheus for statistics data. The grafana dashboard gives a sample visualization of this data. The kibana dashboard is based on the json output from an Integration Server (v11.0.0.2). `LOG_FORMAT` env variable has to be set to `json`. -- **initial-config** is a directory that can be mounted by the container. This contains sample configuration files that the container will process on start up. -- **PIs** contain Project Interchange files with the source for the applications that will be loaded from `bars_aceonly` and `bars_mq`. -- **Dockerfile.aceonly** the Dockerfile that builds "FROM" the `ace-only` image and builds an application image. -- **Dockerfile.acemq** the Dockerfile that builds "FROM" the `ace-mq` image and builds an application image. - -## What are the sample applications? -All of the applications either just echo back a timestamp, or call another flow that echoes a timestamp: - -- **HTTPSEcho** - Presents an echo service over a HTTPS endpoint on port 7843. This uses self-signed certificates (from `initial-config/keystore`). You can call this by going to https://localhost:7843/Echo (though you will get an exception about the certificate), or running `curl -k https://localhost:7843/Echo`. This demonstrates how to deploy a flow that presents a HTTPS endpoint, and how to apply a policy and custom `server.conf.yaml` overrides. -- **CallHTTPSEcho** - Wraps a call to `HTTPSEcho` and presents a service over HTTP that is convenient to call. This uses out self-signed CA certificate (from `initial-config/truststore`) to ensure the call to the HTTPS server is trusted. You can call this by going to http://localhost:7800/CallHTTPSEcho, or running `curl http://localhost:7800/CallHTTPSEcho`. This demonstrates how to deploy flow that calls a HTTPS endpoint with specific trust certificates. diff --git a/sample/bars_aceonly/CallHTTPSEcho.bar b/sample/bars_aceonly/CallHTTPSEcho.bar deleted file mode 100644 index a861e040465a3d2d5ae761d95895400150e2c3dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4197 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 diff --git a/sample/bars_aceonly/HTTPSEcho.bar b/sample/bars_aceonly/HTTPSEcho.bar deleted file mode 100644 index 4c6347d91ef7cbe208e28332c3a95ab5647e3b61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4407 zcmaKv1yCH@wuT87f;)rD1b24}!67&d&fpSY(BOn%3BldnU4lEo5-iMMgA*(S4+p|P zfCsngox?q^>aAT})w{a;-+!-OwR?SPU}*`2Jj7URRb=`IQ2< zGuXw!*2cq=%h{HlO9l7Ugt9u9vU-EM>NxAGX0FLmZaglP7EV?+_2yTcT;sfa*s7dt zljBozIv7Zrs!uWc*1$+S_j=;@hvwfan*4t&df2#oJ6PFpy>_+RY-D@}abt}rD`gQ$ zEoBFgvjU7jkIX(Ol z9(Cw^tLFvo$F8>`nM~0&#_S17!X(Lybk$LoIo#WG?x~`7pNP$tb^@5P#qFQYd@(ce z9zKv|AyHa=x5mKHIMs_)Tw}UWFi>>5;G;qwMzPpS!xVUZg#n!eDqaZ%;BTcZ8xO34 zcm)4!D*lDjBVyJ^lK8s$tfhN?(CfS;se~Z*>cBIG^`#8pg|?NSDDRcTVt4ylW+ilC z1(SgQ$#r$|I4!Bm6)ldEBYrp3eiOu&&Jf4+Yg48`MIS%Td;-EC~$Z9MF`UOTur zdDQ4Gxh(Nu2TmDaQ1HCQOUgDTuy(_2Rc`>3Q_LKF&PA5DC9G>KF?2UQ-d7Y?k23Lo zR!?SFIQm%*s#r9(tDC?)>Ria5*OH<5%vyUyx#icMoaZ%IhI^z&3tM5&(FYcQF>LMK z)`0Ll%qDj?OJ!6gWla*>jzJM_kM+&qx*Se2=cyak;H!uN;O`5~?+F-!^0>kqWkxt( zC@hL>St>JWpEzvLRr4Xx*i>@E*uKr2E~2=8+Z*rx=;FFctGPPpocKN***Nbvhjml< zLKzqL+r;qE%5`v#Eu+yuh6kuA--Y8RkIL$&Vp71q)m(5<91@>zOQxum3FY#ssee z&DgDYK^e@$dw;03%LrcaXR&}^ubBTyi5LWu)_*t^zrIC9++`t_$s1;BQ&rM-W9=w^ zKP8=T|MqOh3q5ir-@flDvzs0#PLB?rihE*vmmk6H#$DGr)u};-V8^ciz#5HtZl&8k z*~4b=r+yxceqTx7Q6E>+zc&N?`)X3s)z#FIx3YKT0z)8v4v=ttUy)=dO4@RsBUjF@aurOCaj!X26