From 2eabd4fd079036e3119a56d39fe49c3d47084543 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Tue, 18 Jan 2022 08:05:03 +0100 Subject: [PATCH 01/24] Identify Solaris goos, 386, mipsle and amd goarch --- __tests__/go.test.ts | 6 +++--- package-lock.json | 20 ++++++++++++------ src/go.ts | 48 +++++++++++++++++++++++++++++--------------- src/kind/main.ts | 6 ++---- src/main.ts | 4 ++-- 5 files changed, 53 insertions(+), 31 deletions(-) diff --git a/__tests__/go.test.ts b/__tests__/go.test.ts index e083103b..b8b748f9 100644 --- a/__tests__/go.test.ts +++ b/__tests__/go.test.ts @@ -1,11 +1,11 @@ -import * as go from '../src/go'; +import { env as goenv } from '../src/go'; describe('checking go env simulation', function () { it('correctly parse os', () => { - expect(['windows', 'darwin', 'linux']).toContain(go.goos()); + expect(['windows', 'darwin', 'linux']).toContain(goenv.GOOS); }); it('correctly parse arch', () => { - expect(['amd64', 'arm64']).toContain(go.goarch()); + expect(['amd64', 'arm64']).toContain(goenv.GOARCH); }); }); diff --git a/package-lock.json b/package-lock.json index dfd988e3..187ab3f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4809,14 +4809,22 @@ "dev": true }, "node_modules/node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dependencies": { "whatwg-url": "^5.0.0" }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-fetch/node_modules/tr46": { @@ -9901,9 +9909,9 @@ "dev": true }, "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { "whatwg-url": "^5.0.0" }, diff --git a/src/go.ts b/src/go.ts index 1bee0caf..5e74a2dc 100644 --- a/src/go.ts +++ b/src/go.ts @@ -5,8 +5,15 @@ import process from 'process'; * Simulate the calculation of the goos * @returns go env GOOS */ -export function goos(): string { - return process.platform == 'win32' ? 'windows' : process.platform; +function _goos(platform: string): string { + switch (platform) { + case 'sunos': + return 'solaris'; + case 'win32': + return 'windows'; + default: + return platform; + } } /** @@ -14,27 +21,36 @@ export function goos(): string { * Based on https://nodejs.org/api/process.html#processarch * @returns go env GOARCH */ -export function goarch(): string { - const architecture = process.arch; +function _goarch(architecture: string, endianness: string): string { switch (architecture) { + case 'ia32': + return '386'; + case 'x32': + return 'amd'; case 'x64': return 'amd64'; case 'arm': - if (os.endianness().toLowerCase() === 'be') { - return 'armbe'; - } - return architecture; + return _withEndiannessOrDefault(architecture, endianness, 'be'); case 'arm64': - if (os.endianness().toLowerCase() === 'be') { - return 'arm64be'; - } - return architecture; + return _withEndiannessOrDefault(architecture, endianness, 'be'); + case 'mips': + return _withEndiannessOrDefault(architecture, endianness, 'le'); case 'ppc64': - if (os.endianness().toLowerCase() === 'le') { - return 'ppc64le'; - } - return architecture; + return _withEndiannessOrDefault(architecture, endianness, 'le'); default: return architecture; } } + +function _withEndiannessOrDefault( + architecture: string, + endianness: string, + suffix: string +): string { + return endianness === suffix ? architecture + suffix : architecture; +} + +export const env = { + GOARCH: _goarch(process.arch, os.endianness().toLowerCase()), + GOOS: _goos(process.platform), +}; diff --git a/src/kind/main.ts b/src/kind/main.ts index 8113d42d..bb76eb12 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -12,7 +12,7 @@ import { KIND_DEFAULT_VERSION, KIND_TOOL_NAME, } from '../constants'; -import * as go from '../go'; +import { env as goenv } from '../go'; import { executeKindCommand, KIND_COMMAND } from './core'; export class KindMainService { @@ -109,9 +109,7 @@ export class KindMainService { // this action should always be run from a Linux worker private async downloadKind(): Promise { - const url = `https://github.com/kubernetes-sigs/kind/releases/download/${ - this.version - }/kind-${go.goos()}-${go.goarch()}`; + const url = `https://github.com/kubernetes-sigs/kind/releases/download/${this.version}/kind-${goenv.GOOS}-${goenv.GOARCH}`; console.log('downloading kind from ' + url); const downloadPath = await tc.downloadTool(url); if (process.platform !== 'win32') { diff --git a/src/main.ts b/src/main.ts index f7bf5cb1..a290331c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core'; import * as io from '@actions/io'; import { ok } from 'assert'; -import * as go from './go'; +import { env as goenv } from './go'; import { KindMainService } from './kind/main'; async function run() { @@ -18,7 +18,7 @@ async function run() { function checkEnvironment() { const supportedPlatforms: string[] = ['linux/amd64', 'linux/arm64']; - const platform = `${go.goos()}/${go.goarch()}`; + const platform = `${goenv.GOOS}/${goenv.GOARCH}`; ok( supportedPlatforms.includes(platform), `Platform "${platform}" is not supported` From 04f2de3258049014cc76f3ed0424f0ef25f4d3de Mon Sep 17 00:00:00 2001 From: Matthieu Morel Date: Tue, 25 Jan 2022 10:21:49 +0100 Subject: [PATCH 02/24] externalize requirements from main.ts --- .github/workflows/e2e.yml | 4 +- __tests__/kind/main.test.ts | 2 - __tests__/requirements.test.ts | 33 ++++ action.yml | 4 + package-lock.json | 300 +++++++++++++++++++++++++++++++++ package.json | 3 + src/constants.ts | 1 + src/kind/main.ts | 59 +------ src/main.ts | 30 +--- src/requirements.ts | 173 +++++++++++++++++++ 10 files changed, 527 insertions(+), 82 deletions(-) create mode 100644 __tests__/requirements.test.ts create mode 100644 src/requirements.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b1d2b8c9..25a0a344 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,4 +1,4 @@ -name: "Create cluster using KinD" +name: 'Create cluster using KinD' on: [pull_request, push] jobs: @@ -15,7 +15,7 @@ jobs: - run: npm run build - - name: "Run engineerd/setup-kind@${{github.sha}}" + - name: 'Run engineerd/setup-kind@${{github.sha}}' uses: ./ - run: kubectl cluster-info diff --git a/__tests__/kind/main.test.ts b/__tests__/kind/main.test.ts index 5ad4633a..7a258014 100644 --- a/__tests__/kind/main.test.ts +++ b/__tests__/kind/main.test.ts @@ -4,7 +4,6 @@ import { KindMainService } from '../../src/kind/main'; const testEnvVars = { INPUT_VERBOSITY: '3', INPUT_QUIET: 'true', - INPUT_VERSION: 'v0.5.3', INPUT_CONFIG: 'some-path', INPUT_IMAGE: 'some-docker-image', INPUT_NAME: 'some-name', @@ -30,7 +29,6 @@ describe('checking input parsing', function () { it('correctly parse input', () => { const service: KindMainService = KindMainService.getInstance(); - expect(service.version).toEqual(testEnvVars.INPUT_VERSION); expect(service.configFile).toEqual(testEnvVars.INPUT_CONFIG); expect(service.image).toEqual(testEnvVars.INPUT_IMAGE); expect(service.name).toEqual(testEnvVars.INPUT_NAME); diff --git a/__tests__/requirements.test.ts b/__tests__/requirements.test.ts new file mode 100644 index 00000000..0966e801 --- /dev/null +++ b/__tests__/requirements.test.ts @@ -0,0 +1,33 @@ +import { checkEnvironment } from '../src/requirements'; + +const testEnvVars = { + INPUT_VERSION: 'v0.7.0', + GITHUB_JOB: 'kind', + GITHUB_WORKSPACE: '/home/runner/repo', + RUNNER_OS: 'Linux', + RUNNER_ARCH: 'X64', + RUNNER_TEMP: '/home/runner/work/_temp', + RUNNER_TOOL_CACHE: '/opt/hostedtoolcache', +}; + +describe('checking requirements', function () { + const originalEnv = process.env; + beforeEach(() => { + jest.resetModules(); + process.env = { + ...originalEnv, + ...testEnvVars, + }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('required GITHUB_JOB must be defined', async () => { + process.env['GITHUB_JOB'] = ''; + await expect(checkEnvironment()).rejects.toThrow( + 'Expected GITHUB_JOB to be defined' + ); + }); +}); diff --git a/action.yml b/action.yml index c9d33a1e..4e2dd144 100644 --- a/action.yml +++ b/action.yml @@ -34,6 +34,10 @@ inputs: quiet: description: "Silence all stderr output" default: "false" + token: + description: "Used to retrieve release informations concerning KinD" + default: "${{ github.token }}" + required: true runs: using: "node12" main: "dist/main/index.js" diff --git a/package-lock.json b/package-lock.json index 187ab3f2..9c2998b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,13 +13,16 @@ "@actions/cache": "^1.0.8", "@actions/core": "^1.6.0", "@actions/exec": "^1.1.0", + "@actions/github": "^5.0.0", "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", + "@octokit/rest": "^18.10.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { + "@octokit/types": "^6.28.1", "@types/jest": "^27.4.0", "@types/node": "^17.0.8", "@types/semver": "^7.3.9", @@ -117,6 +120,17 @@ "@actions/io": "^1.0.1" } }, + "node_modules/@actions/github": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", + "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", + "dependencies": { + "@actions/http-client": "^1.0.11", + "@octokit/core": "^3.4.0", + "@octokit/plugin-paginate-rest": "^2.13.3", + "@octokit/plugin-rest-endpoint-methods": "^5.1.1" + } + }, "node_modules/@actions/glob": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.2.0.tgz", @@ -1381,6 +1395,126 @@ "node": ">= 8" } }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.2.2.tgz", + "integrity": "sha512-EVcXQ+ZrC04cg17AMg1ofocWMxHDn17cB66ZHgYc0eUwjFtxS0oBzkyw2VqIrHBwVgtfoYrq1WMQfQmMjUwthw==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.3.tgz", + "integrity": "sha512-kdc65UEsqze/9fCISq6BxLzeB9qf0vKvKojIfzgwf4tEF+Wy6c9dXnPFE6vgpoDFB1Z5Jek5WFVU6vL1w22+Iw==", + "dependencies": { + "@octokit/types": "^6.28.1" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.10.4.tgz", + "integrity": "sha512-Dh+EAMCYR9RUHwQChH94Skl0lM8Fh99auT8ggck/xTzjJrwVzvsd0YH68oRPqp/HxICzmUjLfaQ9sy1o1sfIiA==", + "dependencies": { + "@octokit/types": "^6.28.1", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz", + "integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/rest": { + "version": "18.10.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.10.0.tgz", + "integrity": "sha512-esHR5OKy38bccL/sajHqZudZCvmv4yjovMJzyXlphaUo7xykmtOdILGJ3aAm0mFHmMLmPFmDMJXf39cAjNJsrw==", + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.0", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.9.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.28.1.tgz", + "integrity": "sha512-XlxDoQLFO5JnFZgKVQTYTvXRsQFfr/GwDUU108NJ9R5yFPkA2qXhTJjYuul3vE4eLXP40FA2nysOu2zd6boE+w==", + "dependencies": { + "@octokit/openapi-types": "^10.2.2" + } + }, "node_modules/@opentelemetry/api": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", @@ -2108,6 +2242,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2559,6 +2698,11 @@ "node": ">=0.4.0" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3630,6 +3774,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -5951,6 +6103,11 @@ "node": ">=4.2.0" } }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -6319,6 +6476,17 @@ "@actions/io": "^1.0.1" } }, + "@actions/github": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", + "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", + "requires": { + "@actions/http-client": "^1.0.11", + "@octokit/core": "^3.4.0", + "@octokit/plugin-paginate-rest": "^2.13.3", + "@octokit/plugin-rest-endpoint-methods": "^5.1.1" + } + }, "@actions/glob": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.2.0.tgz", @@ -7338,6 +7506,118 @@ "fastq": "^1.6.0" } }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.2.2.tgz", + "integrity": "sha512-EVcXQ+ZrC04cg17AMg1ofocWMxHDn17cB66ZHgYc0eUwjFtxS0oBzkyw2VqIrHBwVgtfoYrq1WMQfQmMjUwthw==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.3.tgz", + "integrity": "sha512-kdc65UEsqze/9fCISq6BxLzeB9qf0vKvKojIfzgwf4tEF+Wy6c9dXnPFE6vgpoDFB1Z5Jek5WFVU6vL1w22+Iw==", + "requires": { + "@octokit/types": "^6.28.1" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "requires": {} + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.10.4.tgz", + "integrity": "sha512-Dh+EAMCYR9RUHwQChH94Skl0lM8Fh99auT8ggck/xTzjJrwVzvsd0YH68oRPqp/HxICzmUjLfaQ9sy1o1sfIiA==", + "requires": { + "@octokit/types": "^6.28.1", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz", + "integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "18.10.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.10.0.tgz", + "integrity": "sha512-esHR5OKy38bccL/sajHqZudZCvmv4yjovMJzyXlphaUo7xykmtOdILGJ3aAm0mFHmMLmPFmDMJXf39cAjNJsrw==", + "requires": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.0", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.9.0" + } + }, + "@octokit/types": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.28.1.tgz", + "integrity": "sha512-XlxDoQLFO5JnFZgKVQTYTvXRsQFfr/GwDUU108NJ9R5yFPkA2qXhTJjYuul3vE4eLXP40FA2nysOu2zd6boE+w==", + "requires": { + "@octokit/openapi-types": "^10.2.2" + } + }, "@opentelemetry/api": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", @@ -7884,6 +8164,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -8233,6 +8518,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -9003,6 +9293,11 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -10705,6 +11000,11 @@ "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index ef6ef3ae..570d9569 100644 --- a/package.json +++ b/package.json @@ -35,13 +35,16 @@ "@actions/cache": "^1.0.8", "@actions/core": "^1.6.0", "@actions/exec": "^1.1.0", + "@actions/github": "^5.0.0", "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", + "@octokit/rest": "^18.10.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { + "@octokit/types": "^6.28.1", "@types/jest": "^27.4.0", "@types/node": "^17.0.8", "@types/semver": "^7.3.9", diff --git a/src/constants.ts b/src/constants.ts index 5e88fbeb..58ecf65c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,6 +5,7 @@ export enum Input { Config = 'config', Image = 'image', Name = 'name', + Token = 'token', Wait = 'wait', KubeConfig = 'kubeconfig', SkipClusterCreation = 'skipClusterCreation', diff --git a/src/kind/main.ts b/src/kind/main.ts index bb76eb12..80986181 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -1,22 +1,14 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; import * as tc from '@actions/tool-cache'; -import { ok } from 'assert'; import path from 'path'; import process from 'process'; -import * as semver from 'semver'; import * as cache from '../cache'; -import { - Flag, - Input, - KIND_DEFAULT_VERSION, - KIND_TOOL_NAME, -} from '../constants'; +import { Flag, Input, KIND_TOOL_NAME } from '../constants'; import { env as goenv } from '../go'; import { executeKindCommand, KIND_COMMAND } from './core'; export class KindMainService { - version: string; configFile: string; image: string; name: string; @@ -27,11 +19,8 @@ export class KindMainService { quiet: boolean; private constructor() { - this.version = core.getInput(Input.Version, { required: true }); - this.checkVersion(); this.configFile = core.getInput(Input.Config); this.image = core.getInput(Input.Image); - this.checkImage(); this.name = core.getInput(Input.Name, { required: true }); this.waitDuration = core.getInput(Input.Wait); this.kubeConfigFile = core.getInput(Input.KubeConfig); @@ -45,38 +34,6 @@ export class KindMainService { return new KindMainService(); } - /** - * Verify that the version of kind is a valid semver and prints a warning if the kind version used is older than the default for setup-kind - */ - private checkVersion() { - const cleanVersion = semver.clean(this.version); - ok( - cleanVersion, - `Input ${Input.Version} expects a valid version like ${KIND_DEFAULT_VERSION}` - ); - if (semver.lt(this.version, KIND_DEFAULT_VERSION)) { - core.warning( - `Kind ${KIND_DEFAULT_VERSION} is available, have you considered using it ? See https://github.com/kubernetes-sigs/kind/releases/tag/${KIND_DEFAULT_VERSION}` - ); - } - } - - /** - * Prints a warning if a kindest/node is used without sha256. - * This follows the recommendation from https://kind.sigs.k8s.io/docs/user/working-offline/#using-a-prebuilt-node-imagenode-image - */ - private checkImage() { - if ( - this.image !== '' && - this.image.startsWith('kindest/node') && - !this.image.includes('@sha256:') - ) { - core.warning( - `Please include the @sha256: image digest from the image in the release notes. You can find available image tags on the release page, https://github.com/kubernetes-sigs/kind/releases/tag/${this.version}` - ); - } - } - // returns the arguments to pass to `kind create cluster` createCommand(): string[] { const args: string[] = ['create', 'cluster']; @@ -108,8 +65,8 @@ export class KindMainService { } // this action should always be run from a Linux worker - private async downloadKind(): Promise { - const url = `https://github.com/kubernetes-sigs/kind/releases/download/${this.version}/kind-${goenv.GOOS}-${goenv.GOARCH}`; + private async downloadKind(version: string): Promise { + const url = `https://github.com/kubernetes-sigs/kind/releases/download/${version}/kind-${goenv.GOOS}-${goenv.GOARCH}`; console.log('downloading kind from ' + url); const downloadPath = await tc.downloadTool(url); if (process.platform !== 'win32') { @@ -119,17 +76,17 @@ export class KindMainService { downloadPath, KIND_COMMAND, KIND_TOOL_NAME, - this.version + version ); core.debug(`kind is cached under ${toolPath}`); return toolPath; } - async installKind(): Promise { - const parameters = await cache.restoreKindCache(this.version); - let toolPath: string = tc.find(KIND_TOOL_NAME, this.version); + async installKind(version: string): Promise { + const parameters = await cache.restoreKindCache(version); + let toolPath: string = tc.find(KIND_TOOL_NAME, version); if (toolPath === '') { - toolPath = await this.downloadKind(); + toolPath = await this.downloadKind(version); await cache.saveKindCache(parameters); } return toolPath; diff --git a/src/main.ts b/src/main.ts index a290331c..d91b11de 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,12 @@ import * as core from '@actions/core'; -import * as io from '@actions/io'; -import { ok } from 'assert'; -import { env as goenv } from './go'; import { KindMainService } from './kind/main'; +import { checkEnvironment } from './requirements'; async function run() { try { - checkEnvironment(); + const version = await checkEnvironment(); const service: KindMainService = KindMainService.getInstance(); - const toolPath: string = await service.installKind(); + const toolPath: string = await service.installKind(version); core.addPath(toolPath); await service.createCluster(); } catch (error) { @@ -16,26 +14,4 @@ async function run() { } } -function checkEnvironment() { - const supportedPlatforms: string[] = ['linux/amd64', 'linux/arm64']; - const platform = `${goenv.GOOS}/${goenv.GOARCH}`; - ok( - supportedPlatforms.includes(platform), - `Platform "${platform}" is not supported` - ); - const requiredVariables = [ - 'GITHUB_JOB', - 'GITHUB_WORKSPACE', - 'RUNNER_ARCH', - 'RUNNER_OS', - 'RUNNER_TEMP', - 'RUNNER_TOOL_CACHE', - ]; - requiredVariables.forEach((variable) => { - ok(`${process.env[variable]}`, `Expected ${variable} to be defined`); - }); - const docker = io.which('docker', false); - ok(docker, 'Docker is required for kind use'); -} - run(); diff --git a/src/requirements.ts b/src/requirements.ts new file mode 100644 index 00000000..0ad6d857 --- /dev/null +++ b/src/requirements.ts @@ -0,0 +1,173 @@ +import * as core from '@actions/core'; +import * as github from '@actions/github'; +import * as io from '@actions/io'; +import { ok } from 'assert'; +import * as semver from 'semver'; +import { Input, KIND_DEFAULT_VERSION } from './constants'; +import { env as goenv } from './go'; + +export async function checkEnvironment() { + checkVariables(); + await checkDocker(); + const { platform, version } = await checkPlatform(); + const image = core.getInput(Input.Image); + checkImageForPlatform(image, platform); + checkImageForVersion(image, version); + return version; +} + +/** + * Check that the platform allows KinD installation with engineerd/setup-kind + * @returns + */ +async function checkPlatform() { + const platform = `${goenv.GOOS}/${goenv.GOARCH}`; + const version = await ensureKindSupportsPlatform(platform); + await ensureSetupKindSupportsPlatform(platform); + return { platform, version }; +} + +/** + * Check that KinD supports the actual platform + */ +async function ensureKindSupportsPlatform(platform: string) { + const { platforms, version } = await findVersionAndSupportedPlatforms(); + ok( + platforms.includes(platform), + `sigs.k8s.io/kind@${version} doesn't support platform ${platform} but ${platforms.join( + ' and ' + )}` + ); + return version; +} + +/** + * Finds supported platforms by version by calling api.github.com + * @param inputVersion + * @returns + */ +async function getReleaseByInputVersion(inputVersion: string) { + const token = core.getInput(Input.Token, { required: true }); + const octokit = github.getOctokit(token, { + userAgent: 'engineerd/setup-kind', + }); + const KUBERNETES_SIGS = 'kubernetes-sigs'; + const KIND = 'kind'; + if (inputVersion === 'latest') { + const { data } = await octokit.rest.repos.getLatestRelease({ + owner: KUBERNETES_SIGS, + repo: KIND, + }); + return { + assets: data.assets, + version: data.tag_name, + }; + } else { + const { data } = await octokit.rest.repos.getReleaseByTag({ + owner: KUBERNETES_SIGS, + repo: KIND, + tag: inputVersion, + }); + return { + assets: data.assets, + version: data.tag_name, + }; + } +} + +/** + * Finds supported platforms by version by calling api.github.com + * @returns + */ +async function findVersionAndSupportedPlatforms() { + const inputVersion = core.getInput(Input.Version, { required: true }); + const { assets, version } = await getReleaseByInputVersion(inputVersion); + checkVersion(version); + const platforms: string[] = assets.map((asset) => { + const parts = asset.name.split('-'); + return `${parts[1]}/${parts[2]}`; + }); + return { platforms, version }; +} + +/** + * Check actually supported platforms by engineerd/setup-kind + * @param platform + */ +function ensureSetupKindSupportsPlatform(platform: string) { + const platforms: string[] = ['linux/amd64', 'linux/arm64']; + if (!platforms.includes(platform)) { + core.warning( + `engineerd/setup-kind doesn't support platform ${platform} but ${platforms.join( + ' and ' + )}` + ); + } +} + +/** + * Check required variables + */ +function checkVariables() { + [ + 'GITHUB_JOB', + 'GITHUB_WORKSPACE', + 'RUNNER_ARCH', + 'RUNNER_OS', + 'RUNNER_TEMP', + 'RUNNER_TOOL_CACHE', + ].forEach((variable) => { + ok(`${process.env[variable]}`, `Expected ${variable} to be defined`); + }); +} + +/** + * Check that Docker is installed on the server + */ +async function checkDocker() { + const docker = await io.which('docker', false); + ok(docker, 'Docker is required for kind use'); +} + +/** + * Verify that the version of kind is a valid semver and prints a warning if the kind version used is older than the default for setup-kind + */ +function checkVersion(version: string) { + ok( + semver.clean(version), + `Input ${Input.Version} expects a valid version like ${KIND_DEFAULT_VERSION}` + ); + if (semver.lt(version, KIND_DEFAULT_VERSION)) { + core.warning( + `sigs.k8s.io/kind@${KIND_DEFAULT_VERSION} is available, have you considered using it ? See https://github.com/kubernetes-sigs/kind/releases/tag/${KIND_DEFAULT_VERSION}` + ); + } +} + +/** + * An image is required for platforms outside of linux/amd64 and linux/arm64 as they are not packages with KinD by default + * @param image + * @param platform + */ +function checkImageForPlatform(image: string, platform: string) { + const platforms: string[] = ['linux/amd64', 'linux/arm64']; + if (!platforms.includes(platform)) { + ok(image, `Input ${Input.Image} is required for platform ${platform}`); + } +} + +/** + * Prints a warning if a kindest/node is used without sha256. + * This follows the recommendation from https://kind.sigs.k8s.io/docs/user/working-offline/#using-a-prebuilt-node-imagenode-image + */ +function checkImageForVersion(image: string, version: string) { + if ( + image !== '' && + image.startsWith('kindest/node') && + !image.includes('@sha256:') + ) { + core.warning( + `Please include the @sha256: image digest from the image in the release notes. You can find available image tags on the release page, https://github.com/kubernetes-sigs/kind/releases/tag/${version}` + ); + } +} From a62ee06a10fcbf1eec50cfe910f7ecc86624c38a Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 27 Jan 2022 23:00:10 +0100 Subject: [PATCH 03/24] boolean flag doesn't need value --- __tests__/kind/main.test.ts | 1 - __tests__/kind/post.test.ts | 2 -- src/kind/main.ts | 2 +- src/kind/post.ts | 4 ++-- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/__tests__/kind/main.test.ts b/__tests__/kind/main.test.ts index 7a258014..859f8a48 100644 --- a/__tests__/kind/main.test.ts +++ b/__tests__/kind/main.test.ts @@ -51,7 +51,6 @@ describe('checking input parsing', function () { '--verbosity', testEnvVars.INPUT_VERBOSITY, '--quiet', - testEnvVars.INPUT_QUIET, '--config', path.normalize('/home/runner/repo/some-path'), '--image', diff --git a/__tests__/kind/post.test.ts b/__tests__/kind/post.test.ts index 45d90f45..3142c96f 100644 --- a/__tests__/kind/post.test.ts +++ b/__tests__/kind/post.test.ts @@ -45,7 +45,6 @@ describe('checking input parsing', function () { '--verbosity', testEnvVars.INPUT_VERBOSITY, '--quiet', - testEnvVars.INPUT_QUIET, '--name', testEnvVars.INPUT_NAME, '--kubeconfig', @@ -64,7 +63,6 @@ describe('checking input parsing', function () { '--verbosity', testEnvVars.INPUT_VERBOSITY, '--quiet', - testEnvVars.INPUT_QUIET, '--name', testEnvVars.INPUT_NAME, ]); diff --git a/src/kind/main.ts b/src/kind/main.ts index 80986181..e8254a4e 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -41,7 +41,7 @@ export class KindMainService { args.push(Flag.Verbosity, this.verbosity.toString()); } if (this.quiet) { - args.push(Flag.Quiet, this.quiet.toString()); + args.push(Flag.Quiet); } if (this.configFile != '') { args.push( diff --git a/src/kind/post.ts b/src/kind/post.ts index 201c6e13..17ef4c16 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -37,7 +37,7 @@ export class KindPostService { args.push(Flag.Verbosity, this.verbosity.toString()); } if (this.quiet) { - args.push(Flag.Quiet, this.quiet.toString()); + args.push(Flag.Quiet); } if (this.name != '') { args.push(Flag.Name, this.name); @@ -55,7 +55,7 @@ export class KindPostService { args.push(Flag.Verbosity, this.verbosity.toString()); } if (this.quiet) { - args.push(Flag.Quiet, this.quiet.toString()); + args.push(Flag.Quiet); } if (this.name != '') { args.push(Flag.Name, this.name); From de5e401c6dfe901d28620fe818e3554f90714a5d Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 2 Feb 2022 19:43:04 +0100 Subject: [PATCH 04/24] check version before consulting github api --- src/requirements.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requirements.ts b/src/requirements.ts index 0ad6d857..d45838d2 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -63,6 +63,7 @@ async function getReleaseByInputVersion(inputVersion: string) { version: data.tag_name, }; } else { + checkVersion(inputVersion); const { data } = await octokit.rest.repos.getReleaseByTag({ owner: KUBERNETES_SIGS, repo: KIND, @@ -82,7 +83,6 @@ async function getReleaseByInputVersion(inputVersion: string) { async function findVersionAndSupportedPlatforms() { const inputVersion = core.getInput(Input.Version, { required: true }); const { assets, version } = await getReleaseByInputVersion(inputVersion); - checkVersion(version); const platforms: string[] = assets.map((asset) => { const parts = asset.name.split('-'); return `${parts[1]}/${parts[2]}`; From a9c6dc0c9b0da30eb7d4e4fcd0fb7ce53955849f Mon Sep 17 00:00:00 2001 From: Matthieu Morel Date: Tue, 25 Jan 2022 10:21:49 +0100 Subject: [PATCH 05/24] externalize requirements from main.ts --- .github/workflows/e2e.yml | 4 +- __tests__/kind/main.test.ts | 2 - __tests__/requirements.test.ts | 33 +++++ action.yml | 4 + package-lock.json | 262 +++++++++++++++++++++++++++++++++ package.json | 1 + src/constants.ts | 1 + src/kind/main.ts | 59 +------- src/main.ts | 30 +--- src/requirements.ts | 185 +++++++++++++++++++++++ 10 files changed, 498 insertions(+), 83 deletions(-) create mode 100644 __tests__/requirements.test.ts create mode 100644 src/requirements.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b1d2b8c9..25a0a344 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,4 +1,4 @@ -name: "Create cluster using KinD" +name: 'Create cluster using KinD' on: [pull_request, push] jobs: @@ -15,7 +15,7 @@ jobs: - run: npm run build - - name: "Run engineerd/setup-kind@${{github.sha}}" + - name: 'Run engineerd/setup-kind@${{github.sha}}' uses: ./ - run: kubectl cluster-info diff --git a/__tests__/kind/main.test.ts b/__tests__/kind/main.test.ts index 5ad4633a..7a258014 100644 --- a/__tests__/kind/main.test.ts +++ b/__tests__/kind/main.test.ts @@ -4,7 +4,6 @@ import { KindMainService } from '../../src/kind/main'; const testEnvVars = { INPUT_VERBOSITY: '3', INPUT_QUIET: 'true', - INPUT_VERSION: 'v0.5.3', INPUT_CONFIG: 'some-path', INPUT_IMAGE: 'some-docker-image', INPUT_NAME: 'some-name', @@ -30,7 +29,6 @@ describe('checking input parsing', function () { it('correctly parse input', () => { const service: KindMainService = KindMainService.getInstance(); - expect(service.version).toEqual(testEnvVars.INPUT_VERSION); expect(service.configFile).toEqual(testEnvVars.INPUT_CONFIG); expect(service.image).toEqual(testEnvVars.INPUT_IMAGE); expect(service.name).toEqual(testEnvVars.INPUT_NAME); diff --git a/__tests__/requirements.test.ts b/__tests__/requirements.test.ts new file mode 100644 index 00000000..0966e801 --- /dev/null +++ b/__tests__/requirements.test.ts @@ -0,0 +1,33 @@ +import { checkEnvironment } from '../src/requirements'; + +const testEnvVars = { + INPUT_VERSION: 'v0.7.0', + GITHUB_JOB: 'kind', + GITHUB_WORKSPACE: '/home/runner/repo', + RUNNER_OS: 'Linux', + RUNNER_ARCH: 'X64', + RUNNER_TEMP: '/home/runner/work/_temp', + RUNNER_TOOL_CACHE: '/opt/hostedtoolcache', +}; + +describe('checking requirements', function () { + const originalEnv = process.env; + beforeEach(() => { + jest.resetModules(); + process.env = { + ...originalEnv, + ...testEnvVars, + }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('required GITHUB_JOB must be defined', async () => { + process.env['GITHUB_JOB'] = ''; + await expect(checkEnvironment()).rejects.toThrow( + 'Expected GITHUB_JOB to be defined' + ); + }); +}); diff --git a/action.yml b/action.yml index c9d33a1e..4e2dd144 100644 --- a/action.yml +++ b/action.yml @@ -34,6 +34,10 @@ inputs: quiet: description: "Silence all stderr output" default: "false" + token: + description: "Used to retrieve release informations concerning KinD" + default: "${{ github.token }}" + required: true runs: using: "node12" main: "dist/main/index.js" diff --git a/package-lock.json b/package-lock.json index 187ab3f2..6ce12c3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@actions/cache": "^1.0.8", "@actions/core": "^1.6.0", "@actions/exec": "^1.1.0", + "@actions/github": "^5.0.0", "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", @@ -117,6 +118,17 @@ "@actions/io": "^1.0.1" } }, + "node_modules/@actions/github": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", + "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", + "dependencies": { + "@actions/http-client": "^1.0.11", + "@octokit/core": "^3.4.0", + "@octokit/plugin-paginate-rest": "^2.13.3", + "@octokit/plugin-rest-endpoint-methods": "^5.1.1" + } + }, "node_modules/@actions/glob": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.2.0.tgz", @@ -1381,6 +1393,107 @@ "node": ">= 8" } }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.2.2.tgz", + "integrity": "sha512-EVcXQ+ZrC04cg17AMg1ofocWMxHDn17cB66ZHgYc0eUwjFtxS0oBzkyw2VqIrHBwVgtfoYrq1WMQfQmMjUwthw==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.3.tgz", + "integrity": "sha512-kdc65UEsqze/9fCISq6BxLzeB9qf0vKvKojIfzgwf4tEF+Wy6c9dXnPFE6vgpoDFB1Z5Jek5WFVU6vL1w22+Iw==", + "dependencies": { + "@octokit/types": "^6.28.1" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.10.4.tgz", + "integrity": "sha512-Dh+EAMCYR9RUHwQChH94Skl0lM8Fh99auT8ggck/xTzjJrwVzvsd0YH68oRPqp/HxICzmUjLfaQ9sy1o1sfIiA==", + "dependencies": { + "@octokit/types": "^6.28.1", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz", + "integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.28.1.tgz", + "integrity": "sha512-XlxDoQLFO5JnFZgKVQTYTvXRsQFfr/GwDUU108NJ9R5yFPkA2qXhTJjYuul3vE4eLXP40FA2nysOu2zd6boE+w==", + "dependencies": { + "@octokit/openapi-types": "^10.2.2" + } + }, "node_modules/@opentelemetry/api": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", @@ -2108,6 +2221,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2559,6 +2677,11 @@ "node": ">=0.4.0" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3630,6 +3753,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -5951,6 +6082,11 @@ "node": ">=4.2.0" } }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -6319,6 +6455,17 @@ "@actions/io": "^1.0.1" } }, + "@actions/github": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.0.0.tgz", + "integrity": "sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ==", + "requires": { + "@actions/http-client": "^1.0.11", + "@octokit/core": "^3.4.0", + "@octokit/plugin-paginate-rest": "^2.13.3", + "@octokit/plugin-rest-endpoint-methods": "^5.1.1" + } + }, "@actions/glob": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.2.0.tgz", @@ -7338,6 +7485,101 @@ "fastq": "^1.6.0" } }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.2.2.tgz", + "integrity": "sha512-EVcXQ+ZrC04cg17AMg1ofocWMxHDn17cB66ZHgYc0eUwjFtxS0oBzkyw2VqIrHBwVgtfoYrq1WMQfQmMjUwthw==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.3.tgz", + "integrity": "sha512-kdc65UEsqze/9fCISq6BxLzeB9qf0vKvKojIfzgwf4tEF+Wy6c9dXnPFE6vgpoDFB1Z5Jek5WFVU6vL1w22+Iw==", + "requires": { + "@octokit/types": "^6.28.1" + } + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.10.4.tgz", + "integrity": "sha512-Dh+EAMCYR9RUHwQChH94Skl0lM8Fh99auT8ggck/xTzjJrwVzvsd0YH68oRPqp/HxICzmUjLfaQ9sy1o1sfIiA==", + "requires": { + "@octokit/types": "^6.28.1", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz", + "integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.28.1.tgz", + "integrity": "sha512-XlxDoQLFO5JnFZgKVQTYTvXRsQFfr/GwDUU108NJ9R5yFPkA2qXhTJjYuul3vE4eLXP40FA2nysOu2zd6boE+w==", + "requires": { + "@octokit/openapi-types": "^10.2.2" + } + }, "@opentelemetry/api": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz", @@ -7884,6 +8126,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -8233,6 +8480,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -9003,6 +9255,11 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -10705,6 +10962,11 @@ "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index ef6ef3ae..612ab1be 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@actions/cache": "^1.0.8", "@actions/core": "^1.6.0", "@actions/exec": "^1.1.0", + "@actions/github": "^5.0.0", "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", diff --git a/src/constants.ts b/src/constants.ts index 5e88fbeb..58ecf65c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,6 +5,7 @@ export enum Input { Config = 'config', Image = 'image', Name = 'name', + Token = 'token', Wait = 'wait', KubeConfig = 'kubeconfig', SkipClusterCreation = 'skipClusterCreation', diff --git a/src/kind/main.ts b/src/kind/main.ts index bb76eb12..3066abce 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -1,22 +1,13 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; import * as tc from '@actions/tool-cache'; -import { ok } from 'assert'; import path from 'path'; import process from 'process'; -import * as semver from 'semver'; import * as cache from '../cache'; -import { - Flag, - Input, - KIND_DEFAULT_VERSION, - KIND_TOOL_NAME, -} from '../constants'; -import { env as goenv } from '../go'; +import { Flag, Input, KIND_TOOL_NAME } from '../constants'; import { executeKindCommand, KIND_COMMAND } from './core'; export class KindMainService { - version: string; configFile: string; image: string; name: string; @@ -27,11 +18,8 @@ export class KindMainService { quiet: boolean; private constructor() { - this.version = core.getInput(Input.Version, { required: true }); - this.checkVersion(); this.configFile = core.getInput(Input.Config); this.image = core.getInput(Input.Image); - this.checkImage(); this.name = core.getInput(Input.Name, { required: true }); this.waitDuration = core.getInput(Input.Wait); this.kubeConfigFile = core.getInput(Input.KubeConfig); @@ -45,38 +33,6 @@ export class KindMainService { return new KindMainService(); } - /** - * Verify that the version of kind is a valid semver and prints a warning if the kind version used is older than the default for setup-kind - */ - private checkVersion() { - const cleanVersion = semver.clean(this.version); - ok( - cleanVersion, - `Input ${Input.Version} expects a valid version like ${KIND_DEFAULT_VERSION}` - ); - if (semver.lt(this.version, KIND_DEFAULT_VERSION)) { - core.warning( - `Kind ${KIND_DEFAULT_VERSION} is available, have you considered using it ? See https://github.com/kubernetes-sigs/kind/releases/tag/${KIND_DEFAULT_VERSION}` - ); - } - } - - /** - * Prints a warning if a kindest/node is used without sha256. - * This follows the recommendation from https://kind.sigs.k8s.io/docs/user/working-offline/#using-a-prebuilt-node-imagenode-image - */ - private checkImage() { - if ( - this.image !== '' && - this.image.startsWith('kindest/node') && - !this.image.includes('@sha256:') - ) { - core.warning( - `Please include the @sha256: image digest from the image in the release notes. You can find available image tags on the release page, https://github.com/kubernetes-sigs/kind/releases/tag/${this.version}` - ); - } - } - // returns the arguments to pass to `kind create cluster` createCommand(): string[] { const args: string[] = ['create', 'cluster']; @@ -108,8 +64,7 @@ export class KindMainService { } // this action should always be run from a Linux worker - private async downloadKind(): Promise { - const url = `https://github.com/kubernetes-sigs/kind/releases/download/${this.version}/kind-${goenv.GOOS}-${goenv.GOARCH}`; + private async downloadKind(version: string, url: string): Promise { console.log('downloading kind from ' + url); const downloadPath = await tc.downloadTool(url); if (process.platform !== 'win32') { @@ -119,17 +74,17 @@ export class KindMainService { downloadPath, KIND_COMMAND, KIND_TOOL_NAME, - this.version + version ); core.debug(`kind is cached under ${toolPath}`); return toolPath; } - async installKind(): Promise { - const parameters = await cache.restoreKindCache(this.version); - let toolPath: string = tc.find(KIND_TOOL_NAME, this.version); + async installKind(version: string, url: string): Promise { + const parameters = await cache.restoreKindCache(version); + let toolPath: string = tc.find(KIND_TOOL_NAME, version); if (toolPath === '') { - toolPath = await this.downloadKind(); + toolPath = await this.downloadKind(version, url); await cache.saveKindCache(parameters); } return toolPath; diff --git a/src/main.ts b/src/main.ts index a290331c..ed882a03 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,12 @@ import * as core from '@actions/core'; -import * as io from '@actions/io'; -import { ok } from 'assert'; -import { env as goenv } from './go'; import { KindMainService } from './kind/main'; +import { checkEnvironment } from './requirements'; async function run() { try { - checkEnvironment(); + const { version, url } = await checkEnvironment(); const service: KindMainService = KindMainService.getInstance(); - const toolPath: string = await service.installKind(); + const toolPath: string = await service.installKind(version, url); core.addPath(toolPath); await service.createCluster(); } catch (error) { @@ -16,26 +14,4 @@ async function run() { } } -function checkEnvironment() { - const supportedPlatforms: string[] = ['linux/amd64', 'linux/arm64']; - const platform = `${goenv.GOOS}/${goenv.GOARCH}`; - ok( - supportedPlatforms.includes(platform), - `Platform "${platform}" is not supported` - ); - const requiredVariables = [ - 'GITHUB_JOB', - 'GITHUB_WORKSPACE', - 'RUNNER_ARCH', - 'RUNNER_OS', - 'RUNNER_TEMP', - 'RUNNER_TOOL_CACHE', - ]; - requiredVariables.forEach((variable) => { - ok(`${process.env[variable]}`, `Expected ${variable} to be defined`); - }); - const docker = io.which('docker', false); - ok(docker, 'Docker is required for kind use'); -} - run(); diff --git a/src/requirements.ts b/src/requirements.ts new file mode 100644 index 00000000..a14fea27 --- /dev/null +++ b/src/requirements.ts @@ -0,0 +1,185 @@ +import * as core from '@actions/core'; +import * as github from '@actions/github'; +import * as io from '@actions/io'; +import { ok } from 'assert'; +import * as semver from 'semver'; +import { Input, KIND_DEFAULT_VERSION } from './constants'; +import { env as goenv } from './go'; + +export async function checkEnvironment() { + checkVariables(); + await checkDocker(); + const { platform, url, version } = await checkPlatform(); + const image = core.getInput(Input.Image); + checkImageForPlatform(image, platform); + checkImageForVersion(image, version); + return { version, url }; +} + +/** + * Check that the platform allows KinD installation with engineerd/setup-kind + * @returns + */ +async function checkPlatform() { + const platform = `${goenv.GOOS}/${goenv.GOARCH}`; + const { version, url } = await ensureKindSupportsPlatform(platform); + await ensureSetupKindSupportsPlatform(platform); + return { platform, url, version }; +} + +/** + * Check that KinD supports the actual platform + */ +async function ensureKindSupportsPlatform(platform: string) { + const { platforms, version } = await findVersionAndSupportedPlatforms(); + ok( + platforms[platform], + `sigs.k8s.io/kind@${version} doesn't support platform ${platform} but ${Object.getOwnPropertyNames( + platforms + ) + .sort() + .join(' and ')}` + ); + return { + version: version, + url: platforms[platform], + }; +} + +/** + * Finds supported platforms by version by calling api.github.com + * @param inputVersion + * @returns + */ +async function getReleaseByInputVersion(inputVersion: string) { + const token = core.getInput(Input.Token, { required: true }); + const octokit = github.getOctokit(token, { + userAgent: `engineerd/setup-kind@${process.env['npm_package_version']}`, + }); + const KUBERNETES_SIGS = 'kubernetes-sigs'; + const KIND = 'kind'; + if (inputVersion === 'latest') { + const { data } = await octokit.rest.repos.getLatestRelease({ + owner: KUBERNETES_SIGS, + repo: KIND, + }); + return { + assets: data.assets, + version: data.tag_name, + }; + } else { + checkVersion(inputVersion); + const { data } = await octokit.rest.repos.getReleaseByTag({ + owner: KUBERNETES_SIGS, + repo: KIND, + tag: inputVersion, + }); + return { + assets: data.assets, + version: data.tag_name, + }; + } +} + +/** + * Finds supported platforms by version by calling api.github.com + * @returns + */ +async function findVersionAndSupportedPlatforms() { + const inputVersion = core.getInput(Input.Version, { required: true }); + const { assets, version } = await getReleaseByInputVersion(inputVersion); + const platforms = assets.reduce( + ( + total: { [key: string]: string }, + asset: { name: string; browser_download_url: string } + ) => { + const parts = asset.name.split('-'); + total[`${parts[1]}/${parts[2]}`] = asset.browser_download_url; + return total; + }, + {} + ); + return { platforms, version }; +} + +/** + * Check actually supported platforms by engineerd/setup-kind + * @param platform + */ +function ensureSetupKindSupportsPlatform(platform: string) { + const platforms: string[] = ['linux/amd64', 'linux/arm64']; + if (!platforms.includes(platform)) { + core.warning( + `engineerd/setup-kind@${ + process.env['npm_package_version'] + } doesn't support platform ${platform} but ${platforms.join(' and ')}` + ); + } +} + +/** + * Check required variables + */ +function checkVariables() { + [ + 'GITHUB_JOB', + 'GITHUB_WORKSPACE', + 'RUNNER_ARCH', + 'RUNNER_OS', + 'RUNNER_TEMP', + 'RUNNER_TOOL_CACHE', + ].forEach((variable) => { + ok(`${process.env[variable]}`, `Expected ${variable} to be defined`); + }); +} + +/** + * Check that Docker is installed on the server + */ +async function checkDocker() { + const docker = await io.which('docker', false); + ok(docker, 'Docker is required for kind use'); +} + +/** + * Verify that the version of kind is a valid semver and prints a warning if the kind version used is older than the default for setup-kind + */ +function checkVersion(version: string) { + ok( + semver.clean(version), + `Input ${Input.Version} expects a valid version like ${KIND_DEFAULT_VERSION}` + ); + if (semver.lt(version, KIND_DEFAULT_VERSION)) { + core.warning( + `sigs.k8s.io/kind@${KIND_DEFAULT_VERSION} is available, have you considered using it ? See https://github.com/kubernetes-sigs/kind/releases/tag/${KIND_DEFAULT_VERSION}` + ); + } +} + +/** + * An image is required for platforms outside of linux/amd64 and linux/arm64 as they are not packages with KinD by default + * @param image + * @param platform + */ +function checkImageForPlatform(image: string, platform: string) { + const platforms: string[] = ['linux/amd64', 'linux/arm64']; + if (!platforms.includes(platform)) { + ok(image, `Input ${Input.Image} is required for platform ${platform}`); + } +} + +/** + * Prints a warning if a kindest/node is used without sha256. + * This follows the recommendation from https://kind.sigs.k8s.io/docs/user/working-offline/#using-a-prebuilt-node-imagenode-image + */ +function checkImageForVersion(image: string, version: string) { + if ( + image !== '' && + image.startsWith('kindest/node') && + !image.includes('@sha256:') + ) { + core.warning( + `Please include the @sha256: image digest from the image in the release notes. You can find available image tags on the release page, https://github.com/kubernetes-sigs/kind/releases/tag/${version}` + ); + } +} From e07abbfd7f4d910033bf55f68a24f7f9f4794027 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 27 Jan 2022 23:00:10 +0100 Subject: [PATCH 06/24] boolean flag doesn't need value --- __tests__/kind/main.test.ts | 1 - __tests__/kind/post.test.ts | 2 -- src/kind/main.ts | 2 +- src/kind/post.ts | 4 ++-- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/__tests__/kind/main.test.ts b/__tests__/kind/main.test.ts index 7a258014..859f8a48 100644 --- a/__tests__/kind/main.test.ts +++ b/__tests__/kind/main.test.ts @@ -51,7 +51,6 @@ describe('checking input parsing', function () { '--verbosity', testEnvVars.INPUT_VERBOSITY, '--quiet', - testEnvVars.INPUT_QUIET, '--config', path.normalize('/home/runner/repo/some-path'), '--image', diff --git a/__tests__/kind/post.test.ts b/__tests__/kind/post.test.ts index 45d90f45..3142c96f 100644 --- a/__tests__/kind/post.test.ts +++ b/__tests__/kind/post.test.ts @@ -45,7 +45,6 @@ describe('checking input parsing', function () { '--verbosity', testEnvVars.INPUT_VERBOSITY, '--quiet', - testEnvVars.INPUT_QUIET, '--name', testEnvVars.INPUT_NAME, '--kubeconfig', @@ -64,7 +63,6 @@ describe('checking input parsing', function () { '--verbosity', testEnvVars.INPUT_VERBOSITY, '--quiet', - testEnvVars.INPUT_QUIET, '--name', testEnvVars.INPUT_NAME, ]); diff --git a/src/kind/main.ts b/src/kind/main.ts index 3066abce..5358d303 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -40,7 +40,7 @@ export class KindMainService { args.push(Flag.Verbosity, this.verbosity.toString()); } if (this.quiet) { - args.push(Flag.Quiet, this.quiet.toString()); + args.push(Flag.Quiet); } if (this.configFile != '') { args.push( diff --git a/src/kind/post.ts b/src/kind/post.ts index 201c6e13..17ef4c16 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -37,7 +37,7 @@ export class KindPostService { args.push(Flag.Verbosity, this.verbosity.toString()); } if (this.quiet) { - args.push(Flag.Quiet, this.quiet.toString()); + args.push(Flag.Quiet); } if (this.name != '') { args.push(Flag.Name, this.name); @@ -55,7 +55,7 @@ export class KindPostService { args.push(Flag.Verbosity, this.verbosity.toString()); } if (this.quiet) { - args.push(Flag.Quiet, this.quiet.toString()); + args.push(Flag.Quiet); } if (this.name != '') { args.push(Flag.Name, this.name); From 96ddde2e5c099c722797edb3ab000bf595947955 Mon Sep 17 00:00:00 2001 From: MOREL Matthieu Date: Sun, 6 Feb 2022 11:11:26 +0100 Subject: [PATCH 07/24] remove @octokit/types @octokit/rest --- package-lock.json | 2 -- package.json | 2 -- 2 files changed, 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9d805ec..6ce12c3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,12 +17,10 @@ "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", - "@octokit/rest": "^18.10.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { - "@octokit/types": "^6.28.1", "@types/jest": "^27.4.0", "@types/node": "^17.0.8", "@types/semver": "^7.3.9", diff --git a/package.json b/package.json index 570d9569..612ab1be 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,10 @@ "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", - "@octokit/rest": "^18.10.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { - "@octokit/types": "^6.28.1", "@types/jest": "^27.4.0", "@types/node": "^17.0.8", "@types/semver": "^7.3.9", From 059381694a6b97474f38a8f7bc1a66f00ee69de1 Mon Sep 17 00:00:00 2001 From: MOREL Matthieu Date: Mon, 7 Feb 2022 08:09:01 +0100 Subject: [PATCH 08/24] don't look for process.env['npm_package_version'] --- src/requirements.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/requirements.ts b/src/requirements.ts index a14fea27..daac5f51 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -54,7 +54,7 @@ async function ensureKindSupportsPlatform(platform: string) { async function getReleaseByInputVersion(inputVersion: string) { const token = core.getInput(Input.Token, { required: true }); const octokit = github.getOctokit(token, { - userAgent: `engineerd/setup-kind@${process.env['npm_package_version']}`, + userAgent: 'engineerd/setup-kind', }); const KUBERNETES_SIGS = 'kubernetes-sigs'; const KIND = 'kind'; @@ -110,9 +110,9 @@ function ensureSetupKindSupportsPlatform(platform: string) { const platforms: string[] = ['linux/amd64', 'linux/arm64']; if (!platforms.includes(platform)) { core.warning( - `engineerd/setup-kind@${ - process.env['npm_package_version'] - } doesn't support platform ${platform} but ${platforms.join(' and ')}` + `engineerd/setup-kind doesn't support platform ${platform} but ${platforms.join( + ' and ' + )}` ); } } From 16c3d14e339108d62ea0b316a097ab2081535394 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 9 Feb 2022 10:07:29 +0100 Subject: [PATCH 09/24] install kubectl when kubernetes version can be identified throught image tag --- .github/workflows/e2e.yml | 2 ++ src/cache.ts | 62 ++++++++++++++++---------------- src/constants.ts | 6 +++- src/installer.ts | 76 +++++++++++++++++++++++++++++++++++++++ src/kind/core.ts | 4 +-- src/kind/main.ts | 33 ++--------------- src/main.ts | 6 ++-- src/requirements.ts | 47 +++++++++++++++++++++--- 8 files changed, 162 insertions(+), 74 deletions(-) create mode 100644 src/installer.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 25a0a344..2a52b120 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -17,6 +17,8 @@ jobs: - name: 'Run engineerd/setup-kind@${{github.sha}}' uses: ./ + with: + image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6 - run: kubectl cluster-info diff --git a/src/cache.ts b/src/cache.ts index 4168def7..0f264723 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -4,36 +4,23 @@ import crypto from 'crypto'; import path from 'path'; import process from 'process'; import * as semver from 'semver'; -import { KIND_TOOL_NAME } from './constants'; +import { KIND_TOOL_NAME, KUBECTL_TOOL_NAME } from './constants'; /** * Prefix of the kind cache key */ const KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS']}-${process.env['RUNNER_ARCH']}-setup-kind-`; -/** - * Parameters used by the cache to save and restore - */ -export interface CacheParameters { - /** - * a list of file paths to restore from the cache - */ - paths: string[]; - /** - * An explicit key for restoring the cache - */ - primaryKey: string; -} - /** * Restores Kind by version, $RUNNER_OS and $RUNNER_ARCH * @param version */ -export async function restoreKindCache( - version: string -): Promise { - const primaryKey = kindPrimaryKey(version); - const cachePaths = kindCachePaths(version); +export async function restoreSetupKindCache( + kind_version: string, + kubectl_version: string +) { + const primaryKey = setupKindPrimaryKey(kind_version, kubectl_version); + const cachePaths = setupKindCachePaths(kind_version, kubectl_version); core.debug(`Primary key is ${primaryKey}`); @@ -56,14 +43,24 @@ export async function restoreKindCache( * @param version * @returns the cache paths */ -function kindCachePaths(version: string) { - return [ +function setupKindCachePaths(kind_version: string, kubectl_version: string) { + const paths = [ path.join( `${process.env['RUNNER_TOOL_CACHE']}`, KIND_TOOL_NAME, - semver.clean(version) || version + semver.clean(kind_version) || kind_version ), ]; + if (kubectl_version !== '') { + paths.push( + path.join( + `${process.env['RUNNER_TOOL_CACHE']}`, + KUBECTL_TOOL_NAME, + semver.clean(kubectl_version) || kubectl_version + ) + ); + } + return paths; } /** @@ -73,11 +70,14 @@ function kindCachePaths(version: string) { * @param version * @returns the primary Key */ -function kindPrimaryKey(version: string) { - const hash = crypto - .createHash('sha256') - .update(`kind-${version}-${process.platform}-${process.arch}-`) - .digest('hex'); +function setupKindPrimaryKey(kind_version: string, kubectl_version: string) { + const key = JSON.stringify({ + architecture: process.arch, + kind: kind_version, + kubectl: kubectl_version, + platform: process.platform, + }); + const hash = crypto.createHash('sha256').update(key).digest('hex'); return `${KIND_CACHE_KEY_PREFIX}${hash}`; } @@ -85,10 +85,10 @@ function kindPrimaryKey(version: string) { * Caches Kind by it's primaryKey * @param primaryKey */ -export async function saveKindCache(parameters: CacheParameters) { +export async function saveSetupKindCache(paths: string[], primaryKey: string) { try { - await cache.saveCache(parameters.paths, parameters.primaryKey); - core.info(`Cache setup-kind saved with the key ${parameters.primaryKey}`); + await cache.saveCache(paths, primaryKey); + core.info(`Cache setup-kind saved with the key ${primaryKey}`); } catch (err) { const error = err as Error; if (error.name === cache.ValidationError.name) { diff --git a/src/constants.ts b/src/constants.ts index 58ecf65c..0ba2da36 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,6 +24,10 @@ export enum Flag { KubeConfig = '--kubeconfig', } +export const KIND_COMMAND = process.platform === 'win32' ? 'kind.exe' : 'kind'; +export const KIND_DEFAULT_VERSION = 'v0.11.1'; export const KIND_TOOL_NAME = 'kind'; -export const KIND_DEFAULT_VERSION = 'v0.11.1'; +export const KUBECTL_COMMAND = + process.platform === 'win32' ? 'kubectl.exe' : 'kubectl'; +export const KUBECTL_TOOL_NAME = 'kubectl'; diff --git a/src/installer.ts b/src/installer.ts new file mode 100644 index 00000000..f57035ad --- /dev/null +++ b/src/installer.ts @@ -0,0 +1,76 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; +import * as tc from '@actions/tool-cache'; +import process from 'process'; +import * as cache from './cache'; +import { + KIND_COMMAND, + KIND_TOOL_NAME, + KUBECTL_COMMAND, + KUBECTL_TOOL_NAME, +} from './constants'; + +export async function installTools( + kind: { + version: string; + url: string; + }, + kubectl: { + version: string; + url: string; + } +) { + const { paths, primaryKey } = await cache.restoreSetupKindCache( + kind.version, + kubectl.version + ); + const kindDownloaded = await installKind(kind.version, kind.url); + const kubectlDownloaded = await installKubectl(kubectl.version, kubectl.url); + if (kindDownloaded || kubectlDownloaded) { + await cache.saveSetupKindCache(paths, primaryKey); + } +} + +async function installKind(version: string, url: string) { + return await installTool(KIND_COMMAND, KIND_TOOL_NAME, version, url); +} +async function installKubectl(version: string, url: string) { + return await installTool(KUBECTL_COMMAND, KUBECTL_TOOL_NAME, version, url); +} + +async function downloadTool( + command: string, + toolName: string, + version: string, + url: string +): Promise { + console.log(`downloading ${toolName} from ${url}`); + const downloadPath = await tc.downloadTool(url); + if (process.platform !== 'win32') { + await exec.exec('chmod', ['+x', downloadPath]); + } + const toolPath: string = await tc.cacheFile( + downloadPath, + command, + toolName, + version + ); + core.debug(`${toolName} is cached under ${toolPath}`); + return toolPath; +} + +async function installTool( + command: string, + toolName: string, + version: string, + url: string +) { + let toolPath: string = tc.find(toolName, version); + let downloaded = false; + if (toolPath === '') { + toolPath = await downloadTool(command, toolName, version, url); + downloaded = true; + } + core.addPath(toolPath); + return downloaded; +} diff --git a/src/kind/core.ts b/src/kind/core.ts index f89cba22..5069bb14 100644 --- a/src/kind/core.ts +++ b/src/kind/core.ts @@ -1,7 +1,5 @@ import * as exec from '@actions/exec'; -import process from 'process'; - -export const KIND_COMMAND = process.platform === 'win32' ? 'kind.exe' : 'kind'; +import { KIND_COMMAND } from '../constants'; export async function executeKindCommand(args: string[]) { await exec.exec(KIND_COMMAND, args); diff --git a/src/kind/main.ts b/src/kind/main.ts index cbed3a85..862adce5 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -1,11 +1,8 @@ import * as core from '@actions/core'; -import * as exec from '@actions/exec'; -import * as tc from '@actions/tool-cache'; import path from 'path'; import process from 'process'; -import * as cache from '../cache'; -import { Flag, Input, KIND_TOOL_NAME } from '../constants'; -import { executeKindCommand, KIND_COMMAND } from './core'; +import { Flag, Input } from '../constants'; +import { executeKindCommand } from './core'; export class KindMainService { configFile: string; @@ -63,32 +60,6 @@ export class KindMainService { return args; } - private async downloadKind(version: string, url: string): Promise { - console.log('downloading kind from ' + url); - const downloadPath = await tc.downloadTool(url); - if (process.platform !== 'win32') { - await exec.exec('chmod', ['+x', downloadPath]); - } - const toolPath: string = await tc.cacheFile( - downloadPath, - KIND_COMMAND, - KIND_TOOL_NAME, - version - ); - core.debug(`kind is cached under ${toolPath}`); - return toolPath; - } - - async installKind(version: string, url: string): Promise { - const parameters = await cache.restoreKindCache(version); - let toolPath: string = tc.find(KIND_TOOL_NAME, version); - if (toolPath === '') { - toolPath = await this.downloadKind(version, url); - await cache.saveKindCache(parameters); - } - return toolPath; - } - async createCluster() { if (this.skipClusterCreation) { return; diff --git a/src/main.ts b/src/main.ts index ed882a03..ab3f0aad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,13 @@ import * as core from '@actions/core'; import { KindMainService } from './kind/main'; import { checkEnvironment } from './requirements'; +import { installTools } from './installer'; async function run() { try { - const { version, url } = await checkEnvironment(); + const { kind, kubectl } = await checkEnvironment(); const service: KindMainService = KindMainService.getInstance(); - const toolPath: string = await service.installKind(version, url); - core.addPath(toolPath); + await installTools(kind, kubectl); await service.createCluster(); } catch (error) { core.setFailed((error as Error).message); diff --git a/src/requirements.ts b/src/requirements.ts index daac5f51..da29e5cd 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -3,17 +3,48 @@ import * as github from '@actions/github'; import * as io from '@actions/io'; import { ok } from 'assert'; import * as semver from 'semver'; -import { Input, KIND_DEFAULT_VERSION } from './constants'; +import { Input, KIND_DEFAULT_VERSION, KUBECTL_COMMAND } from './constants'; import { env as goenv } from './go'; export async function checkEnvironment() { checkVariables(); await checkDocker(); - const { platform, url, version } = await checkPlatform(); + const { platform, kind } = await checkPlatform(); const image = core.getInput(Input.Image); checkImageForPlatform(image, platform); - checkImageForVersion(image, version); - return { version, url }; + checkImageForVersion(image, kind.version); + const kubectl = await getKubectl(image, platform); + return { + kind, + kubectl, + }; +} + +async function getKubectl(image: string, platform: string) { + let version = ''; + let url = ''; + if (image !== '' && image.startsWith('kindest/node')) { + version = image.split('@')[0].split(':')[1]; + await checkKubernetesVersion(version); + url = `https://storage.googleapis.com/kubernetes-release/release/${version}/bin/${platform}/${KUBECTL_COMMAND}`; + } + return { + version, + url, + }; +} + +async function checkKubernetesVersion(version: string) { + const token = core.getInput(Input.Token, { required: true }); + const octokit = github.getOctokit(token, { + userAgent: 'engineerd/setup-kind', + }); + const { status } = await octokit.rest.repos.getReleaseByTag({ + owner: 'kubernetes', + repo: 'kubernetes', + tag: version, + }); + ok(status === 200, `Kubernetes ${version} doesn't exists`); } /** @@ -24,7 +55,13 @@ async function checkPlatform() { const platform = `${goenv.GOOS}/${goenv.GOARCH}`; const { version, url } = await ensureKindSupportsPlatform(platform); await ensureSetupKindSupportsPlatform(platform); - return { platform, url, version }; + return { + platform, + kind: { + url: url, + version: version, + }, + }; } /** From f029a62ce3a4879655311f0165f74dcf5851d085 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 9 Feb 2022 18:50:17 +0100 Subject: [PATCH 10/24] install kubectl only if identified --- src/installer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/installer.ts b/src/installer.ts index f57035ad..63b2a891 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -34,8 +34,12 @@ export async function installTools( async function installKind(version: string, url: string) { return await installTool(KIND_COMMAND, KIND_TOOL_NAME, version, url); } + async function installKubectl(version: string, url: string) { - return await installTool(KUBECTL_COMMAND, KUBECTL_TOOL_NAME, version, url); + if (version !== '' && url !== '') { + return await installTool(KUBECTL_COMMAND, KUBECTL_TOOL_NAME, version, url); + } + return false; } async function downloadTool( From d1ca53d58b6953bbcfde65491a53c802ed9f7eed Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 9 Feb 2022 20:26:23 +0100 Subject: [PATCH 11/24] remove useless await and provide empty string as default for empty variables --- src/cache.ts | 8 +++++--- src/kind/main.ts | 2 +- src/kind/post.ts | 4 ++-- src/requirements.ts | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 0f264723..80ad6541 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -9,7 +9,9 @@ import { KIND_TOOL_NAME, KUBECTL_TOOL_NAME } from './constants'; /** * Prefix of the kind cache key */ -const KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS']}-${process.env['RUNNER_ARCH']}-setup-kind-`; +const KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS'] || ''}-${ + process.env['RUNNER_ARCH'] || '' +}-setup-kind-`; /** * Restores Kind by version, $RUNNER_OS and $RUNNER_ARCH @@ -46,7 +48,7 @@ export async function restoreSetupKindCache( function setupKindCachePaths(kind_version: string, kubectl_version: string) { const paths = [ path.join( - `${process.env['RUNNER_TOOL_CACHE']}`, + `${process.env['RUNNER_TOOL_CACHE'] || ''}`, KIND_TOOL_NAME, semver.clean(kind_version) || kind_version ), @@ -54,7 +56,7 @@ function setupKindCachePaths(kind_version: string, kubectl_version: string) { if (kubectl_version !== '') { paths.push( path.join( - `${process.env['RUNNER_TOOL_CACHE']}`, + `${process.env['RUNNER_TOOL_CACHE'] || ''}`, KUBECTL_TOOL_NAME, semver.clean(kubectl_version) || kubectl_version ) diff --git a/src/kind/main.ts b/src/kind/main.ts index 862adce5..95d6d0b0 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -42,7 +42,7 @@ export class KindMainService { if (this.configFile != '') { args.push( Flag.Config, - path.join(`${process.env['GITHUB_WORKSPACE']}`, this.configFile) + path.join(`${process.env['GITHUB_WORKSPACE'] || ''}`, this.configFile) ); } if (this.image != '') { diff --git a/src/kind/post.ts b/src/kind/post.ts index 17ef4c16..1a826634 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -70,14 +70,14 @@ export class KindPostService { } dirs.push('logs'); return path.join( - `${process.env['RUNNER_TEMP']}`, + `${process.env['RUNNER_TEMP'] || ''}`, uuidv5(dirs.join('/'), uuidv5.URL) ); } private artifactName(): string { const artifactArgs: string[] = [ - `${process.env['GITHUB_JOB']}`, + `${process.env['GITHUB_JOB'] || ''}`, KIND_TOOL_NAME, ]; if (this.name != '') { diff --git a/src/requirements.ts b/src/requirements.ts index da29e5cd..68f670fd 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -54,7 +54,7 @@ async function checkKubernetesVersion(version: string) { async function checkPlatform() { const platform = `${goenv.GOOS}/${goenv.GOARCH}`; const { version, url } = await ensureKindSupportsPlatform(platform); - await ensureSetupKindSupportsPlatform(platform); + ensureSetupKindSupportsPlatform(platform); return { platform, kind: { @@ -166,7 +166,7 @@ function checkVariables() { 'RUNNER_TEMP', 'RUNNER_TOOL_CACHE', ].forEach((variable) => { - ok(`${process.env[variable]}`, `Expected ${variable} to be defined`); + ok(`${process.env[variable] || ''}`, `Expected ${variable} to be defined`); }); } From 7ece80cb2fec38b5534a869feb6446e45b767da4 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 9 Feb 2022 23:07:09 +0100 Subject: [PATCH 12/24] use catch after promise instead of try catch inside the promise --- .github/workflows/e2e.yml | 2 -- src/main.ts | 15 ++++++--------- src/post.ts | 14 ++++++-------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2a52b120..25a0a344 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -17,8 +17,6 @@ jobs: - name: 'Run engineerd/setup-kind@${{github.sha}}' uses: ./ - with: - image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6 - run: kubectl cluster-info diff --git a/src/main.ts b/src/main.ts index ab3f0aad..bea3d162 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,14 +4,11 @@ import { checkEnvironment } from './requirements'; import { installTools } from './installer'; async function run() { - try { - const { kind, kubectl } = await checkEnvironment(); - const service: KindMainService = KindMainService.getInstance(); - await installTools(kind, kubectl); - await service.createCluster(); - } catch (error) { - core.setFailed((error as Error).message); - } + const { kind, kubectl } = await checkEnvironment(); + await installTools(kind, kubectl); + await KindMainService.getInstance().createCluster(); } -run(); +run().catch((error) => { + core.setFailed((error as Error).message); +}); diff --git a/src/post.ts b/src/post.ts index 4c4de83c..e1e2811f 100644 --- a/src/post.ts +++ b/src/post.ts @@ -2,13 +2,11 @@ import * as core from '@actions/core'; import { KindPostService } from './kind/post'; async function run() { - try { - const service: KindPostService = KindPostService.getInstance(); - await service.exportClusterLogs(); - await service.deleteCluster(); - } catch (error) { - core.setFailed((error as Error).message); - } + const service: KindPostService = KindPostService.getInstance(); + await service.exportClusterLogs(); + await service.deleteCluster(); } -run(); +run().catch((error) => { + core.setFailed((error as Error).message); +}); From 313808bbf4930c896d4b9bc8d1784bd6f4c88373 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 9 Feb 2022 23:40:11 +0100 Subject: [PATCH 13/24] mutualise octokit instantiation --- src/requirements.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/requirements.ts b/src/requirements.ts index 68f670fd..346fbb51 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -34,11 +34,15 @@ async function getKubectl(image: string, platform: string) { }; } -async function checkKubernetesVersion(version: string) { +function getOctokit() { const token = core.getInput(Input.Token, { required: true }); - const octokit = github.getOctokit(token, { + return github.getOctokit(token, { userAgent: 'engineerd/setup-kind', }); +} + +async function checkKubernetesVersion(version: string) { + const octokit = getOctokit(); const { status } = await octokit.rest.repos.getReleaseByTag({ owner: 'kubernetes', repo: 'kubernetes', @@ -89,10 +93,7 @@ async function ensureKindSupportsPlatform(platform: string) { * @returns */ async function getReleaseByInputVersion(inputVersion: string) { - const token = core.getInput(Input.Token, { required: true }); - const octokit = github.getOctokit(token, { - userAgent: 'engineerd/setup-kind', - }); + const octokit = getOctokit(); const KUBERNETES_SIGS = 'kubernetes-sigs'; const KIND = 'kind'; if (inputVersion === 'latest') { From a5c975c233eec57cba805854ffab2ba41f43148a Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 10 Feb 2022 06:20:19 +0100 Subject: [PATCH 14/24] use core.info instead of console.log and define consts --- src/cache.ts | 8 ++++---- src/installer.ts | 2 +- src/requirements.ts | 12 +++++------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 80ad6541..06c3f67a 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -22,11 +22,11 @@ export async function restoreSetupKindCache( kubectl_version: string ) { const primaryKey = setupKindPrimaryKey(kind_version, kubectl_version); - const cachePaths = setupKindCachePaths(kind_version, kubectl_version); + const paths = setupKindCachePaths(kind_version, kubectl_version); core.debug(`Primary key is ${primaryKey}`); - const matchedKey = await cache.restoreCache(cachePaths, primaryKey); + const matchedKey = await cache.restoreCache(paths, primaryKey); if (matchedKey) { core.info(`Cache setup-kind restored from key: ${matchedKey}`); @@ -34,8 +34,8 @@ export async function restoreSetupKindCache( core.info('Cache setup-kind is not found'); } return { - paths: cachePaths, - primaryKey: primaryKey, + paths, + primaryKey, }; } /** diff --git a/src/installer.ts b/src/installer.ts index 63b2a891..87574b08 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -48,7 +48,7 @@ async function downloadTool( version: string, url: string ): Promise { - console.log(`downloading ${toolName} from ${url}`); + core.info(`downloading ${toolName} from ${url}`); const downloadPath = await tc.downloadTool(url); if (process.platform !== 'win32') { await exec.exec('chmod', ['+x', downloadPath]); diff --git a/src/requirements.ts b/src/requirements.ts index 346fbb51..1d22c5dc 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -43,9 +43,10 @@ function getOctokit() { async function checkKubernetesVersion(version: string) { const octokit = getOctokit(); + const KUBERNETES = 'kubernetes'; const { status } = await octokit.rest.repos.getReleaseByTag({ - owner: 'kubernetes', - repo: 'kubernetes', + owner: KUBERNETES, + repo: KUBERNETES, tag: version, }); ok(status === 200, `Kubernetes ${version} doesn't exists`); @@ -57,14 +58,11 @@ async function checkKubernetesVersion(version: string) { */ async function checkPlatform() { const platform = `${goenv.GOOS}/${goenv.GOARCH}`; - const { version, url } = await ensureKindSupportsPlatform(platform); + const kind = await ensureKindSupportsPlatform(platform); ensureSetupKindSupportsPlatform(platform); return { platform, - kind: { - url: url, - version: version, - }, + kind, }; } From aa984ea2252fe842867495073871b8b872691132 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sun, 13 Feb 2022 10:48:18 +0100 Subject: [PATCH 15/24] change log and log level for tools --- src/installer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index 87574b08..344c3188 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -48,7 +48,7 @@ async function downloadTool( version: string, url: string ): Promise { - core.info(`downloading ${toolName} from ${url}`); + core.info(`Downloading ${toolName}@${version} from ${url}`); const downloadPath = await tc.downloadTool(url); if (process.platform !== 'win32') { await exec.exec('chmod', ['+x', downloadPath]); @@ -59,7 +59,6 @@ async function downloadTool( toolName, version ); - core.debug(`${toolName} is cached under ${toolPath}`); return toolPath; } @@ -76,5 +75,6 @@ async function installTool( downloaded = true; } core.addPath(toolPath); + core.info(`The tool ${toolName}@${version} is cached under ${toolPath}`); return downloaded; } From a903540b02db343abb6deac82e22c6842a98c922 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 10 Feb 2022 10:36:57 +0100 Subject: [PATCH 16/24] parse kubernetes version from kind config file --- .prettierrc.json | 3 +- README.md | 11 +++- __tests__/kind/post.test.ts | 4 +- __tests__/requirements.test.ts | 4 +- action.yml | 2 +- package-lock.json | 22 +++++-- package.json | 2 + src/cache.ts | 27 ++++----- src/constants.ts | 5 +- src/go.ts | 22 +++---- src/installer.ts | 41 +++++-------- src/kind/main.ts | 3 +- src/kind/post.ts | 23 ++----- src/main.ts | 4 +- src/requirements.ts | 107 +++++++++++++++++++++++++-------- 15 files changed, 162 insertions(+), 118 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 8f1add61..0835748d 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,5 @@ { "endOfLine": "auto", + "printWidth": 100, "singleQuote": true -} +} \ No newline at end of file diff --git a/README.md b/README.md index 9545e54d..0a19f995 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,6 @@ jobs: > version 0.6 of Kind. See [this document for a detailed migration > guide][kind-kubeconfig] -> Note: GitHub Actions workers come pre-configured with `kubectl`. - The following arguments can be configured on the job using the `with` keyword (see example above). Currently, possible inputs are all the flags for `kind cluster create`, with the additional version, which sets the Kind version @@ -78,6 +76,15 @@ jobs: echo "environment-kubeconfig:" ${KUBECONFIG} ``` +## Kubectl + +GitHub Actions workers come pre-configured with `kubectl` but if the Kubernetes version can be identified from the image +input or the images of the nodes in the config file, it will be installed in the tool-cache with the right version. + +## Self-hosted agents + +When using on a self-hosted agent, an access to GITHUB_API_URL ( by default) and are required for setup-kind to work properly. + [kind-kubeconfig]: https://github.com/kubernetes-sigs/kind/issues/1060 [gh-actions-path]: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/ diff --git a/__tests__/kind/post.test.ts b/__tests__/kind/post.test.ts index 3142c96f..bd701c74 100644 --- a/__tests__/kind/post.test.ts +++ b/__tests__/kind/post.test.ts @@ -57,9 +57,7 @@ describe('checking input parsing', function () { expect(args).toEqual([ 'export', 'logs', - path.normalize( - '/home/runner/work/_temp/1c1900ec-8f4f-5069-a966-1d3072cc9723' - ), + path.normalize('/home/runner/work/_temp/1c1900ec-8f4f-5069-a966-1d3072cc9723'), '--verbosity', testEnvVars.INPUT_VERBOSITY, '--quiet', diff --git a/__tests__/requirements.test.ts b/__tests__/requirements.test.ts index 0966e801..41e874e3 100644 --- a/__tests__/requirements.test.ts +++ b/__tests__/requirements.test.ts @@ -26,8 +26,6 @@ describe('checking requirements', function () { it('required GITHUB_JOB must be defined', async () => { process.env['GITHUB_JOB'] = ''; - await expect(checkEnvironment()).rejects.toThrow( - 'Expected GITHUB_JOB to be defined' - ); + await expect(checkEnvironment()).rejects.toThrow('Expected GITHUB_JOB to be defined'); }); }); diff --git a/action.yml b/action.yml index 4e2dd144..366e4194 100644 --- a/action.yml +++ b/action.yml @@ -35,7 +35,7 @@ inputs: description: "Silence all stderr output" default: "false" token: - description: "Used to retrieve release informations concerning KinD" + description: "Used to retrieve release informations concerning KinD and Kubernetes from https://api.github.com" default: "${{ github.token }}" required: true runs: diff --git a/package-lock.json b/package-lock.json index 6ce12c3e..2540a8f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,11 +17,13 @@ "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", + "js-yaml": "^4.1.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { "@types/jest": "^27.4.0", + "@types/js-yaml": "^4.0.5", "@types/node": "^17.0.8", "@types/semver": "^7.3.9", "@types/uuid": "^8.3.4", @@ -1637,6 +1639,12 @@ "pretty-format": "^27.0.0" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -2098,8 +2106,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-union": { "version": "2.1.0", @@ -4476,7 +4483,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -7717,6 +7723,12 @@ "pretty-format": "^27.0.0" } }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -8030,8 +8042,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-union": { "version": "2.1.0", @@ -9823,7 +9834,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } diff --git a/package.json b/package.json index 612ab1be..23e85116 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,13 @@ "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", + "js-yaml": "^4.1.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { "@types/jest": "^27.4.0", + "@types/js-yaml": "^4.0.5", "@types/node": "^17.0.8", "@types/semver": "^7.3.9", "@types/uuid": "^8.3.4", diff --git a/src/cache.ts b/src/cache.ts index 06c3f67a..15f5f51e 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -9,20 +9,17 @@ import { KIND_TOOL_NAME, KUBECTL_TOOL_NAME } from './constants'; /** * Prefix of the kind cache key */ -const KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS'] || ''}-${ +const SETUP_KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS'] || ''}-${ process.env['RUNNER_ARCH'] || '' }-setup-kind-`; /** - * Restores Kind by version, $RUNNER_OS and $RUNNER_ARCH + * Restores Kind and Kubectl from cache * @param version */ -export async function restoreSetupKindCache( - kind_version: string, - kubectl_version: string -) { - const primaryKey = setupKindPrimaryKey(kind_version, kubectl_version); - const paths = setupKindCachePaths(kind_version, kubectl_version); +export async function restoreSetupKindCache(kind_version: string, kubernetes_version: string) { + const primaryKey = setupKindPrimaryKey(kind_version, kubernetes_version); + const paths = setupKindCachePaths(kind_version, kubernetes_version); core.debug(`Primary key is ${primaryKey}`); @@ -45,7 +42,7 @@ export async function restoreSetupKindCache( * @param version * @returns the cache paths */ -function setupKindCachePaths(kind_version: string, kubectl_version: string) { +function setupKindCachePaths(kind_version: string, kubernetes_version: string) { const paths = [ path.join( `${process.env['RUNNER_TOOL_CACHE'] || ''}`, @@ -53,12 +50,12 @@ function setupKindCachePaths(kind_version: string, kubectl_version: string) { semver.clean(kind_version) || kind_version ), ]; - if (kubectl_version !== '') { + if (kubernetes_version !== '') { paths.push( path.join( `${process.env['RUNNER_TOOL_CACHE'] || ''}`, KUBECTL_TOOL_NAME, - semver.clean(kubectl_version) || kubectl_version + semver.clean(kubernetes_version) || kubernetes_version ) ); } @@ -72,19 +69,19 @@ function setupKindCachePaths(kind_version: string, kubectl_version: string) { * @param version * @returns the primary Key */ -function setupKindPrimaryKey(kind_version: string, kubectl_version: string) { +function setupKindPrimaryKey(kind_version: string, kubernetes_version: string) { const key = JSON.stringify({ architecture: process.arch, kind: kind_version, - kubectl: kubectl_version, + kubernetes: kubernetes_version, platform: process.platform, }); const hash = crypto.createHash('sha256').update(key).digest('hex'); - return `${KIND_CACHE_KEY_PREFIX}${hash}`; + return `${SETUP_KIND_CACHE_KEY_PREFIX}${hash}`; } /** - * Caches Kind by it's primaryKey + * Save Kind and Kubectl in the cache * @param primaryKey */ export async function saveSetupKindCache(paths: string[], primaryKey: string) { diff --git a/src/constants.ts b/src/constants.ts index 0ba2da36..87a06266 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,5 @@ +import process from 'process'; + export enum Input { Version = 'version', Verbosity = 'verbosity', @@ -28,6 +30,5 @@ export const KIND_COMMAND = process.platform === 'win32' ? 'kind.exe' : 'kind'; export const KIND_DEFAULT_VERSION = 'v0.11.1'; export const KIND_TOOL_NAME = 'kind'; -export const KUBECTL_COMMAND = - process.platform === 'win32' ? 'kubectl.exe' : 'kubectl'; +export const KUBECTL_COMMAND = process.platform === 'win32' ? 'kubectl.exe' : 'kubectl'; export const KUBECTL_TOOL_NAME = 'kubectl'; diff --git a/src/go.ts b/src/go.ts index 5e74a2dc..ee7506fb 100644 --- a/src/go.ts +++ b/src/go.ts @@ -5,7 +5,7 @@ import process from 'process'; * Simulate the calculation of the goos * @returns go env GOOS */ -function _goos(platform: string): string { +function goos(platform: string): string { switch (platform) { case 'sunos': return 'solaris'; @@ -21,7 +21,7 @@ function _goos(platform: string): string { * Based on https://nodejs.org/api/process.html#processarch * @returns go env GOARCH */ -function _goarch(architecture: string, endianness: string): string { +function goarch(architecture: string, endianness: string): string { switch (architecture) { case 'ia32': return '386'; @@ -30,27 +30,23 @@ function _goarch(architecture: string, endianness: string): string { case 'x64': return 'amd64'; case 'arm': - return _withEndiannessOrDefault(architecture, endianness, 'be'); + return withEndiannessOrDefault(architecture, endianness, 'be'); case 'arm64': - return _withEndiannessOrDefault(architecture, endianness, 'be'); + return withEndiannessOrDefault(architecture, endianness, 'be'); case 'mips': - return _withEndiannessOrDefault(architecture, endianness, 'le'); + return withEndiannessOrDefault(architecture, endianness, 'le'); case 'ppc64': - return _withEndiannessOrDefault(architecture, endianness, 'le'); + return withEndiannessOrDefault(architecture, endianness, 'le'); default: return architecture; } } -function _withEndiannessOrDefault( - architecture: string, - endianness: string, - suffix: string -): string { +function withEndiannessOrDefault(architecture: string, endianness: string, suffix: string): string { return endianness === suffix ? architecture + suffix : architecture; } export const env = { - GOARCH: _goarch(process.arch, os.endianness().toLowerCase()), - GOOS: _goos(process.platform), + GOARCH: goarch(process.arch, os.endianness().toLowerCase()), + GOOS: goos(process.platform), }; diff --git a/src/installer.ts b/src/installer.ts index 344c3188..44a3fbf1 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -3,41 +3,38 @@ import * as exec from '@actions/exec'; import * as tc from '@actions/tool-cache'; import process from 'process'; import * as cache from './cache'; -import { - KIND_COMMAND, - KIND_TOOL_NAME, - KUBECTL_COMMAND, - KUBECTL_TOOL_NAME, -} from './constants'; +import { KIND_COMMAND, KIND_TOOL_NAME, KUBECTL_COMMAND, KUBECTL_TOOL_NAME } from './constants'; export async function installTools( kind: { version: string; url: string; }, - kubectl: { + kubernetes: { version: string; url: string; } -) { - const { paths, primaryKey } = await cache.restoreSetupKindCache( - kind.version, - kubectl.version - ); +): Promise { + const { paths, primaryKey } = await cache.restoreSetupKindCache(kind.version, kubernetes.version); const kindDownloaded = await installKind(kind.version, kind.url); - const kubectlDownloaded = await installKubectl(kubectl.version, kubectl.url); - if (kindDownloaded || kubectlDownloaded) { + const kubernetesDownloaded = await installKubernetesTools(kubernetes.version, kubernetes.url); + if (kindDownloaded || kubernetesDownloaded) { await cache.saveSetupKindCache(paths, primaryKey); } } -async function installKind(version: string, url: string) { +async function installKind(version: string, url: string): Promise { return await installTool(KIND_COMMAND, KIND_TOOL_NAME, version, url); } -async function installKubectl(version: string, url: string) { +async function installKubernetesTools(version: string, url: string): Promise { if (version !== '' && url !== '') { - return await installTool(KUBECTL_COMMAND, KUBECTL_TOOL_NAME, version, url); + return await installTool( + KUBECTL_COMMAND, + KUBECTL_TOOL_NAME, + version, + `${url}/${KUBECTL_COMMAND}` + ); } return false; } @@ -53,13 +50,7 @@ async function downloadTool( if (process.platform !== 'win32') { await exec.exec('chmod', ['+x', downloadPath]); } - const toolPath: string = await tc.cacheFile( - downloadPath, - command, - toolName, - version - ); - return toolPath; + return await tc.cacheFile(downloadPath, command, toolName, version); } async function installTool( @@ -67,7 +58,7 @@ async function installTool( toolName: string, version: string, url: string -) { +): Promise { let toolPath: string = tc.find(toolName, version); let downloaded = false; if (toolPath === '') { diff --git a/src/kind/main.ts b/src/kind/main.ts index 95d6d0b0..d4658ce0 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -20,8 +20,7 @@ export class KindMainService { this.name = core.getInput(Input.Name, { required: true }); this.waitDuration = core.getInput(Input.Wait); this.kubeConfigFile = core.getInput(Input.KubeConfig); - this.skipClusterCreation = - core.getInput(Input.SkipClusterCreation) === 'true'; + this.skipClusterCreation = core.getInput(Input.SkipClusterCreation) === 'true'; this.verbosity = +core.getInput(Input.Verbosity); this.quiet = core.getInput(Input.Quiet) === 'true'; } diff --git a/src/kind/post.ts b/src/kind/post.ts index 1a826634..bb1e2091 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -18,10 +18,8 @@ export class KindPostService { private constructor() { this.name = core.getInput(Input.Name); this.kubeConfigFile = core.getInput(Input.KubeConfig); - this.skipClusterDeletion = - core.getInput(Input.SkipClusterDeletion) === 'true'; - this.skipClusterLogsExport = - core.getInput(Input.SkipClusterLogsExport) === 'true'; + this.skipClusterDeletion = core.getInput(Input.SkipClusterDeletion) === 'true'; + this.skipClusterLogsExport = core.getInput(Input.SkipClusterLogsExport) === 'true'; this.verbosity = +core.getInput(Input.Verbosity); this.quiet = core.getInput(Input.Quiet) === 'true'; } @@ -69,17 +67,11 @@ export class KindPostService { dirs.push(this.name); } dirs.push('logs'); - return path.join( - `${process.env['RUNNER_TEMP'] || ''}`, - uuidv5(dirs.join('/'), uuidv5.URL) - ); + return path.join(`${process.env['RUNNER_TEMP'] || ''}`, uuidv5(dirs.join('/'), uuidv5.URL)); } private artifactName(): string { - const artifactArgs: string[] = [ - `${process.env['GITHUB_JOB'] || ''}`, - KIND_TOOL_NAME, - ]; + const artifactArgs: string[] = [`${process.env['GITHUB_JOB'] || ''}`, KIND_TOOL_NAME]; if (this.name != '') { artifactArgs.push(this.name); } @@ -96,12 +88,7 @@ export class KindPostService { const options = { continueOnError: true, }; - await artifactClient.uploadArtifact( - this.artifactName(), - files, - rootDirectory, - options - ); + await artifactClient.uploadArtifact(this.artifactName(), files, rootDirectory, options); } async deleteCluster() { diff --git a/src/main.ts b/src/main.ts index bea3d162..0511424e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,8 +4,8 @@ import { checkEnvironment } from './requirements'; import { installTools } from './installer'; async function run() { - const { kind, kubectl } = await checkEnvironment(); - await installTools(kind, kubectl); + const { kind, kubernetes } = await checkEnvironment(); + await installTools(kind, kubernetes); await KindMainService.getInstance().createCluster(); } diff --git a/src/requirements.ts b/src/requirements.ts index 1d22c5dc..abfac0b3 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -2,31 +2,56 @@ import * as core from '@actions/core'; import * as github from '@actions/github'; import * as io from '@actions/io'; import { ok } from 'assert'; +import fs from 'fs'; +import * as yaml from 'js-yaml'; +import path from 'path'; import * as semver from 'semver'; -import { Input, KIND_DEFAULT_VERSION, KUBECTL_COMMAND } from './constants'; +import { Input, KIND_DEFAULT_VERSION } from './constants'; import { env as goenv } from './go'; export async function checkEnvironment() { checkVariables(); await checkDocker(); const { platform, kind } = await checkPlatform(); - const image = core.getInput(Input.Image); - checkImageForPlatform(image, platform); - checkImageForVersion(image, kind.version); - const kubectl = await getKubectl(image, platform); + const kubernetes = await getKubernetes(platform, kind.version); return { kind, - kubectl, + kubernetes, }; } -async function getKubectl(image: string, platform: string) { - let version = ''; +interface Cluster { + kind?: string; + apiVersion?: string; + name?: string; + nodes?: Node[]; + kubeadmConfigPatches?: string[]; + containerdConfigPatches?: string[]; +} + +interface Node { + role?: string; + image?: string; + kubeadmConfigPatches?: string[]; +} + +async function getKubernetes(platform: string, kind_version: string) { + const image = core.getInput(Input.Image); + checkImageForPlatform(image, platform); + checkImageForVersion(image, kind_version, {}); + let version = parseKubernetesVersion(image); let url = ''; - if (image !== '' && image.startsWith('kindest/node')) { - version = image.split('@')[0].split(':')[1]; + + if (version === '') { + const kindConfig = core.getInput(Input.Config); + if (kindConfig !== '') { + version = parseKubernetesVersionFromConfig(kindConfig, kind_version); + } + } + + if (version !== '') { await checkKubernetesVersion(version); - url = `https://storage.googleapis.com/kubernetes-release/release/${version}/bin/${platform}/${KUBECTL_COMMAND}`; + url = `https://storage.googleapis.com/kubernetes-release/release/${version}/bin/${platform}`; } return { version, @@ -34,6 +59,42 @@ async function getKubectl(image: string, platform: string) { }; } +function parseKubernetesVersionFromConfig(kindConfig: string, kind_version: string) { + let version = ''; + const kindConfigPath = path.join(`${process.env['GITHUB_WORKSPACE'] || ''}`, kindConfig); + const doc = yaml.load(fs.readFileSync(kindConfigPath, 'utf8')) as Cluster; + ok(doc.kind === 'Cluster', `The config file ${kindConfig} must be of kind Cluster`); + if (doc.nodes) { + const versions: string[] = doc.nodes + .map((node) => { + const image = node.image || ''; + checkImageForVersion(image, kind_version, { file: kindConfig }); + return parseKubernetesVersion(image); + }) + .filter((value, index, self) => value && self.indexOf(value) === index) + .sort() + .reverse(); + if (versions.length >= 1) { + version = versions[0]; + if (versions.length > 1) { + core.warning( + `There are multiple versions of Kubernetes, ${version} will be used to configure kubectl`, + { file: kindConfig } + ); + } + } + } + return version; +} + +function parseKubernetesVersion(image: string) { + let version = ''; + if (image && image.startsWith('kindest/node')) { + version = image.split('@')[0].split(':')[1]; + } + return version; +} + function getOctokit() { const token = core.getInput(Input.Token, { required: true }); return github.getOctokit(token, { @@ -125,10 +186,7 @@ async function findVersionAndSupportedPlatforms() { const inputVersion = core.getInput(Input.Version, { required: true }); const { assets, version } = await getReleaseByInputVersion(inputVersion); const platforms = assets.reduce( - ( - total: { [key: string]: string }, - asset: { name: string; browser_download_url: string } - ) => { + (total: { [key: string]: string }, asset: { name: string; browser_download_url: string }) => { const parts = asset.name.split('-'); total[`${parts[1]}/${parts[2]}`] = asset.browser_download_url; return total; @@ -146,9 +204,7 @@ function ensureSetupKindSupportsPlatform(platform: string) { const platforms: string[] = ['linux/amd64', 'linux/arm64']; if (!platforms.includes(platform)) { core.warning( - `engineerd/setup-kind doesn't support platform ${platform} but ${platforms.join( - ' and ' - )}` + `engineerd/setup-kind doesn't support platform ${platform} but ${platforms.join(' and ')}` ); } } @@ -208,14 +264,15 @@ function checkImageForPlatform(image: string, platform: string) { * Prints a warning if a kindest/node is used without sha256. * This follows the recommendation from https://kind.sigs.k8s.io/docs/user/working-offline/#using-a-prebuilt-node-imagenode-image */ -function checkImageForVersion(image: string, version: string) { - if ( - image !== '' && - image.startsWith('kindest/node') && - !image.includes('@sha256:') - ) { +function checkImageForVersion( + image: string, + kind_version: string, + annotationProperties: core.AnnotationProperties +) { + if (image && image.startsWith('kindest/node') && !image.includes('@sha256:')) { core.warning( - `Please include the @sha256: image digest from the image in the release notes. You can find available image tags on the release page, https://github.com/kubernetes-sigs/kind/releases/tag/${version}` + `Please include the @sha256: image digest for ${image} from the image in the release notes. You can find available image tags on the release page, https://github.com/kubernetes-sigs/kind/releases/tag/${kind_version}`, + annotationProperties ); } } From 9649d09cd930a5ed14ac695f2c2fc8d58cbdc0fd Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 17 Feb 2022 14:57:17 +0100 Subject: [PATCH 17/24] Group logs by action --- .github/workflows/e2e.yml | 2 ++ src/installer.ts | 20 ++++++++++++++------ src/kind/main.ts | 4 +++- src/kind/post.ts | 10 +++++++--- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 25a0a344..89d3f619 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -17,6 +17,8 @@ jobs: - name: 'Run engineerd/setup-kind@${{github.sha}}' uses: ./ + with: + image: kindest/node:v1.20.2 - run: kubectl cluster-info diff --git a/src/installer.ts b/src/installer.ts index 44a3fbf1..b8f502e5 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -15,12 +15,20 @@ export async function installTools( url: string; } ): Promise { - const { paths, primaryKey } = await cache.restoreSetupKindCache(kind.version, kubernetes.version); - const kindDownloaded = await installKind(kind.version, kind.url); - const kubernetesDownloaded = await installKubernetesTools(kubernetes.version, kubernetes.url); - if (kindDownloaded || kubernetesDownloaded) { - await cache.saveSetupKindCache(paths, primaryKey); - } + await core.group( + `Install kind@${kind.version}${kubernetes.version ? ' and kubectl@' + kubernetes.version : ''}`, + async () => { + const { paths, primaryKey } = await cache.restoreSetupKindCache( + kind.version, + kubernetes.version + ); + const kindDownloaded = await installKind(kind.version, kind.url); + const kubernetesDownloaded = await installKubernetesTools(kubernetes.version, kubernetes.url); + if (kindDownloaded || kubernetesDownloaded) { + await cache.saveSetupKindCache(paths, primaryKey); + } + } + ); } async function installKind(version: string, url: string): Promise { diff --git a/src/kind/main.ts b/src/kind/main.ts index d4658ce0..d0aeb6ff 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -63,6 +63,8 @@ export class KindMainService { if (this.skipClusterCreation) { return; } - await executeKindCommand(this.createCommand()); + await core.group(`Create cluster "${this.name}"`, async () => { + await executeKindCommand(this.createCommand()); + }); } } diff --git a/src/kind/post.ts b/src/kind/post.ts index bb1e2091..e7d23bfc 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -95,14 +95,18 @@ export class KindPostService { if (this.skipClusterDeletion) { return; } - await executeKindCommand(this.deleteCommand()); + await core.group(`Delete cluster "${this.name}"`, async () => { + await executeKindCommand(this.deleteCommand()); + }); } async exportClusterLogs() { if (this.skipClusterLogsExport) { return; } - await executeKindCommand(this.exportLogsCommand()); - await this.uploadKindLogs(); + await core.group(`Export logs for cluster "${this.name}"`, async () => { + await executeKindCommand(this.exportLogsCommand()); + await this.uploadKindLogs(); + }); } } From 4701224bee3a9ba90352d19717efd6181ed3514b Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 18 Feb 2022 11:32:10 +0100 Subject: [PATCH 18/24] setup a Metallb load-balancer --- .github/workflows/e2e.yml | 1 + README.md | 1 + action.yml | 3 + src/constants.ts | 1 + src/load-balancer.ts | 144 ++++++++++++++++++++++++++++++++++++++ src/main.ts | 4 +- 6 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/load-balancer.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 89d3f619..56a02796 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -19,6 +19,7 @@ jobs: uses: ./ with: image: kindest/node:v1.20.2 + loadBalancer: true - run: kubectl cluster-info diff --git a/README.md b/README.md index 0a19f995..e0713555 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Optional inputs: - `skipClusterLogsExport`: if `"true"`, the action will not export the cluster logs - `verbosity`: numeric log verbosity, (info = 0, debug = 3, trace = 2147483647) (default `"0"`) - `quiet`: silence all stderr output (default `"false"`) +- `loadBalancer`: setup a Metallb load-balancer (default `"false"`) Example using optional inputs: diff --git a/action.yml b/action.yml index 366e4194..e929a138 100644 --- a/action.yml +++ b/action.yml @@ -38,6 +38,9 @@ inputs: description: "Used to retrieve release informations concerning KinD and Kubernetes from https://api.github.com" default: "${{ github.token }}" required: true + loadBalancer: + description: "Setup a Metallb load-balancer" + default: "false" runs: using: "node12" main: "dist/main/index.js" diff --git a/src/constants.ts b/src/constants.ts index 87a06266..2d476451 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -6,6 +6,7 @@ export enum Input { Quiet = 'quiet', Config = 'config', Image = 'image', + LoadBalancer = 'loadBalancer', Name = 'name', Token = 'token', Wait = 'wait', diff --git a/src/load-balancer.ts b/src/load-balancer.ts new file mode 100644 index 00000000..c7598022 --- /dev/null +++ b/src/load-balancer.ts @@ -0,0 +1,144 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; +import fs from 'fs'; +import * as yaml from 'js-yaml'; +import path from 'path'; +import { v5 as uuidv5 } from 'uuid'; +import { Input, KIND_TOOL_NAME, KUBECTL_COMMAND } from './constants'; + +async function executeKubectlCommand(args: string[]) { + await exec.exec(KUBECTL_COMMAND, args); +} + +async function kubectlApply(file: string) { + const args: string[] = ['apply', '-f', file]; + await executeKubectlCommand(args); +} + +const METALLB_DEFAULT_VERSION = 'v0.12.1'; + +async function createMemberlistSecrets() { + await core.group('Create the memberlist secrets', async () => { + const args: string[] = [ + 'create', + 'secret', + 'generic', + '-n', + 'metallb-system', + 'memberlist', + '--from-literal=secretkey="$(openssl rand -base64 128)"', + ]; + await executeKubectlCommand(args); + }); +} + +async function createMetallbNamespace() { + await core.group('Create the metallb namespace', async () => { + await kubectlApply( + `https://raw.githubusercontent.com/metallb/metallb/${METALLB_DEFAULT_VERSION}/manifests/namespace.yaml` + ); + }); +} + +async function applyMetallbManifest() { + await core.group('Apply metallb manifest', async () => { + await kubectlApply( + `https://raw.githubusercontent.com/metallb/metallb/${METALLB_DEFAULT_VERSION}/manifests/metallb.yaml` + ); + }); +} + +export async function setUpLoadBalancer() { + if (hasLoadBalancer()) { + await createMetallbNamespace(); + await createMemberlistSecrets(); + await applyMetallbManifest(); + await waitForMetallbPods(); + await setupAddressPool(); + } +} + +async function waitForMetallbPods() { + await core.group('Wait for metallb pods to have a status of Running', async () => { + const args: string[] = [ + 'wait', + '-n', + 'metallb-system', + 'pod', + '--all', + '--for=condition=ready', + '--timeout=240s', + ]; + await executeKubectlCommand(args); + }); +} + +async function getIPBytes() { + const args: string[] = [ + 'network', + 'inspect', + '-f', + "'{{(index .IPAM.Config 0).Subnet}}'", + 'kind', + ]; + const { stdout } = await exec.getExecOutput('docker', args, { silent: true }); + const bytes = stdout.replace(/'/g, '').split('.'); + return { + first: bytes[0], + second: bytes[1], + }; +} + +export async function setupAddressPool() { + await core.group('Setup address pool used by load-balancers', async () => { + const { first, second } = await getIPBytes(); + + const addressPool = { + 'address-pools': [ + { + name: 'default', + protocol: 'layer2', + addresses: [`${first}.${second}.255.200-${first}.${second}.255.250`], + }, + ], + }; + + const configMap = { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + namespace: 'metallb-system', + name: 'config', + }, + data: { + config: yaml.dump(addressPool), + }, + }; + const dirs: string[] = [KIND_TOOL_NAME, core.getInput(Input.Name), 'load-balancer']; + const dir = path.join( + `${process.env['RUNNER_TEMP'] || ''}`, + uuidv5(dirs.join('/'), uuidv5.URL) + ); + const file = path.join(dir, 'metallb-configmap.yaml'); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + const data = yaml.dump(configMap); + core.debug(`Dumping into ${file}: \n${data}`); + fs.writeFileSync(file, data, 'utf8'); + await kubectlApply(file); + }); +} + +function hasLoadBalancer() { + if (core.getInput(Input.LoadBalancer) == 'true') { + if (core.getInput(Input.SkipClusterCreation) == 'true') { + core.warning( + "The load-balancer requires the cluster to exists. It's configuration will be skipped" + ); + return false; + } + return true; + } + return false; +} diff --git a/src/main.ts b/src/main.ts index 0511424e..50af511a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,14 @@ import * as core from '@actions/core'; +import { installTools } from './installer'; import { KindMainService } from './kind/main'; +import { setUpLoadBalancer } from './load-balancer'; import { checkEnvironment } from './requirements'; -import { installTools } from './installer'; async function run() { const { kind, kubernetes } = await checkEnvironment(); await installTools(kind, kubernetes); await KindMainService.getInstance().createCluster(); + await setUpLoadBalancer(); } run().catch((error) => { From 18ba7990039387d3fe43cc925b13bdf0d01f0e97 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 18 Feb 2022 19:08:31 +0100 Subject: [PATCH 19/24] setup a local registry --- .github/workflows/checkin.yml | 4 +- .github/workflows/e2e.yml | 3 +- .gitignore | 3 +- README.md | 10 +++ __tests__/kind/main.test.ts | 2 +- __tests__/local-registry.test.ts | 36 ++++++++ action.yml | 61 ++++++------- package-lock.json | 30 +++++++ package.json | 2 + src/cache.ts | 19 ++--- src/constants.ts | 7 +- src/containerd.d.ts | 15 ++++ src/installer.ts | 11 ++- src/kind/core.ts | 2 +- src/kind/main.ts | 20 +++-- src/kind/post.ts | 6 +- src/kubectl.ts | 49 +++++++++++ src/kubernetes.d.ts | 26 ++++++ src/load-balancer.ts | 104 +++++++---------------- src/local-registry.ts | 141 +++++++++++++++++++++++++++++++ src/main.ts | 5 +- src/post-local-registry.ts | 13 +++ src/post.ts | 2 + src/requirements.ts | 70 +++++++-------- src/yaml-helper.ts | 18 ++++ 25 files changed, 486 insertions(+), 173 deletions(-) create mode 100644 __tests__/local-registry.test.ts create mode 100644 src/containerd.d.ts create mode 100644 src/kubectl.ts create mode 100644 src/kubernetes.d.ts create mode 100644 src/local-registry.ts create mode 100644 src/post-local-registry.ts create mode 100644 src/yaml-helper.ts diff --git a/.github/workflows/checkin.yml b/.github/workflows/checkin.yml index 0f66c870..e517dd95 100644 --- a/.github/workflows/checkin.yml +++ b/.github/workflows/checkin.yml @@ -1,4 +1,4 @@ -name: "Build and test Action" +name: 'Build and test Action' on: [push, pull_request] jobs: @@ -20,5 +20,5 @@ jobs: - run: npm run test:coverage - uses: codecov/codecov-action@v2 - + - run: npm audit diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 56a02796..77d59434 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -18,8 +18,9 @@ jobs: - name: 'Run engineerd/setup-kind@${{github.sha}}' uses: ./ with: - image: kindest/node:v1.20.2 + image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6 loadBalancer: true + localRegistry: true - run: kubectl cluster-info diff --git a/.gitignore b/.gitignore index e61de3ed..45d8a6f9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ __tests__/runner/* lib/ dist/ -coverage/ \ No newline at end of file +coverage/ +.vscode/ diff --git a/README.md b/README.md index e0713555..83ac6f04 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Optional inputs: - `verbosity`: numeric log verbosity, (info = 0, debug = 3, trace = 2147483647) (default `"0"`) - `quiet`: silence all stderr output (default `"false"`) - `loadBalancer`: setup a Metallb load-balancer (default `"false"`) +- `localRegistry`: setup a local registry on localhost:5000 (default `"false"`) Example using optional inputs: @@ -82,6 +83,15 @@ jobs: GitHub Actions workers come pre-configured with `kubectl` but if the Kubernetes version can be identified from the image input or the images of the nodes in the config file, it will be installed in the tool-cache with the right version. +## Load-balancer + +When `loadBalancer: true` a load-balancer is created based on + +## Local registry + +When `localRegistry: true` a local registry is created based on +It is then available on localhost:5000 as KIND_REGISTRY on the host machine + ## Self-hosted agents When using on a self-hosted agent, an access to GITHUB_API_URL ( by default) and are required for setup-kind to work properly. diff --git a/__tests__/kind/main.test.ts b/__tests__/kind/main.test.ts index 859f8a48..eca46775 100644 --- a/__tests__/kind/main.test.ts +++ b/__tests__/kind/main.test.ts @@ -44,7 +44,7 @@ describe('checking input parsing', function () { }); it('correctly generates the cluster create command', () => { - const args: string[] = KindMainService.getInstance().createCommand(); + const args: string[] = KindMainService.getInstance().createCommand(''); expect(args).toEqual([ 'create', 'cluster', diff --git a/__tests__/local-registry.test.ts b/__tests__/local-registry.test.ts new file mode 100644 index 00000000..a5ffeb84 --- /dev/null +++ b/__tests__/local-registry.test.ts @@ -0,0 +1,36 @@ +import { ConfigPatch } from '../src/containerd'; +import { hasRegistryConfig, parseConfigPatch } from '../src/local-registry'; + +describe('checking go env simulation', function () { + it('correctly parse ConfigPatch', () => { + const configPatch = `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] + endpoint = ["http://kind-registry:5000"]`; + + const json: ConfigPatch = parseConfigPatch(configPatch); + + expect(json).not.toBeNull(); + expect(json.plugins).not.toBeNull(); + expect(json.plugins['io.containerd.grpc.v1.cri']).not.toBeNull(); + expect(json.plugins['io.containerd.grpc.v1.cri'].registry).not.toBeNull(); + expect(json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors).not.toBeNull(); + expect( + json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors['localhost:5000'] + ).not.toBeNull(); + expect( + json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors['localhost:5000'].endpoint + ).not.toBeNull(); + expect( + json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors['localhost:5000'].endpoint + ).toContain('http://kind-registry:5000'); + }); + it('misconfigured', () => { + const configPatch = `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5001"] + endpoint = ["http://kind-registry:5000"]`; + expect(hasRegistryConfig(configPatch)).toBeFalsy(); + }); + it('right configured', () => { + const configPatch = `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] + endpoint = ["http://kind-registry:5000"]`; + expect(hasRegistryConfig(configPatch)).toBeTruthy(); + }); +}); diff --git a/action.yml b/action.yml index e929a138..783036f1 100644 --- a/action.yml +++ b/action.yml @@ -1,48 +1,51 @@ -name: "KinD (Kubernetes in Docker) Action" -description: "Easily run a Kubernetes cluster in your GitHub Action" -author: "Engineerd" +name: 'KinD (Kubernetes in Docker) Action' +description: 'Easily run a Kubernetes cluster in your GitHub Action' +author: 'Engineerd' inputs: version: - description: "Version of Kind to use (default v0.11.1)" - default: "v0.11.1" + description: 'Version of Kind to use (default v0.11.1)' + default: 'v0.11.1' required: true config: - description: "Path (relative to the root of the repository) to a kind config file" + description: 'Path (relative to the root of the repository) to a kind config file' image: - description: "Node Docker image to use for booting the cluster" + description: 'Node Docker image to use for booting the cluster' name: - description: "Cluster name (default kind)" - default: "kind" + description: 'Cluster name (default kind)' + default: 'kind' required: true wait: - description: "Wait for control plane node to be ready (default 300s)" - default: "300s" + description: 'Wait for control plane node to be ready (default 300s)' + default: '300s' kubeconfig: - description: "Sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config" + description: 'Sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config' skipClusterCreation: - description: "If true, the action will not create a cluster, just acquire the tools" - default: "false" + description: 'If true, the action will not create a cluster, just acquire the tools' + default: 'false' skipClusterDeletion: - description: "If true, the action will not delete the cluster" - default: "false" + description: 'If true, the action will not delete the cluster' + default: 'false' skipClusterLogsExport: - description: "If true, the action will not export the cluster logs" - default: "false" + description: 'If true, the action will not export the cluster logs' + default: 'false' verbosity: - description: "Defines log verbosity with a numeric value, (info = 0, debug = 3, trace = 2147483647)" - default: "0" + description: 'Defines log verbosity with a numeric value, (info = 0, debug = 3, trace = 2147483647)' + default: '0' quiet: - description: "Silence all stderr output" - default: "false" + description: 'Silence all stderr output' + default: 'false' token: - description: "Used to retrieve release informations concerning KinD and Kubernetes from https://api.github.com" - default: "${{ github.token }}" + description: 'Used to retrieve release informations concerning KinD and Kubernetes from https://api.github.com' + default: '${{ github.token }}' required: true loadBalancer: - description: "Setup a Metallb load-balancer" - default: "false" + description: 'Setup a Metallb load-balancer' + default: 'false' + localRegistry: + description: 'Setup a local registry on localhost:5000' + default: 'false' runs: - using: "node12" - main: "dist/main/index.js" - post: "dist/post/index.js" + using: 'node12' + main: 'dist/main/index.js' + post: 'dist/post/index.js' post-if: success() diff --git a/package-lock.json b/package-lock.json index 2540a8f2..e9d07d99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,11 +17,13 @@ "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", + "@iarna/toml": "^2.2.5", "js-yaml": "^4.1.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { + "@types/iarna__toml": "^2.0.2", "@types/jest": "^27.4.0", "@types/js-yaml": "^4.0.5", "@types/node": "^17.0.8", @@ -1055,6 +1057,11 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1605,6 +1612,15 @@ "@types/node": "*" } }, + "node_modules/@types/iarna__toml": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/iarna__toml/-/iarna__toml-2.0.2.tgz", + "integrity": "sha512-Q3obxKhBLVVbEQ8zsAmsQVobAAZhi8dFFFjF0q5xKXiaHvH8IkSxcbM27e46M9feUMieR03SPpmp5CtaNzpdBg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -7222,6 +7238,11 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -7689,6 +7710,15 @@ "@types/node": "*" } }, + "@types/iarna__toml": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/iarna__toml/-/iarna__toml-2.0.2.tgz", + "integrity": "sha512-Q3obxKhBLVVbEQ8zsAmsQVobAAZhi8dFFFjF0q5xKXiaHvH8IkSxcbM27e46M9feUMieR03SPpmp5CtaNzpdBg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", diff --git a/package.json b/package.json index 23e85116..76c3e821 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,13 @@ "@actions/glob": "^0.2.0", "@actions/io": "^1.1.1", "@actions/tool-cache": "^1.7.1", + "@iarna/toml": "^2.2.5", "js-yaml": "^4.1.0", "semver": "^7.3.5", "uuid": "^8.3.2" }, "devDependencies": { + "@types/iarna__toml": "^2.0.2", "@types/jest": "^27.4.0", "@types/js-yaml": "^4.0.5", "@types/node": "^17.0.8", diff --git a/src/cache.ts b/src/cache.ts index 15f5f51e..a7d406f1 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -6,13 +6,6 @@ import process from 'process'; import * as semver from 'semver'; import { KIND_TOOL_NAME, KUBECTL_TOOL_NAME } from './constants'; -/** - * Prefix of the kind cache key - */ -const SETUP_KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS'] || ''}-${ - process.env['RUNNER_ARCH'] || '' -}-setup-kind-`; - /** * Restores Kind and Kubectl from cache * @param version @@ -43,17 +36,14 @@ export async function restoreSetupKindCache(kind_version: string, kubernetes_ver * @returns the cache paths */ function setupKindCachePaths(kind_version: string, kubernetes_version: string) { + const RUNNER_TOOL_CACHE = `${process.env['RUNNER_TOOL_CACHE'] || ''}`; const paths = [ - path.join( - `${process.env['RUNNER_TOOL_CACHE'] || ''}`, - KIND_TOOL_NAME, - semver.clean(kind_version) || kind_version - ), + path.join(RUNNER_TOOL_CACHE, KIND_TOOL_NAME, semver.clean(kind_version) || kind_version), ]; if (kubernetes_version !== '') { paths.push( path.join( - `${process.env['RUNNER_TOOL_CACHE'] || ''}`, + RUNNER_TOOL_CACHE, KUBECTL_TOOL_NAME, semver.clean(kubernetes_version) || kubernetes_version ) @@ -70,6 +60,9 @@ function setupKindCachePaths(kind_version: string, kubernetes_version: string) { * @returns the primary Key */ function setupKindPrimaryKey(kind_version: string, kubernetes_version: string) { + const SETUP_KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS'] || ''}-${ + process.env['RUNNER_ARCH'] || '' + }-setup-kind-`; const key = JSON.stringify({ architecture: process.arch, kind: kind_version, diff --git a/src/constants.ts b/src/constants.ts index 2d476451..1ec76200 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,6 +7,7 @@ export enum Input { Config = 'config', Image = 'image', LoadBalancer = 'loadBalancer', + LocalRegistry = 'localRegistry', Name = 'name', Token = 'token', Wait = 'wait', @@ -27,9 +28,11 @@ export enum Flag { KubeConfig = '--kubeconfig', } -export const KIND_COMMAND = process.platform === 'win32' ? 'kind.exe' : 'kind'; +export const IS_WINDOWS = process.platform === 'win32'; + +export const KIND_COMMAND = IS_WINDOWS ? 'kind.exe' : 'kind'; export const KIND_DEFAULT_VERSION = 'v0.11.1'; export const KIND_TOOL_NAME = 'kind'; -export const KUBECTL_COMMAND = process.platform === 'win32' ? 'kubectl.exe' : 'kubectl'; +export const KUBECTL_COMMAND = IS_WINDOWS ? 'kubectl.exe' : 'kubectl'; export const KUBECTL_TOOL_NAME = 'kubectl'; diff --git a/src/containerd.d.ts b/src/containerd.d.ts new file mode 100644 index 00000000..dc9b701d --- /dev/null +++ b/src/containerd.d.ts @@ -0,0 +1,15 @@ +export interface ConfigPatch { + plugins: { [key: string]: PluginConfig }; +} + +export interface PluginConfig { + registry: Registry; +} + +export interface Registry { + mirrors: { [key: string]: Mirror }; +} + +export interface Mirror { + endpoint: string[]; +} diff --git a/src/installer.ts b/src/installer.ts index b8f502e5..da7a1ef7 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -1,9 +1,14 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; import * as tc from '@actions/tool-cache'; -import process from 'process'; import * as cache from './cache'; -import { KIND_COMMAND, KIND_TOOL_NAME, KUBECTL_COMMAND, KUBECTL_TOOL_NAME } from './constants'; +import { + IS_WINDOWS, + KIND_COMMAND, + KIND_TOOL_NAME, + KUBECTL_COMMAND, + KUBECTL_TOOL_NAME, +} from './constants'; export async function installTools( kind: { @@ -55,7 +60,7 @@ async function downloadTool( ): Promise { core.info(`Downloading ${toolName}@${version} from ${url}`); const downloadPath = await tc.downloadTool(url); - if (process.platform !== 'win32') { + if (!IS_WINDOWS) { await exec.exec('chmod', ['+x', downloadPath]); } return await tc.cacheFile(downloadPath, command, toolName, version); diff --git a/src/kind/core.ts b/src/kind/core.ts index 5069bb14..dff60b26 100644 --- a/src/kind/core.ts +++ b/src/kind/core.ts @@ -1,6 +1,6 @@ import * as exec from '@actions/exec'; import { KIND_COMMAND } from '../constants'; -export async function executeKindCommand(args: string[]) { +export async function executeKind(args: string[]) { await exec.exec(KIND_COMMAND, args); } diff --git a/src/kind/main.ts b/src/kind/main.ts index d0aeb6ff..cf058f4d 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -2,7 +2,7 @@ import * as core from '@actions/core'; import path from 'path'; import process from 'process'; import { Flag, Input } from '../constants'; -import { executeKindCommand } from './core'; +import { executeKind } from './core'; export class KindMainService { configFile: string; @@ -30,7 +30,7 @@ export class KindMainService { } // returns the arguments to pass to `kind create cluster` - createCommand(): string[] { + createCommand(configFile: string): string[] { const args: string[] = ['create', 'cluster']; if (this.verbosity > 0) { args.push(Flag.Verbosity, this.verbosity.toString()); @@ -38,33 +38,35 @@ export class KindMainService { if (this.quiet) { args.push(Flag.Quiet); } - if (this.configFile != '') { + if (this.configFile !== '') { args.push( Flag.Config, path.join(`${process.env['GITHUB_WORKSPACE'] || ''}`, this.configFile) ); + } else if (configFile !== '') { + args.push(Flag.Config, configFile); } - if (this.image != '') { + if (this.image !== '') { args.push(Flag.Image, this.image); } - if (this.name != '') { + if (this.name !== '') { args.push(Flag.Name, this.name); } - if (this.waitDuration != '') { + if (this.waitDuration !== '') { args.push(Flag.Wait, this.waitDuration); } - if (this.kubeConfigFile != '') { + if (this.kubeConfigFile !== '') { args.push(Flag.KubeConfig, this.kubeConfigFile); } return args; } - async createCluster() { + async createCluster(configFile: string) { if (this.skipClusterCreation) { return; } await core.group(`Create cluster "${this.name}"`, async () => { - await executeKindCommand(this.createCommand()); + await executeKind(this.createCommand(configFile)); }); } } diff --git a/src/kind/post.ts b/src/kind/post.ts index e7d23bfc..8894dde9 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -5,7 +5,7 @@ import path from 'path'; import process from 'process'; import { v5 as uuidv5 } from 'uuid'; import { Flag, Input, KIND_TOOL_NAME } from '../constants'; -import { executeKindCommand } from './core'; +import { executeKind } from './core'; export class KindPostService { name: string; @@ -96,7 +96,7 @@ export class KindPostService { return; } await core.group(`Delete cluster "${this.name}"`, async () => { - await executeKindCommand(this.deleteCommand()); + await executeKind(this.deleteCommand()); }); } @@ -105,7 +105,7 @@ export class KindPostService { return; } await core.group(`Export logs for cluster "${this.name}"`, async () => { - await executeKindCommand(this.exportLogsCommand()); + await executeKind(this.exportLogsCommand()); await this.uploadKindLogs(); }); } diff --git a/src/kubectl.ts b/src/kubectl.ts new file mode 100644 index 00000000..f5b59f4b --- /dev/null +++ b/src/kubectl.ts @@ -0,0 +1,49 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; +import path from 'path'; +import { v5 as uuidv5 } from 'uuid'; +import { Input, KUBECTL_COMMAND, KUBECTL_TOOL_NAME } from './constants'; +import { ConfigMap } from './kubernetes'; +import { write } from './yaml-helper'; + +export async function executeKubectl(args: string[]) { + await exec.exec(KUBECTL_COMMAND, args); +} + +export async function apply(file: string) { + const args: string[] = ['apply', '-f', file]; + await executeKubectl(args); +} + +export async function applyConfigMap(configMap: ConfigMap, fileName: string) { + const dirs: string[] = [KUBECTL_TOOL_NAME, core.getInput(Input.Name)]; + const dir = path.join(`${process.env['RUNNER_TEMP'] || ''}`, uuidv5(dirs.join('/'), uuidv5.URL)); + const file = write(dir, fileName, configMap); + await apply(file); +} + +export async function createMemberlistSecret(namespace: string) { + const args: string[] = [ + 'create', + 'secret', + 'generic', + '-n', + namespace, + 'memberlist', + '--from-literal=secretkey="$(openssl rand -base64 128)"', + ]; + await executeKubectl(args); +} + +export async function waitForPodReady(namespace: string) { + const args: string[] = [ + 'wait', + '-n', + namespace, + 'pod', + '--all', + '--for=condition=ready', + '--timeout=240s', + ]; + await executeKubectl(args); +} diff --git a/src/kubernetes.d.ts b/src/kubernetes.d.ts new file mode 100644 index 00000000..429410cb --- /dev/null +++ b/src/kubernetes.d.ts @@ -0,0 +1,26 @@ +export interface Cluster { + kind: string; + apiVersion: string; + name?: string; + nodes?: Node[]; + kubeadmConfigPatches?: string[]; + containerdConfigPatches?: string[]; +} + +export interface ConfigMap { + apiVersion: string; + kind: string; + metadata: Metadata; + data: { [key: string]: string }; +} + +export interface Metadata { + namespace: string; + name: string; +} + +export interface Node { + role: string; + image: string; + kubeadmConfigPatches?: string[]; +} diff --git a/src/load-balancer.ts b/src/load-balancer.ts index c7598022..f4c40784 100644 --- a/src/load-balancer.ts +++ b/src/load-balancer.ts @@ -1,75 +1,50 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; -import fs from 'fs'; import * as yaml from 'js-yaml'; -import path from 'path'; -import { v5 as uuidv5 } from 'uuid'; -import { Input, KIND_TOOL_NAME, KUBECTL_COMMAND } from './constants'; - -async function executeKubectlCommand(args: string[]) { - await exec.exec(KUBECTL_COMMAND, args); -} - -async function kubectlApply(file: string) { - const args: string[] = ['apply', '-f', file]; - await executeKubectlCommand(args); -} +import { Input } from './constants'; +import * as kubectl from './kubectl'; +import { ConfigMap } from './kubernetes'; const METALLB_DEFAULT_VERSION = 'v0.12.1'; +const METALLB_SYSTEM = 'metallb-system'; + async function createMemberlistSecrets() { await core.group('Create the memberlist secrets', async () => { - const args: string[] = [ - 'create', - 'secret', - 'generic', - '-n', - 'metallb-system', - 'memberlist', - '--from-literal=secretkey="$(openssl rand -base64 128)"', - ]; - await executeKubectlCommand(args); + await kubectl.createMemberlistSecret(METALLB_SYSTEM); }); } -async function createMetallbNamespace() { - await core.group('Create the metallb namespace', async () => { - await kubectlApply( - `https://raw.githubusercontent.com/metallb/metallb/${METALLB_DEFAULT_VERSION}/manifests/namespace.yaml` +async function createNamespace(version: string) { + await core.group(`Create the metallb@${version} namespace`, async () => { + await kubectl.apply( + `https://raw.githubusercontent.com/metallb/metallb/${version}/manifests/namespace.yaml` ); }); } -async function applyMetallbManifest() { - await core.group('Apply metallb manifest', async () => { - await kubectlApply( - `https://raw.githubusercontent.com/metallb/metallb/${METALLB_DEFAULT_VERSION}/manifests/metallb.yaml` +async function applyManifest(version: string) { + await core.group(`Apply metallb@${version} manifest`, async () => { + await kubectl.apply( + `https://raw.githubusercontent.com/metallb/metallb/${version}/manifests/metallb.yaml` ); }); } export async function setUpLoadBalancer() { if (hasLoadBalancer()) { - await createMetallbNamespace(); + const version = METALLB_DEFAULT_VERSION; + await createNamespace(version); await createMemberlistSecrets(); - await applyMetallbManifest(); - await waitForMetallbPods(); + await applyManifest(version); + await waitForPods(); await setupAddressPool(); } } -async function waitForMetallbPods() { +async function waitForPods() { await core.group('Wait for metallb pods to have a status of Running', async () => { - const args: string[] = [ - 'wait', - '-n', - 'metallb-system', - 'pod', - '--all', - '--for=condition=ready', - '--timeout=240s', - ]; - await executeKubectlCommand(args); + await kubectl.waitForPodReady(METALLB_SYSTEM); }); } @@ -92,41 +67,26 @@ async function getIPBytes() { export async function setupAddressPool() { await core.group('Setup address pool used by load-balancers', async () => { const { first, second } = await getIPBytes(); - - const addressPool = { - 'address-pools': [ - { - name: 'default', - protocol: 'layer2', - addresses: [`${first}.${second}.255.200-${first}.${second}.255.250`], - }, - ], - }; - - const configMap = { + const configMap: ConfigMap = { apiVersion: 'v1', kind: 'ConfigMap', metadata: { - namespace: 'metallb-system', + namespace: METALLB_SYSTEM, name: 'config', }, data: { - config: yaml.dump(addressPool), + config: yaml.dump({ + 'address-pools': [ + { + name: 'default', + protocol: 'layer2', + addresses: [`${first}.${second}.255.200-${first}.${second}.255.250`], + }, + ], + }), }, }; - const dirs: string[] = [KIND_TOOL_NAME, core.getInput(Input.Name), 'load-balancer']; - const dir = path.join( - `${process.env['RUNNER_TEMP'] || ''}`, - uuidv5(dirs.join('/'), uuidv5.URL) - ); - const file = path.join(dir, 'metallb-configmap.yaml'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - const data = yaml.dump(configMap); - core.debug(`Dumping into ${file}: \n${data}`); - fs.writeFileSync(file, data, 'utf8'); - await kubectlApply(file); + await kubectl.applyConfigMap(configMap, 'metallb-configmap.yaml'); }); } diff --git a/src/local-registry.ts b/src/local-registry.ts new file mode 100644 index 00000000..a5dbac0e --- /dev/null +++ b/src/local-registry.ts @@ -0,0 +1,141 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; +import * as TOML from '@iarna/toml'; +import * as yaml from 'js-yaml'; +import path from 'path'; +import { v5 as uuidv5 } from 'uuid'; +import { Input, KIND_TOOL_NAME } from './constants'; +import { ConfigPatch } from './containerd'; +import * as kubectl from './kubectl'; +import { Cluster, ConfigMap } from './kubernetes'; +import { write } from './yaml-helper'; + +export const REGISTRY_NAME = 'kind-registry'; +export const REGISTRY_HOST = 'localhost'; +export const REGISTRY_PORT = '5000'; +export const KIND_REGISTRY = `${REGISTRY_HOST}:${REGISTRY_PORT}`; +const REGISTRY_IMAGE = 'registry:2'; + +export async function initRegistrySetup() { + if (core.getInput(Input.LocalRegistry) === 'true') { + await createRegistryUnlessAlreadyExists(); + return createKindConfig(); + } + return ''; +} + +async function createRegistryUnlessAlreadyExists() { + if (!(await registryAlreadyExists())) { + await createRegistry(); + } + core.exportVariable('KIND_REGISTRY', KIND_REGISTRY); +} + +async function connectRegistryToClusterNetwork() { + await core.group(`Connect ${REGISTRY_NAME} to the kind network`, async () => { + const args = ['network', 'connect', 'kind', REGISTRY_NAME]; + await exec.exec('docker', args); + }); +} + +async function createRegistry() { + await core.group( + `Create ${REGISTRY_NAME} at ${KIND_REGISTRY} with ${REGISTRY_IMAGE}`, + async () => { + const args = [ + 'run', + '-d', + '--restart', + 'always', + '-p', + `${REGISTRY_PORT}:5000`, + '--name', + REGISTRY_NAME, + REGISTRY_IMAGE, + ]; + await exec.exec('docker', args); + } + ); +} + +function createKindConfig() { + if (core.getInput(Input.Config) === '') { + const cluster: Cluster = { + kind: 'Cluster', + apiVersion: 'kind.x-k8s.io/v1alpha4', + containerdConfigPatches: [ + `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."${KIND_REGISTRY}"] + endpoint = ["http://${REGISTRY_NAME}:5000"]`, + ], + }; + const dirs: string[] = [KIND_TOOL_NAME, core.getInput(Input.Name)]; + const dir = path.join( + `${process.env['RUNNER_TEMP'] || ''}`, + uuidv5(dirs.join('/'), uuidv5.URL) + ); + return write(dir, 'kind-config.yaml', cluster); + } + return ''; +} + +async function registryAlreadyExists() { + const args = ['inspect', '-f', "'{{.State.Running}}'", REGISTRY_NAME]; + const exitCode = await exec.exec('docker', args, { + ignoreReturnCode: true, + silent: true, + }); + return exitCode === 0; +} + +export async function finishRegistrySetup() { + if (core.getInput(Input.LocalRegistry) === 'true') { + await connectRegistryToClusterNetwork(); + await documentRegistry(); + } +} + +async function documentRegistry() { + await core.group(`Document ${REGISTRY_NAME}`, async () => { + const configMap: ConfigMap = { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: 'local-registry-hosting', + namespace: 'kube-public', + }, + data: { + 'localRegistryHosting.v1': yaml.dump( + { + host: KIND_REGISTRY, + help: 'https://kind.sigs.k8s.io/docs/user/local-registry/', + }, + { + quotingType: '"', + forceQuotes: true, + } + ), + }, + }; + await kubectl.applyConfigMap(configMap, 'local-registry-configmap.yaml'); + }); +} + +export function parseConfigPatch(configPatch: string) { + return JSON.parse(JSON.stringify(TOML.parse(configPatch))) as ConfigPatch; +} + +export function hasRegistryConfig(configPatch: string) { + const config: ConfigPatch = parseConfigPatch(configPatch); + return ( + config && + config.plugins && + config.plugins['io.containerd.grpc.v1.cri'] && + config.plugins['io.containerd.grpc.v1.cri'].registry && + config.plugins['io.containerd.grpc.v1.cri'].registry.mirrors && + config.plugins['io.containerd.grpc.v1.cri'].registry.mirrors[KIND_REGISTRY] && + config.plugins['io.containerd.grpc.v1.cri'].registry.mirrors[KIND_REGISTRY].endpoint && + config.plugins['io.containerd.grpc.v1.cri'].registry.mirrors[KIND_REGISTRY].endpoint.includes( + `http://${REGISTRY_NAME}:5000` + ) + ); +} diff --git a/src/main.ts b/src/main.ts index 50af511a..04e9d06f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,12 +2,15 @@ import * as core from '@actions/core'; import { installTools } from './installer'; import { KindMainService } from './kind/main'; import { setUpLoadBalancer } from './load-balancer'; +import { finishRegistrySetup, initRegistrySetup } from './local-registry'; import { checkEnvironment } from './requirements'; async function run() { const { kind, kubernetes } = await checkEnvironment(); await installTools(kind, kubernetes); - await KindMainService.getInstance().createCluster(); + const configFile = await initRegistrySetup(); + await KindMainService.getInstance().createCluster(configFile); + await finishRegistrySetup(); await setUpLoadBalancer(); } diff --git a/src/post-local-registry.ts b/src/post-local-registry.ts new file mode 100644 index 00000000..e360f6c9 --- /dev/null +++ b/src/post-local-registry.ts @@ -0,0 +1,13 @@ +import * as core from '@actions/core'; +import * as exec from '@actions/exec'; +import { Input } from './constants'; +import { REGISTRY_NAME } from './local-registry'; + +export async function removeRegistry() { + if (core.getInput(Input.LocalRegistry) === 'true') { + await core.group(`Delete ${REGISTRY_NAME}`, async () => { + const args = ['rm', '--force', REGISTRY_NAME]; + await exec.exec('docker', args); + }); + } +} diff --git a/src/post.ts b/src/post.ts index e1e2811f..d691ba6d 100644 --- a/src/post.ts +++ b/src/post.ts @@ -1,10 +1,12 @@ import * as core from '@actions/core'; import { KindPostService } from './kind/post'; +import { removeRegistry } from './post-local-registry'; async function run() { const service: KindPostService = KindPostService.getInstance(); await service.exportClusterLogs(); await service.deleteCluster(); + await removeRegistry(); } run().catch((error) => { diff --git a/src/requirements.ts b/src/requirements.ts index abfac0b3..f219f8c0 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -2,43 +2,29 @@ import * as core from '@actions/core'; import * as github from '@actions/github'; import * as io from '@actions/io'; import { ok } from 'assert'; -import fs from 'fs'; -import * as yaml from 'js-yaml'; import path from 'path'; import * as semver from 'semver'; import { Input, KIND_DEFAULT_VERSION } from './constants'; import { env as goenv } from './go'; +import { Cluster } from './kubernetes'; +import { hasRegistryConfig, KIND_REGISTRY, REGISTRY_NAME } from './local-registry'; +import { read } from './yaml-helper'; export async function checkEnvironment() { checkVariables(); await checkDocker(); const { platform, kind } = await checkPlatform(); const kubernetes = await getKubernetes(platform, kind.version); + checkKindConfig(); return { kind, kubernetes, }; } -interface Cluster { - kind?: string; - apiVersion?: string; - name?: string; - nodes?: Node[]; - kubeadmConfigPatches?: string[]; - containerdConfigPatches?: string[]; -} - -interface Node { - role?: string; - image?: string; - kubeadmConfigPatches?: string[]; -} - async function getKubernetes(platform: string, kind_version: string) { const image = core.getInput(Input.Image); - checkImageForPlatform(image, platform); - checkImageForVersion(image, kind_version, {}); + checkImageForVersion(image, kind_version); let version = parseKubernetesVersion(image); let url = ''; @@ -61,9 +47,7 @@ async function getKubernetes(platform: string, kind_version: string) { function parseKubernetesVersionFromConfig(kindConfig: string, kind_version: string) { let version = ''; - const kindConfigPath = path.join(`${process.env['GITHUB_WORKSPACE'] || ''}`, kindConfig); - const doc = yaml.load(fs.readFileSync(kindConfigPath, 'utf8')) as Cluster; - ok(doc.kind === 'Cluster', `The config file ${kindConfig} must be of kind Cluster`); + const doc = parseKindConfig(kindConfig); if (doc.nodes) { const versions: string[] = doc.nodes .map((node) => { @@ -87,6 +71,13 @@ function parseKubernetesVersionFromConfig(kindConfig: string, kind_version: stri return version; } +function parseKindConfig(kindConfig: string) { + const kindConfigPath = path.join(`${process.env['GITHUB_WORKSPACE'] || ''}`, kindConfig); + const doc = read(kindConfigPath) as Cluster; + ok(doc.kind === 'Cluster', `The config file ${kindConfig} must be of kind Cluster`); + return doc; +} + function parseKubernetesVersion(image: string) { let version = ''; if (image && image.startsWith('kindest/node')) { @@ -248,18 +239,6 @@ function checkVersion(version: string) { } } -/** - * An image is required for platforms outside of linux/amd64 and linux/arm64 as they are not packages with KinD by default - * @param image - * @param platform - */ -function checkImageForPlatform(image: string, platform: string) { - const platforms: string[] = ['linux/amd64', 'linux/arm64']; - if (!platforms.includes(platform)) { - ok(image, `Input ${Input.Image} is required for platform ${platform}`); - } -} - /** * Prints a warning if a kindest/node is used without sha256. * This follows the recommendation from https://kind.sigs.k8s.io/docs/user/working-offline/#using-a-prebuilt-node-imagenode-image @@ -267,7 +246,7 @@ function checkImageForPlatform(image: string, platform: string) { function checkImageForVersion( image: string, kind_version: string, - annotationProperties: core.AnnotationProperties + annotationProperties: core.AnnotationProperties = {} ) { if (image && image.startsWith('kindest/node') && !image.includes('@sha256:')) { core.warning( @@ -276,3 +255,24 @@ function checkImageForVersion( ); } } + +/** + * Prints a warning if the local registry configuration is missing in the kind config file + */ +function checkKindConfig() { + const kindConfigFile = core.getInput(Input.Config); + if (core.getInput(Input.LocalRegistry) === 'true' && kindConfigFile !== '') { + const kindConfig = parseKindConfig(kindConfigFile); + if ( + !kindConfig.containerdConfigPatches || + !kindConfig.containerdConfigPatches.find((value) => hasRegistryConfig(value)) + ) { + core.warning( + `Please provide the following configuration in containerdConfigPatches: + [plugins."io.containerd.grpc.v1.cri".registry.mirrors."${KIND_REGISTRY}"] + endpoint = ["https://${REGISTRY_NAME}:5000"]`, + { file: kindConfigFile } + ); + } + } +} diff --git a/src/yaml-helper.ts b/src/yaml-helper.ts new file mode 100644 index 00000000..75605e8c --- /dev/null +++ b/src/yaml-helper.ts @@ -0,0 +1,18 @@ +import fs from 'fs'; +import * as yaml from 'js-yaml'; +import path from 'path'; + +const UTF8_ENCODING = 'utf8'; + +export function write(dir: string, fileName: string, content: unknown) { + const file = path.join(dir, fileName); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(file, yaml.dump(content), UTF8_ENCODING); + return file; +} + +export function read(path: string) { + return yaml.load(fs.readFileSync(path, UTF8_ENCODING)); +} From 545ec57e6dce62b45ea51c3c2f6097623dbbc93c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 25 Feb 2022 14:16:24 +0100 Subject: [PATCH 20/24] retrieve addresses instead of the first parts of the IP --- src/load-balancer.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/load-balancer.ts b/src/load-balancer.ts index f4c40784..54bad2b0 100644 --- a/src/load-balancer.ts +++ b/src/load-balancer.ts @@ -48,7 +48,7 @@ async function waitForPods() { }); } -async function getIPBytes() { +async function getIPAddresses() { const args: string[] = [ 'network', 'inspect', @@ -58,15 +58,12 @@ async function getIPBytes() { ]; const { stdout } = await exec.getExecOutput('docker', args, { silent: true }); const bytes = stdout.replace(/'/g, '').split('.'); - return { - first: bytes[0], - second: bytes[1], - }; + return [`${bytes[0]}.${bytes[1]}.255.200-${bytes[0]}.${bytes[1]}.255.250`]; } export async function setupAddressPool() { await core.group('Setup address pool used by load-balancers', async () => { - const { first, second } = await getIPBytes(); + const addresses = await getIPAddresses(); const configMap: ConfigMap = { apiVersion: 'v1', kind: 'ConfigMap', @@ -80,7 +77,7 @@ export async function setupAddressPool() { { name: 'default', protocol: 'layer2', - addresses: [`${first}.${second}.255.200-${first}.${second}.255.250`], + addresses: addresses, }, ], }), From 530a40fd535a76055e9be18ae08418ecbf70056f Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 26 Feb 2022 13:48:00 +0100 Subject: [PATCH 21/24] simplify variables calculation --- src/cache.ts | 8 ++++---- src/kind/main.ts | 5 +---- src/kind/post.ts | 4 ++-- src/kubectl.ts | 2 +- src/local-registry.ts | 5 +---- src/requirements.ts | 2 +- 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index a7d406f1..2a4be04d 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -36,7 +36,7 @@ export async function restoreSetupKindCache(kind_version: string, kubernetes_ver * @returns the cache paths */ function setupKindCachePaths(kind_version: string, kubernetes_version: string) { - const RUNNER_TOOL_CACHE = `${process.env['RUNNER_TOOL_CACHE'] || ''}`; + const RUNNER_TOOL_CACHE = process.env['RUNNER_TOOL_CACHE'] || ''; const paths = [ path.join(RUNNER_TOOL_CACHE, KIND_TOOL_NAME, semver.clean(kind_version) || kind_version), ]; @@ -60,9 +60,9 @@ function setupKindCachePaths(kind_version: string, kubernetes_version: string) { * @returns the primary Key */ function setupKindPrimaryKey(kind_version: string, kubernetes_version: string) { - const SETUP_KIND_CACHE_KEY_PREFIX = `${process.env['RUNNER_OS'] || ''}-${ - process.env['RUNNER_ARCH'] || '' - }-setup-kind-`; + const RUNNER_OS = process.env['RUNNER_OS'] || ''; + const RUNNER_ARCH = process.env['RUNNER_ARCH'] || ''; + const SETUP_KIND_CACHE_KEY_PREFIX = `${RUNNER_OS}-${RUNNER_ARCH}-setup-kind-`; const key = JSON.stringify({ architecture: process.arch, kind: kind_version, diff --git a/src/kind/main.ts b/src/kind/main.ts index cf058f4d..fd57b816 100644 --- a/src/kind/main.ts +++ b/src/kind/main.ts @@ -39,10 +39,7 @@ export class KindMainService { args.push(Flag.Quiet); } if (this.configFile !== '') { - args.push( - Flag.Config, - path.join(`${process.env['GITHUB_WORKSPACE'] || ''}`, this.configFile) - ); + args.push(Flag.Config, path.join(process.env['GITHUB_WORKSPACE'] || '', this.configFile)); } else if (configFile !== '') { args.push(Flag.Config, configFile); } diff --git a/src/kind/post.ts b/src/kind/post.ts index 8894dde9..f35009ae 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -67,11 +67,11 @@ export class KindPostService { dirs.push(this.name); } dirs.push('logs'); - return path.join(`${process.env['RUNNER_TEMP'] || ''}`, uuidv5(dirs.join('/'), uuidv5.URL)); + return path.join(process.env['RUNNER_TEMP'] || '', uuidv5(dirs.join('/'), uuidv5.URL)); } private artifactName(): string { - const artifactArgs: string[] = [`${process.env['GITHUB_JOB'] || ''}`, KIND_TOOL_NAME]; + const artifactArgs: string[] = [process.env['GITHUB_JOB'] || '', KIND_TOOL_NAME]; if (this.name != '') { artifactArgs.push(this.name); } diff --git a/src/kubectl.ts b/src/kubectl.ts index f5b59f4b..0dcf6001 100644 --- a/src/kubectl.ts +++ b/src/kubectl.ts @@ -17,7 +17,7 @@ export async function apply(file: string) { export async function applyConfigMap(configMap: ConfigMap, fileName: string) { const dirs: string[] = [KUBECTL_TOOL_NAME, core.getInput(Input.Name)]; - const dir = path.join(`${process.env['RUNNER_TEMP'] || ''}`, uuidv5(dirs.join('/'), uuidv5.URL)); + const dir = path.join(process.env['RUNNER_TEMP'] || '', uuidv5(dirs.join('/'), uuidv5.URL)); const file = write(dir, fileName, configMap); await apply(file); } diff --git a/src/local-registry.ts b/src/local-registry.ts index a5dbac0e..866b2f2d 100644 --- a/src/local-registry.ts +++ b/src/local-registry.ts @@ -69,10 +69,7 @@ function createKindConfig() { ], }; const dirs: string[] = [KIND_TOOL_NAME, core.getInput(Input.Name)]; - const dir = path.join( - `${process.env['RUNNER_TEMP'] || ''}`, - uuidv5(dirs.join('/'), uuidv5.URL) - ); + const dir = path.join(process.env['RUNNER_TEMP'] || '', uuidv5(dirs.join('/'), uuidv5.URL)); return write(dir, 'kind-config.yaml', cluster); } return ''; diff --git a/src/requirements.ts b/src/requirements.ts index f219f8c0..d794a20b 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -212,7 +212,7 @@ function checkVariables() { 'RUNNER_TEMP', 'RUNNER_TOOL_CACHE', ].forEach((variable) => { - ok(`${process.env[variable] || ''}`, `Expected ${variable} to be defined`); + ok(process.env[variable] || '', `Expected ${variable} to be defined`); }); } From c232c02771dd5cebb6db6eff8a40edce345c530a Mon Sep 17 00:00:00 2001 From: MOREL Matthieu Date: Sun, 27 Feb 2022 11:01:27 +0100 Subject: [PATCH 22/24] mutualize docker executor --- __tests__/kind/post.test.ts | 6 ++++- __tests__/local-registry.test.ts | 37 +++++++++----------------- src/constants.ts | 2 ++ src/docker.ts | 10 +++++++ src/kind/post.ts | 14 +++++----- src/kubectl.ts | 2 +- src/load-balancer.ts | 4 +-- src/local-registry.ts | 12 ++++----- src/post-local-registry.ts | 4 +-- src/requirements.ts | 2 +- src/{yaml-helper.ts => yaml/helper.ts} | 0 11 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 src/docker.ts rename src/{yaml-helper.ts => yaml/helper.ts} (100%) diff --git a/__tests__/kind/post.test.ts b/__tests__/kind/post.test.ts index bd701c74..6ad21ca2 100644 --- a/__tests__/kind/post.test.ts +++ b/__tests__/kind/post.test.ts @@ -53,7 +53,11 @@ describe('checking input parsing', function () { }); it('correctly generates the cluster export logs command', () => { - const args: string[] = KindPostService.getInstance().exportLogsCommand(); + const logsDir = KindPostService.getInstance().kindLogsDir(); + expect(logsDir).toEqual( + path.normalize('/home/runner/work/_temp/1c1900ec-8f4f-5069-a966-1d3072cc9723') + ); + const args: string[] = KindPostService.getInstance().exportLogsCommand(logsDir); expect(args).toEqual([ 'export', 'logs', diff --git a/__tests__/local-registry.test.ts b/__tests__/local-registry.test.ts index a5ffeb84..56acc50a 100644 --- a/__tests__/local-registry.test.ts +++ b/__tests__/local-registry.test.ts @@ -1,34 +1,21 @@ -import { ConfigPatch } from '../src/containerd'; -import { hasRegistryConfig, parseConfigPatch } from '../src/local-registry'; +import { hasRegistryConfig } from '../src/local-registry'; -describe('checking go env simulation', function () { - it('correctly parse ConfigPatch', () => { - const configPatch = `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] - endpoint = ["http://kind-registry:5000"]`; - - const json: ConfigPatch = parseConfigPatch(configPatch); - - expect(json).not.toBeNull(); - expect(json.plugins).not.toBeNull(); - expect(json.plugins['io.containerd.grpc.v1.cri']).not.toBeNull(); - expect(json.plugins['io.containerd.grpc.v1.cri'].registry).not.toBeNull(); - expect(json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors).not.toBeNull(); - expect( - json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors['localhost:5000'] - ).not.toBeNull(); - expect( - json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors['localhost:5000'].endpoint - ).not.toBeNull(); - expect( - json.plugins['io.containerd.grpc.v1.cri'].registry.mirrors['localhost:5000'].endpoint - ).toContain('http://kind-registry:5000'); +describe('checking registry validation', function () { + it('disable_tcp_service configuration', () => { + const configPatch = `[plugins."io.containerd.grpc.v1.cri"] + disable_tcp_service = true`; + expect(hasRegistryConfig(configPatch)).toBeFalsy(); + }); + it('empty configuration', () => { + const configPatch = ``; + expect(hasRegistryConfig(configPatch)).toBeFalsy(); }); - it('misconfigured', () => { + it('wrong port', () => { const configPatch = `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5001"] endpoint = ["http://kind-registry:5000"]`; expect(hasRegistryConfig(configPatch)).toBeFalsy(); }); - it('right configured', () => { + it('correctly configured', () => { const configPatch = `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] endpoint = ["http://kind-registry:5000"]`; expect(hasRegistryConfig(configPatch)).toBeTruthy(); diff --git a/src/constants.ts b/src/constants.ts index 1ec76200..78b3c332 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,6 +30,8 @@ export enum Flag { export const IS_WINDOWS = process.platform === 'win32'; +export const DOCKER_COMMAND = IS_WINDOWS ? 'docker.exe' : 'docker'; + export const KIND_COMMAND = IS_WINDOWS ? 'kind.exe' : 'kind'; export const KIND_DEFAULT_VERSION = 'v0.11.1'; export const KIND_TOOL_NAME = 'kind'; diff --git a/src/docker.ts b/src/docker.ts new file mode 100644 index 00000000..88ab5d87 --- /dev/null +++ b/src/docker.ts @@ -0,0 +1,10 @@ +import * as exec from '@actions/exec'; +import { DOCKER_COMMAND } from './constants'; + +export async function executeDocker(args: string[], options?: exec.ExecOptions) { + return await exec.exec(DOCKER_COMMAND, args, options); +} + +export async function getDockerExecutionOutput(args: string[], options?: exec.ExecOptions) { + return await exec.getExecOutput(DOCKER_COMMAND, args, options); +} diff --git a/src/kind/post.ts b/src/kind/post.ts index f35009ae..42b476fb 100644 --- a/src/kind/post.ts +++ b/src/kind/post.ts @@ -47,8 +47,8 @@ export class KindPostService { } // returns the arguments to pass to `kind export logs` - exportLogsCommand(): string[] { - const args: string[] = ['export', 'logs', this.kindLogsDir()]; + exportLogsCommand(logsDir: string): string[] { + const args: string[] = ['export', 'logs', logsDir]; if (this.verbosity > 0) { args.push(Flag.Verbosity, this.verbosity.toString()); } @@ -61,7 +61,7 @@ export class KindPostService { return args; } - private kindLogsDir(): string { + kindLogsDir(): string { const dirs: string[] = [KIND_TOOL_NAME]; if (this.name != '') { dirs.push(this.name); @@ -79,9 +79,8 @@ export class KindPostService { return artifactArgs.join('-'); } - async uploadKindLogs() { + async uploadKindLogs(rootDirectory: string) { const artifactClient = artifact.create(); - const rootDirectory = this.kindLogsDir(); const pattern = rootDirectory + '/**/*'; const globber = await glob.create(pattern); const files = await globber.glob(); @@ -105,8 +104,9 @@ export class KindPostService { return; } await core.group(`Export logs for cluster "${this.name}"`, async () => { - await executeKind(this.exportLogsCommand()); - await this.uploadKindLogs(); + const logsDir = this.kindLogsDir(); + await executeKind(this.exportLogsCommand(logsDir)); + await this.uploadKindLogs(logsDir); }); } } diff --git a/src/kubectl.ts b/src/kubectl.ts index 0dcf6001..31729869 100644 --- a/src/kubectl.ts +++ b/src/kubectl.ts @@ -4,7 +4,7 @@ import path from 'path'; import { v5 as uuidv5 } from 'uuid'; import { Input, KUBECTL_COMMAND, KUBECTL_TOOL_NAME } from './constants'; import { ConfigMap } from './kubernetes'; -import { write } from './yaml-helper'; +import { write } from './yaml/helper'; export async function executeKubectl(args: string[]) { await exec.exec(KUBECTL_COMMAND, args); diff --git a/src/load-balancer.ts b/src/load-balancer.ts index 54bad2b0..92d0901d 100644 --- a/src/load-balancer.ts +++ b/src/load-balancer.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core'; -import * as exec from '@actions/exec'; import * as yaml from 'js-yaml'; import { Input } from './constants'; +import { getDockerExecutionOutput } from './docker'; import * as kubectl from './kubectl'; import { ConfigMap } from './kubernetes'; @@ -56,7 +56,7 @@ async function getIPAddresses() { "'{{(index .IPAM.Config 0).Subnet}}'", 'kind', ]; - const { stdout } = await exec.getExecOutput('docker', args, { silent: true }); + const { stdout } = await getDockerExecutionOutput(args, { silent: true }); const bytes = stdout.replace(/'/g, '').split('.'); return [`${bytes[0]}.${bytes[1]}.255.200-${bytes[0]}.${bytes[1]}.255.250`]; } diff --git a/src/local-registry.ts b/src/local-registry.ts index 866b2f2d..8b02460b 100644 --- a/src/local-registry.ts +++ b/src/local-registry.ts @@ -1,14 +1,14 @@ import * as core from '@actions/core'; -import * as exec from '@actions/exec'; import * as TOML from '@iarna/toml'; import * as yaml from 'js-yaml'; import path from 'path'; import { v5 as uuidv5 } from 'uuid'; import { Input, KIND_TOOL_NAME } from './constants'; import { ConfigPatch } from './containerd'; +import { executeDocker } from './docker'; import * as kubectl from './kubectl'; import { Cluster, ConfigMap } from './kubernetes'; -import { write } from './yaml-helper'; +import { write } from './yaml/helper'; export const REGISTRY_NAME = 'kind-registry'; export const REGISTRY_HOST = 'localhost'; @@ -34,7 +34,7 @@ async function createRegistryUnlessAlreadyExists() { async function connectRegistryToClusterNetwork() { await core.group(`Connect ${REGISTRY_NAME} to the kind network`, async () => { const args = ['network', 'connect', 'kind', REGISTRY_NAME]; - await exec.exec('docker', args); + await executeDocker(args); }); } @@ -53,7 +53,7 @@ async function createRegistry() { REGISTRY_NAME, REGISTRY_IMAGE, ]; - await exec.exec('docker', args); + await executeDocker(args); } ); } @@ -77,7 +77,7 @@ function createKindConfig() { async function registryAlreadyExists() { const args = ['inspect', '-f', "'{{.State.Running}}'", REGISTRY_NAME]; - const exitCode = await exec.exec('docker', args, { + const exitCode = await executeDocker(args, { ignoreReturnCode: true, silent: true, }); @@ -117,7 +117,7 @@ async function documentRegistry() { }); } -export function parseConfigPatch(configPatch: string) { +function parseConfigPatch(configPatch: string) { return JSON.parse(JSON.stringify(TOML.parse(configPatch))) as ConfigPatch; } diff --git a/src/post-local-registry.ts b/src/post-local-registry.ts index e360f6c9..d27225eb 100644 --- a/src/post-local-registry.ts +++ b/src/post-local-registry.ts @@ -1,13 +1,13 @@ import * as core from '@actions/core'; -import * as exec from '@actions/exec'; import { Input } from './constants'; +import { executeDocker } from './docker'; import { REGISTRY_NAME } from './local-registry'; export async function removeRegistry() { if (core.getInput(Input.LocalRegistry) === 'true') { await core.group(`Delete ${REGISTRY_NAME}`, async () => { const args = ['rm', '--force', REGISTRY_NAME]; - await exec.exec('docker', args); + await executeDocker(args); }); } } diff --git a/src/requirements.ts b/src/requirements.ts index d794a20b..4ae4879d 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -8,7 +8,7 @@ import { Input, KIND_DEFAULT_VERSION } from './constants'; import { env as goenv } from './go'; import { Cluster } from './kubernetes'; import { hasRegistryConfig, KIND_REGISTRY, REGISTRY_NAME } from './local-registry'; -import { read } from './yaml-helper'; +import { read } from './yaml/helper'; export async function checkEnvironment() { checkVariables(); diff --git a/src/yaml-helper.ts b/src/yaml/helper.ts similarity index 100% rename from src/yaml-helper.ts rename to src/yaml/helper.ts From 1c43ff0ebebd92bd12519f591967f9ef64bce56c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sun, 27 Feb 2022 12:43:32 +0100 Subject: [PATCH 23/24] executable command function --- src/constants.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 78b3c332..71c4ae28 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,11 +30,15 @@ export enum Flag { export const IS_WINDOWS = process.platform === 'win32'; -export const DOCKER_COMMAND = IS_WINDOWS ? 'docker.exe' : 'docker'; +function executableCommand(command: string) { + return IS_WINDOWS ? `${command}.exe` : command; +} + +export const DOCKER_COMMAND = executableCommand('docker'); -export const KIND_COMMAND = IS_WINDOWS ? 'kind.exe' : 'kind'; +export const KIND_COMMAND = executableCommand('kind'); export const KIND_DEFAULT_VERSION = 'v0.11.1'; export const KIND_TOOL_NAME = 'kind'; -export const KUBECTL_COMMAND = IS_WINDOWS ? 'kubectl.exe' : 'kubectl'; +export const KUBECTL_COMMAND = executableCommand('kubectl'); export const KUBECTL_TOOL_NAME = 'kubectl'; From 5c5bbdf31c21e994a42dfdd319a3e535a4dc5c3e Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 28 Feb 2022 22:14:23 +0100 Subject: [PATCH 24/24] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 83ac6f04..7289d034 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,9 @@ It is then available on localhost:5000 as KIND_REGISTRY on the host machine ## Self-hosted agents -When using on a self-hosted agent, an access to GITHUB_API_URL ( by default) and are required for setup-kind to work properly. +When using on a self-hosted agent, an access to GITHUB_API_URL ( by default) and are required for setup-kind to work properly. + +NB: The load-balancer needs an access to [kind-kubeconfig]: https://github.com/kubernetes-sigs/kind/issues/1060 [gh-actions-path]: