diff --git a/.gitignore b/.gitignore index ad46b30..9fd3cc9 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ typings/ # next.js build output .next +dev diff --git a/.travis.yml b/.travis.yml index 00e1d3c..9234ce4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ script: - if [ -n "${TRAVIS_TAG}" ]; then docker tag quay.io/razee/razeedeploy-delta:${TRAVIS_COMMIT} quay.io/razee/razeedeploy-delta:${TRAVIS_TAG}; fi - docker images - ./build/process-template.sh kubernetes/razeedeploy-delta/resource.yaml >/tmp/resource.yaml + - ./build/process-template.sh kubernetes/job/resource.yaml >/tmp/job.yaml before_deploy: - docker login -u="${QUAY_ID}" -p="${QUAY_TOKEN}" quay.io @@ -26,15 +27,6 @@ deploy: on: tags: true condition: ${TRAVIS_TAG} =~ ^[0-9]+\.[0-9]+\.[0-9]+_[0-9]{3}$ - - provider: releases - file: /tmp/resource.yaml - skip_cleanup: true - draft: true - api_key: - secure: "${GITHUB_TOKEN}" - on: - tags: true - condition: ${TRAVIS_TAG} =~ ^[0-9]+\.[0-9]+\.[0-9]+_[0-9]{3}$ # Deploy released builds - provider: script @@ -44,7 +36,9 @@ deploy: tags: true condition: ${TRAVIS_TAG} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ - provider: releases - file: /tmp/resource.yaml + file: + - "/tmp/resource.yaml" + - "/tmp/job.yaml" skip_cleanup: true api_key: secure: "${GITHUB_TOKEN}" diff --git a/kubernetes/job/resource.yaml b/kubernetes/job/resource.yaml new file mode 100644 index 0000000..8d25181 --- /dev/null +++ b/kubernetes/job/resource.yaml @@ -0,0 +1,72 @@ +################################################################################ +# Copyright 2020 IBM Corp. All Rights Reserved. +# +# 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. +################################################################################ +apiVersion: v1 +kind: Namespace +metadata: + name: razeedeploy-job +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: razeedeploy-job-sa + namespace: razeedeploy-job +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: razeedeploy-job-admin-cr +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' +- nonResourceURLs: + - '*' + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: razeedeploy-job-rb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: razeedeploy-job-admin-cr +subjects: +- kind: ServiceAccount + name: razeedeploy-job-sa + namespace: razeedeploy-job +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: razeedeploy-job + namespace: razeedeploy-job +spec: + template: + spec: + serviceAccountName: razeedeploy-job-sa + containers: + - name: razeedeploy-job + image: "quay.io/razee/razeedeploy-delta:{{TRAVIS_TAG}}" +{{=<% %>=}} + command: ["node", {{ COMMAND }}] +<%={{ }}=%> + restartPolicy: Never + backoffLimit: 2 diff --git a/package-lock.json b/package-lock.json index 5eb99f9..19fefa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -229,63 +229,6 @@ "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "dev": true }, - "@kubernetes/client-node": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.11.1.tgz", - "integrity": "sha512-0A4nwErxzJiGt3WYMR6rvcQF46hFz04b6uCmW7Kuj+Cl0zwe7KKxeMiqbZDtHPOq1CcOHOIcKNWCacUKL5CdxQ==", - "requires": { - "@types/js-yaml": "^3.12.1", - "@types/node": "^10.12.0", - "@types/request": "^2.47.1", - "@types/underscore": "^1.8.9", - "@types/ws": "^6.0.1", - "byline": "^5.0.0", - "execa": "1.0.0", - "isomorphic-ws": "^4.0.1", - "js-yaml": "^3.13.1", - "jsonpath-plus": "^0.19.0", - "openid-client": "2.5.0", - "request": "^2.88.0", - "rfc4648": "^1.3.0", - "shelljs": "^0.8.2", - "tslib": "^1.9.3", - "underscore": "^1.9.1", - "ws": "^6.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, "@razee/kubernetes-util": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@razee/kubernetes-util/-/kubernetes-util-0.0.7.tgz", @@ -492,15 +435,6 @@ "humanize-ms": "^1.2.1" } }, - "aggregate-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", - "integrity": "sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w=", - "requires": { - "clean-stack": "^1.0.0", - "indent-string": "^3.0.0" - } - }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -705,16 +639,19 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" - }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -982,11 +919,6 @@ "resolved": "https://registry.npmjs.org/cint/-/cint-8.2.1.tgz", "integrity": "sha1-cDhrG0jidz0NYxZqVa/5TvRFahI=" }, - "clean-stack": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz", - "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE=" - }, "cli-boxes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", @@ -1248,11 +1180,6 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, "decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -1890,6 +1817,14 @@ "readable-stream": "^2.3.6" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -2141,6 +2076,17 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "handlebars": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", + "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==", + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -2183,25 +2129,12 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" - }, "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, "has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", @@ -2336,11 +2269,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2451,22 +2379,6 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" }, - "into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", - "requires": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" - }, - "dependencies": { - "p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" - } - } - }, "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", @@ -2564,11 +2476,6 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" - }, "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", @@ -2577,11 +2484,6 @@ "path-is-inside": "^1.0.1" } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -2597,11 +2499,6 @@ "has": "^1.0.1" } }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" - }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -2841,15 +2738,6 @@ "istanbul-lib-report": "^3.0.0" } }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, "jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", @@ -2936,9 +2824,9 @@ } }, "jsonc-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.0.tgz", - "integrity": "sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", "dev": true }, "jsonfile": { @@ -2992,11 +2880,6 @@ } } }, - "jsonpath-plus": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz", - "integrity": "sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg==" - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3219,11 +3102,6 @@ "chalk": "^2.0.1" } }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -3806,6 +3684,11 @@ "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + }, "nested-error-stacks": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz", @@ -3814,7 +3697,8 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "nise": { "version": "4.0.2", @@ -3921,24 +3805,6 @@ "safe-buffer": "^5.1.1" } }, - "node-forge": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", - "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" - }, - "node-jose": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.1.3.tgz", - "integrity": "sha512-kupfi4uGWhRjnOmtie2T64cLge5a1TZyalEa8uWWWBgtKBcu41A4IGKpI9twZAxRnmviamEUQRK7LSyfFb2w8A==", - "requires": { - "base64url": "^3.0.1", - "es6-promise": "^4.2.6", - "lodash": "^4.17.11", - "long": "^4.0.0", - "node-forge": "^0.8.1", - "uuid": "^3.3.2" - } - }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -4385,11 +4251,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==" - }, "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", @@ -4481,11 +4342,6 @@ } } }, - "oidc-token-hash": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-3.0.2.tgz", - "integrity": "sha512-dTzp80/y/da+um+i+sOucNqiPpwRL7M/xPwj7pH1TFA2/bqQ+OK2sJahSXbemEoLtPkHcFLyhLhLWZa9yW5+RA==" - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4503,98 +4359,19 @@ "mimic-fn": "^2.1.0" } }, - "openid-client": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-2.5.0.tgz", - "integrity": "sha512-t3hFD7xEoW1U25RyBcRFaL19fGGs6hNVTysq9pgmiltH0IVUPzH/bQV9w24pM5Q7MunnGv2/5XjIru6BQcWdxg==", + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "requires": { - "base64url": "^3.0.0", - "got": "^8.3.2", - "lodash": "^4.17.11", - "lru-cache": "^5.1.1", - "node-jose": "^1.1.0", - "object-hash": "^1.3.1", - "oidc-token-hash": "^3.0.1", - "p-any": "^1.1.0" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" }, "dependencies": { - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" - }, - "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", - "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" - } - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", - "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" - } - }, - "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", - "requires": { - "json-buffer": "3.0.0" - } - }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - } - }, - "p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==" + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" } } }, @@ -4677,14 +4454,6 @@ "os-tmpdir": "^1.0.0" } }, - "p-any": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-any/-/p-any-1.1.0.tgz", - "integrity": "sha512-Ef0tVa4CZ5pTAmKn+Cg3w8ABBXh+hHO1aV8281dKOoUHfX+3tjG2EaFcC+aZyagg9b4EYGsHEjz21DnEE8Og2g==", - "requires": { - "p-some": "^2.0.0" - } - }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -4756,22 +4525,6 @@ } } }, - "p-some": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-some/-/p-some-2.0.1.tgz", - "integrity": "sha1-Zdh8ixVO289SIdFnd4ttLhUPbwY=", - "requires": { - "aggregate-error": "^1.0.0" - } - }, - "p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", - "requires": { - "p-finally": "^1.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -5091,16 +4844,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -5373,11 +5116,6 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" }, - "rfc4648": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.3.0.tgz", - "integrity": "sha512-x36K12jOflpm1V8QjPq3I+pt7Z1xzeZIjiC8J2Oxd7bE1efTrOG241DTYVJByP/SxR9jl1t7iZqYxDX864jgBQ==" - }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -5614,14 +5352,6 @@ } } }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5778,11 +5508,6 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -5974,11 +5699,6 @@ "xtend": "~4.0.1" } }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -6089,6 +5809,24 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, + "uglify-js": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz", + "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==", + "optional": true, + "requires": { + "commander": "~2.20.3", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "optional": true + } + } + }, "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", @@ -6158,11 +5896,6 @@ "prepend-http": "^2.0.0" } }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 3768d38..c45b335 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,12 @@ ], "license": "Apache-2.0", "dependencies": { - "@kubernetes/client-node": "^0.11.1", "@razee/kubernetes-util": "^0.0.7", + "axios": "^0.19.2", "bunyan": "^1.8.12", "deepmerge": "^4.2.2", "fs-extra": "^8.0.1", + "handlebars": "^4.7.3", "js-yaml": "^3.13.1", "object-path": "^0.11.4", "request-promise-native": "^1.0.8", diff --git a/src/install.js b/src/install.js new file mode 100644 index 0000000..4ffe8e4 --- /dev/null +++ b/src/install.js @@ -0,0 +1,283 @@ +/** + * Copyright 2020 IBM Corp. All Rights Reserved. + * + * 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. + */ + +var log = require('./bunyan-api').createLogger('create-rd'); +var argv = require('minimist')(process.argv.slice(2)); + +const { KubeClass, KubeApiConfig } = require('@razee/kubernetes-util'); +const kubeApiConfig = KubeApiConfig(); +const kc = new KubeClass(kubeApiConfig); + +const objectPath = require('object-path'); +const yaml = require('js-yaml'); +const fs = require('fs-extra'); +const axios = require('axios'); +const handlebars = require('handlebars'); + +const argvNamespace = typeof (argv.n || argv.namespace) === 'string' ? argv.n || argv.namespace : 'razeedeploy'; + +async function main() { + if (argv.h || argv.help) { + log.info(` +-h, --help + : help menu +-n, --namespace='' + : namespace to populate razeedeploy resources into (Default 'razeedeploy') +--wk, --watch-keeper='' + : install watch-keeper at a specific version (Default 'latest') +--rd-url, --razeedash-url='' + : url that watch-keeper should post data to +--rd-org-key, --razeedash-org-key='' + : org key that watch-keeper will use to authenticate with razeedash-url +--rr, --remoteresource='' + : install remoteresource at a specific version (Default 'latest') +--rrs3, --remoteresources3='' + : install remoteresources3 at a specific version (Default 'latest') +--rrs3d, --remoteresources3decrypt='' + : install remoteresources3decrypt at a specific version (Default 'latest') +--mtp, --mustachetemplate='' + : install mustachetemplate at a specific version (Default 'latest') +--ffsld, --featureflagsetld='' + : install featureflagsetld at a specific version (Default 'latest') +--ms, --managedset='' + : install managedset at a specific version (Default 'latest') +-a, --autoupdate + : will create a remoteresource that will pull and keep specified resources updated to latest (even if a version was specified). if no resources specified, will do all known resources. + `); + return; + } + + let rdUrl = argv['rd-url'] || argv['razeedash-url'] || false; + let rdOrgKey = argv['rd-org-key'] || argv['razeedash-org-key'] || false; + let autoUpdate = argv.a || argv.autoupdate || false; + let autoUpdateArray = []; + + let resourcesObj = { + 'watch-keeper': { install: argv.wk || argv['watch-keeper'], uri: 'https://github.com/razee-io/watch-keeper/releases/{{install_version}}/resource.yaml' }, + 'remoteresource': { install: argv.rr || argv['remoteresource'], uri: 'https://github.com/razee-io/RemoteResource/releases/{{install_version}}/resource.yaml' }, + 'remoteresources3': { install: argv.rrs3 || argv['remoteresources3'], uri: 'https://github.com/razee-io/RemoteResourceS3/releases/{{install_version}}/resource.yaml' }, + 'remoteresources3decrypt': { install: argv.rrs3d || argv['remoteresources3decrypt'], uri: 'https://github.com/razee-io/RemoteResourceS3Decrypt/releases/{{install_version}}/resource.yaml' }, + 'mustachetemplate': { install: argv.mtp || argv['mustachetemplate'], uri: 'https://github.com/razee-io/MustacheTemplate/releases/{{install_version}}/resource.yaml' }, + 'featureflagsetld': { install: argv.ffsld || argv['featureflagsetld'], uri: 'https://github.com/razee-io/FeatureFlagSetLD/releases/{{install_version}}/resource.yaml' }, + 'managedset': { install: argv.ms || argv['managedset'], uri: 'https://github.com/razee-io/ManagedSet/releases/{{install_version}}/resource.yaml' } + }; + + try { + log.info('=========== Installing Prerequisites ==========='); + let preReqsJson = await readYaml('./src/resources/preReqs.yaml', { desired_namespace: argvNamespace }); + await decomposeFile(preReqsJson); + + let resourceUris = Object.values(resourcesObj); + let resources = Object.keys(resourcesObj); + let installAll = resourceUris.reduce((shouldInstallAll, currentValue) => { + return objectPath.get(currentValue, 'install') === undefined ? shouldInstallAll : false; + }, true); + + for (var i = 0; i < resourceUris.length; i++) { + if (installAll || resourceUris[i].install) { + log.info(`=========== Installing ${resources[i]}:${resourceUris[i].install} ===========`); + if (resources[i] === 'watch-keeper') { + if (rdUrl && rdOrgKey) { + let wkConfigJson = await readYaml('./src/resources/wkConfig.yaml', { desired_namespace: argvNamespace, razeedash_url: rdUrl, razeedash_org_key: Buffer.from(rdOrgKey).toString('base64') }); + await decomposeFile(wkConfigJson); + } else { + log.warn('Failed to find args \'--razeedash-url\' and \'--razeedash-org-key\'.. will create template \'watch-keeper-config\' and \'watch-keeper-secret\' if they dont exist.'); + let wkConfigJson = await readYaml('./src/resources/wkConfig.yaml', { desired_namespace: argvNamespace, razeedash_url: 'insert-rd-url-here', razeedash_org_key: Buffer.from('api-key-youorgkeyhere').toString('base64') }); + await decomposeFile(wkConfigJson, 'ensureExists'); + } + } + let { file } = await download(resourceUris[i]); + file = yaml.safeLoadAll(file); + await decomposeFile(file); + if (autoUpdate) { + autoUpdateArray.push({ options: { url: resourceUris[i].uri.replace('{{install_version}}', 'latest/download') } }); + } + } + } + + if (autoUpdate && (installAll || resourcesObj.remoteresource.install)) { // remoteresource must be installed to use autoUpdate + log.info('=========== Installing Auto-Update RemoteResource ==========='); + let autoUpdateJson = await readYaml('./src/resources/autoUpdateRR.yaml', { desired_namespace: argvNamespace }); + objectPath.set(autoUpdateJson, '0.spec.requests', autoUpdateArray); + try { + await crdRegistered('deploy.razee.io/v1alpha2', 'RemoteResource'); + await decomposeFile(autoUpdateJson); + } catch (e) { + log.error(`${e}.. skipping autoUpdate`); + } + } else if (autoUpdate && !(installAll || resourcesObj.remoteresource.install)) { + log.info('=========== Installing Auto-Update RemoteResource ==========='); + log.warn('RemoteResource CRD must be one of the installed resources in order to use RazeeDeploy Create Job. (eg. --rr).. Skipping autoUpdate'); + } + } catch (e) { + log.error(e); + } +} + +async function readYaml(path, templateOptions = {}) { + let yamlFile = await fs.readFile(path, 'utf8'); + let yamlTemplate = handlebars.compile(yamlFile); + let templatedJson = yaml.safeLoadAll(yamlTemplate(templateOptions)); + return templatedJson; +} + +const pause = (duration) => new Promise(res => setTimeout(res, duration)); + +async function crdRegistered(apiVersion, kind, attempts = 5, backoffInterval = 50) { + let krm = (await kc.getKubeResourceMeta(apiVersion, kind, 'get')); + let krmExists = krm ? true : false; + if (krmExists) { + log.info(`Found ${apiVersion} ${kind}`); + return krm; + } else if (--attempts <= 0) { + throw Error(`Failed to find ${apiVersion} ${kind}`); + } else { + log.warn(`Did not find ${apiVersion} ${kind}.. attempts remaining: ${attempts}`); + await pause(backoffInterval); + return crdRegistered(apiVersion, kind, attempts, backoffInterval * 2); + } +} + +async function download(resourceUriObj) { + let install_version = (typeof resourceUriObj.install === 'string' && resourceUriObj.install.toLowerCase() !== 'latest') ? `download/${resourceUriObj.install}` : 'latest/download'; + let uri = resourceUriObj.uri.replace('{{install_version}}', install_version); + try { + log.info(`Downloading ${uri}`); + return { file: (await axios.get(uri)).data, uri: uri }; + } catch (e) { + let latestUri = resourceUriObj.uri.replace('{{install_version}}', 'latest/download'); + log.warn(`Failed to download ${uri}.. defaulting to ${latestUri}`); + return { file: (await axios.get(latestUri)).data, uri: latestUri }; + } + +} + +async function decomposeFile(file, mode = 'replace') { + let kind = objectPath.get(file, ['kind'], ''); + let apiVersion = objectPath.get(file, ['apiVersion'], ''); + let items = objectPath.get(file, ['items']); + + if (Array.isArray(file)) { + for (let i = 0; i < file.length; i++) { + await decomposeFile(file[i], mode); + } + } else if (kind.toLowerCase() == 'list' && Array.isArray(items)) { + for (let i = 0; i < items.length; i++) { + await decomposeFile(items[i], mode); + } + } else if (file) { + let krm = await kc.getKubeResourceMeta(apiVersion, kind, 'update'); + if (krm) { + if (!objectPath.has(file, 'metadata.namespace') && krm.namespaced) { + log.info(`No namespace found for ${kind} ${objectPath.get(file, 'metadata.name')}.. setting namespace: ${argvNamespace}`); + objectPath.set(file, 'metadata.namespace', argvNamespace); + } + try { + if (mode === 'ensureExists') { + await ensureExists(krm, file); + } else { + await replace(krm, file); + } + } catch (e) { + log.error(e); + } + } else { + log.error(`KubeResourceMeta not found: { kind: ${kind}, apiVersion: ${apiVersion}, name: ${objectPath.get(file, 'metadata.name')}, namespace: ${objectPath.get(file, 'metadata.namespace')} } ... skipping`); + } + } +} + +async function replace(krm, file, options = {}) { + let name = objectPath.get(file, 'metadata.name'); + let namespace = objectPath.get(file, 'metadata.namespace'); + let uri = krm.uri({ name: name, namespace: namespace, status: options.status }); + log.info(`Replace ${uri}`); + let response = {}; + let opt = { simple: false, resolveWithFullResponse: true }; + let liveMetadata; + log.info(`- Get ${uri}`); + let get = await krm.get(name, namespace, opt); + if (get.statusCode === 200) { + liveMetadata = objectPath.get(get, 'body.metadata'); + log.info(`- Get ${get.statusCode} ${uri}: resourceVersion ${objectPath.get(get, 'body.metadata.resourceVersion')}`); + } else if (get.statusCode === 404) { + log.info(`- Get ${get.statusCode} ${uri}`); + } else { + log.info(`- Get ${get.statusCode} ${uri}`); + return Promise.reject({ statusCode: get.statusCode, body: get.body }); + } + + if (liveMetadata) { + objectPath.set(file, 'metadata.resourceVersion', objectPath.get(liveMetadata, 'resourceVersion')); + + log.info(`- Put ${uri}`); + let put = await krm.put(file, opt); + if (!(put.statusCode === 200 || put.statusCode === 201)) { + log.info(`- Put ${put.statusCode} ${uri}`); + return Promise.reject({ statusCode: put.statusCode, body: put.body }); + } else { + log.info(`- Put ${put.statusCode} ${uri}`); + response = { statusCode: put.statusCode, body: put.body }; + } + } else { + log.info(`- Post ${uri}`); + let post = await krm.post(file, opt); + if (!(post.statusCode === 200 || post.statusCode === 201 || post.statusCode === 202)) { + log.info(`- Post ${post.statusCode} ${uri}`); + return Promise.reject({ statusCode: post.statusCode, body: post.body }); + } else { + log.info(`- Post ${post.statusCode} ${uri}`); + response = { statusCode: post.statusCode, body: post.body }; + } + } + return response; +} + +async function ensureExists(krm, file, options = {}) { + let name = objectPath.get(file, 'metadata.name'); + let namespace = objectPath.get(file, 'metadata.namespace'); + let uri = krm.uri({ name: name, namespace: namespace, status: options.status }); + log.info(`EnsureExists ${uri}`); + let response = {}; + let opt = { simple: false, resolveWithFullResponse: true }; + + let get = await krm.get(name, namespace, opt); + if (get.statusCode === 200) { + log.info(`- Get ${get.statusCode} ${uri}`); + return { statusCode: get.statusCode, body: get.body }; + } else if (get.statusCode === 404) { // not found -> must create + log.info(`- Get ${get.statusCode} ${uri}`); + } else { + log.info(`- Get ${get.statusCode} ${uri}`); + return Promise.reject({ statusCode: get.statusCode, body: get.body }); + } + + log.info(`- Post ${uri}`); + let post = await krm.post(file, opt); + if (post.statusCode === 200 || post.statusCode === 201 || post.statusCode === 202) { + log.info(`- Post ${post.statusCode} ${uri}`); + return { statusCode: post.statusCode, body: post.body }; + } else if (post.statusCode === 409) { // already exists + log.info(`- Post ${post.statusCode} ${uri}`); + response = { statusCode: 200, body: post.body }; + } else { + log.info(`- Post ${post.statusCode} ${uri}`); + return Promise.reject({ statusCode: post.statusCode, body: post.body }); + } + return response; +} + +main().catch(log.error); diff --git a/src/remove.js b/src/remove.js new file mode 100644 index 0000000..34d21bf --- /dev/null +++ b/src/remove.js @@ -0,0 +1,252 @@ +/** + * Copyright 2020 IBM Corp. All Rights Reserved. + * + * 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. + */ + +var log = require('./bunyan-api').createLogger('create-rd'); +var argv = require('minimist')(process.argv.slice(2)); + +const { KubeClass, KubeApiConfig } = require('@razee/kubernetes-util'); +const kubeApiConfig = KubeApiConfig(); +const kc = new KubeClass(kubeApiConfig); + +const objectPath = require('object-path'); +const yaml = require('js-yaml'); +const fs = require('fs-extra'); +const axios = require('axios'); +const handlebars = require('handlebars'); + +const argvNamespace = typeof (argv.n || argv.namespace) === 'string' ? argv.n || argv.namespace : 'razeedeploy'; + +async function main() { + if (argv.h || argv.help) { + log.info(` +-h, --help + : help menu +-n, --namespace='' + : namespace to remove razeedeploy resources from (Default 'razeedeploy') +--dn, --delete-namespace='' + : include namespace as a resource to delete (Default false) +-t, --timeout + : time (minutes) before failing to delete CRD (Default 5) +-a, --attempts + : number of attempts to verify CRD is deleted before failing (Default 5) +-f, --force + : force delete the CRD and CR instances without allowing the controller to clean up children (Default false) + `); + return; + } + + let resourcesObj = { + 'watch-keeper': { remove: argv.wk || argv['watch-keeper'], uri: 'https://github.com/razee-io/watch-keeper/releases/{{install_version}}/resource.yaml' }, + 'remoteresource': { remove: argv.rr || argv['remoteresource'], uri: 'https://github.com/razee-io/RemoteResource/releases/{{install_version}}/resource.yaml' }, + 'remoteresources3': { remove: argv.rrs3 || argv['remoteresources3'], uri: 'https://github.com/razee-io/RemoteResourceS3/releases/{{install_version}}/resource.yaml' }, + 'remoteresources3decrypt': { remove: argv.rrs3d || argv['remoteresources3decrypt'], uri: 'https://github.com/razee-io/RemoteResourceS3Decrypt/releases/{{install_version}}/resource.yaml' }, + 'mustachetemplate': { remove: argv.mtp || argv['mustachetemplate'], uri: 'https://github.com/razee-io/MustacheTemplate/releases/{{install_version}}/resource.yaml' }, + 'featureflagsetld': { remove: argv.ffsld || argv['featureflagsetld'], uri: 'https://github.com/razee-io/FeatureFlagSetLD/releases/{{install_version}}/resource.yaml' }, + 'managedset': { remove: argv.ms || argv['managedset'], uri: 'https://github.com/razee-io/ManagedSet/releases/{{install_version}}/resource.yaml' } + }; + + let dltNamespace = typeof (argv.dn || argv['delete-namespace']) === 'boolean' ? argv.dn || argv['delete-namespace'] : false; + let force = typeof (argv.f || argv['force']) === 'boolean' ? argv.f || argv['force'] : false; + + let attempts = typeof (argv.a || argv.attempts) === 'number' ? argv.a || argv.attempts : 5; + let timeout = typeof (argv.t || argv.timeout) === 'number' ? argv.t || argv.timeout : 5; + let timeoutMillSec = timeout * 60 * 1000; + let backoff = Math.floor(timeoutMillSec / Math.pow(2, attempts - 1)); + + try { + let resourceUris = Object.values(resourcesObj); + let resources = Object.keys(resourcesObj); + let removeAll = resourceUris.reduce((shouldInstallAll, currentValue) => { + return objectPath.get(currentValue, 'remove') === undefined ? shouldInstallAll : false; + }, true); + + for (let i = 0; i < resourceUris.length; i++) { + if (removeAll || resourceUris[i].remove) { + log.info(`=========== Removing ${resources[i]}:${resourceUris[i].remove} ===========`); + if (resources[i] === 'watch-keeper') { + let wkConfigJson = await readYaml('./src/resources/wkConfig.yaml', { desired_namespace: argvNamespace }); + await deleteFile(wkConfigJson); + } + let { file } = await download(resourceUris[i]); + file = yaml.safeLoadAll(file); + let flatFile = flatten(file); + let crdIndex = flatFile.findIndex((el) => objectPath.get(el, 'kind') === 'CustomResourceDefinition'); + if (crdIndex >= 0) { + let crd = flatFile.splice(crdIndex, 1)[0]; + try { + await deleteFile(crd); + if (force) { + await forceCleanupCR(crd); + } else { + await crdDeleted(objectPath.get(crd, 'metadata.name'), attempts, backoff); + } + } catch (e) { + log.error(`Timed out trying to safely clean up crd ${objectPath.get(crd, 'metadata.name')}.. use option '-f, --force' to force clean up (note: child resources wont be cleaned up)`); + } + } + await deleteFile(flatFile); + } + } + + log.info('=========== Removing Prerequisites ==========='); + let preReqsJson = await readYaml('./src/resources/preReqs.yaml', { desired_namespace: argvNamespace }); + for (let i = 0; i < preReqsJson.length; i++) { + let preReq = preReqsJson[i]; + let kind = objectPath.get(preReq, 'kind'); + if ((kind.toLowerCase() !== 'namespace') || (kind.toLowerCase() === 'namespace' && dltNamespace)) { + await deleteFile(preReq); + } else { + log.info(`Skipping namespace deletion: --namespace='${argvNamespace}' --delete-namespace='${dltNamespace}'`); + } + } + + } catch (e) { + log.error(e); + } +} + +async function readYaml(path, templateOptions = {}) { + let yamlFile = await fs.readFile(path, 'utf8'); + let yamlTemplate = handlebars.compile(yamlFile); + let templatedJson = yaml.safeLoadAll(yamlTemplate(templateOptions)); + return templatedJson; +} + +async function forceCleanupCR(crd) { + let group = objectPath.get(crd, 'spec.group', 'deploy.razee.io'); + let versions = objectPath.get(crd, 'spec.versions', []); + let kind = objectPath.get(crd, 'spec.names.kind', ''); + + for (let i = 0; i < versions.length; i++) { + let apiVersion = `${group}/${versions[i].name}`; + let krm = versions[i].storage ? await kc.getKubeResourceMeta(apiVersion, kind, 'get') : undefined; + if (krm) { + let crs = await krm.get(); + await deleteFile(crs, true); + } + } +} + +const pause = (duration) => new Promise(res => setTimeout(res, duration)); + +async function crdDeleted(name, attempts = 5, backoffInterval = 3750) { + let krm = await kc.getKubeResourceMeta('apiextensions.k8s.io/v1beta1', 'CustomResourceDefinition', 'get'); + let crdDltd = (await krm.get(name, undefined, { simple: false, resolveWithFullResponse: true })).statusCode === 404 ? true : false; + if (crdDltd) { + log.info(`Successfully deleted ${name}`); + return; + } else if (--attempts <= 0) { + throw Error(`Failed to delete ${name}`); + } else { + log.warn(`CRD ${name} not fully removed.. re-checking in: ${backoffInterval/1000} sec, attempts remaining: ${attempts}`); + await pause(backoffInterval); + return crdDeleted(name, attempts, backoffInterval * 2); + } +} + +async function download(resourceUriObj) { + let install_version = (typeof resourceUriObj.install === 'string' && resourceUriObj.install.toLowerCase() !== 'latest') ? `download/${resourceUriObj.install}` : 'latest/download'; + let uri = resourceUriObj.uri.replace('{{install_version}}', install_version); + try { + log.info(`Downloading ${uri}`); + return { file: (await axios.get(uri)).data, uri: uri }; + } catch (e) { + let latestUri = resourceUriObj.uri.replace('{{install_version}}', 'latest/download'); + log.warn(`Failed to download ${uri}.. defaulting to ${latestUri}`); + return { file: (await axios.get(latestUri)).data, uri: latestUri }; + } +} + +function flatten(file) { + return file.reduce((result, current) => { + let kind = objectPath.get(current, ['kind'], ''); + let items = objectPath.get(current, ['items']); + + if (Array.isArray(current)) { + return result.concat(flatten(current)); + } else if (kind.toLowerCase().endsWith('list') && Array.isArray(items)) { + return result.concat(flatten(items)); + } else { + return result.concat(current); + } + }, []); +} + +async function deleteFile(file, force = false) { + let kind = objectPath.get(file, ['kind'], ''); + let apiVersion = objectPath.get(file, ['apiVersion'], ''); + let items = objectPath.get(file, ['items']); + + if (Array.isArray(file)) { + for (let i = 0; i < file.length; i++) { + await deleteFile(file[i], force); + } + } else if (kind.toLowerCase().endsWith('list') && Array.isArray(items)) { + for (let i = 0; i < items.length; i++) { + await deleteFile(items[i], force); + } + } else if (file) { + let krm = await kc.getKubeResourceMeta(apiVersion, kind, 'delete'); + if (krm) { + if (!objectPath.has(file, 'metadata.namespace') && krm.namespaced) { + log.info(`No namespace found for ${kind} ${objectPath.get(file, 'metadata.name')}.. setting namespace: ${argvNamespace}`); + objectPath.set(file, 'metadata.namespace', argvNamespace); + } + try { + await deleteResource(krm, file, { force: force }); + } catch (e) { + log.error(e); + } + } else { + log.error(`KubeResourceMeta not found: { kind: ${kind}, apiVersion: ${apiVersion}, name: ${objectPath.get(file, 'metadata.name')}, namespace: ${objectPath.get(file, 'metadata.namespace')} } ... skipping`); + } + } +} + +async function deleteResource(krm, file, options = {}) { + let name = objectPath.get(file, 'metadata.name'); + let namespace = objectPath.get(file, 'metadata.namespace'); + let uri = krm.uri({ name: name, namespace: namespace, status: options.status }); + log.info(`Delete ${uri}`); + let opt = { simple: false, resolveWithFullResponse: true }; + + if (options.force) { + let mrgPtch = await krm.mergePatch(name, namespace, { metadata: { finalizers: null } }, opt); + if (mrgPtch.statusCode === 200) { + log.info(`- MergePatch ${mrgPtch.statusCode} ${uri}`); + } else if (mrgPtch.statusCode === 404) { // not found -> already gone + log.info(`- MergePatch ${mrgPtch.statusCode} ${uri}`); + return { statusCode: mrgPtch.statusCode, body: mrgPtch.body }; + } else { + log.info(`- MergePatch ${mrgPtch.statusCode} ${uri}`); + return Promise.reject({ statusCode: mrgPtch.statusCode, body: mrgPtch.body }); + } + } + + let dlt = await krm.delete(name, namespace, opt); + if (dlt.statusCode === 200) { + log.info(`- Delete ${dlt.statusCode} ${uri}`); + return { statusCode: dlt.statusCode, body: dlt.body }; + } else if (dlt.statusCode === 404) { // not found -> already gone + log.info(`- Delete ${dlt.statusCode} ${uri}`); + return { statusCode: dlt.statusCode, body: dlt.body }; + } else { + log.info(`- Delete ${dlt.statusCode} ${uri}`); + return Promise.reject({ statusCode: dlt.statusCode, body: dlt.body }); + } +} + +main().catch(log.error); diff --git a/src/resources/autoUpdateRR.yaml b/src/resources/autoUpdateRR.yaml new file mode 100644 index 0000000..28de850 --- /dev/null +++ b/src/resources/autoUpdateRR.yaml @@ -0,0 +1,24 @@ +################################################################################ +# Copyright 2020 IBM Corp. All Rights Reserved. +# +# 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. +################################################################################ +apiVersion: "deploy.razee.io/v1alpha2" +kind: RemoteResource +metadata: + name: razeedeploy-auto-update + namespace: "{{ desired_namespace }}" + labels: + deploy.razee.io/Reconcile: 'false' +spec: + requests: [] diff --git a/src/resources/preReqs.yaml b/src/resources/preReqs.yaml new file mode 100644 index 0000000..8eebe81 --- /dev/null +++ b/src/resources/preReqs.yaml @@ -0,0 +1,87 @@ +################################################################################ +# Copyright 2020 IBM Corp. All Rights Reserved. +# +# 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. +################################################################################ +apiVersion: v1 +kind: Namespace +metadata: + name: "{{ desired_namespace }}" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: razeedeploy-sa + namespace: "{{ desired_namespace }}" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: razeedeploy-admin-cr +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' +- nonResourceURLs: + - '*' + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: razeedeploy-rb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: razeedeploy-admin-cr +subjects: +- kind: ServiceAccount + name: razeedeploy-sa + namespace: "{{ desired_namespace }}" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: watch-keeper-sa + namespace: "{{ desired_namespace }}" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cluster-reader +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: ["get", "list", "watch"] +- nonResourceURLs: + - '*' + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: watch-keeper-rb +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-reader +subjects: +- kind: ServiceAccount + name: watch-keeper-sa + namespace: "{{ desired_namespace }}" diff --git a/src/resources/wkConfig.yaml b/src/resources/wkConfig.yaml new file mode 100644 index 0000000..12530d4 --- /dev/null +++ b/src/resources/wkConfig.yaml @@ -0,0 +1,31 @@ +################################################################################ +# Copyright 2020 IBM Corp. All Rights Reserved. +# +# 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. +################################################################################ +apiVersion: v1 +kind: ConfigMap +metadata: + name: watch-keeper-config + namespace: "{{ desired_namespace }}" +data: + RAZEEDASH_URL: "{{{ razeedash_url }}}" + START_DELAY_MAX: "0" +--- +apiVersion: v1 +kind: Secret +metadata: + name: watch-keeper-secret + namespace: "{{ desired_namespace }}" +data: + RAZEEDASH_ORG_KEY: "{{{ razeedash_org_key }}}"