diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 462c5c94a..c38aa29da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,14 @@ on: branches: - 'master' pull_request: - paths-ignore: - - '.github/workflows/release.yml' - - '.github/workflows/pr_label.yml' + paths: + - '*gradle*' + - '.github/workflows/test.yml' + - '.groovylintrc.json' + - '.pre-commit-config.yaml' + - 'jobs/**' + - 'src/**' + - 'tests/**' jobs: linting: @@ -20,10 +25,16 @@ jobs: - name: Set up Python uses: actions/setup-python@v2.2.2 + - name: Python Cache + uses: actions/cache@v2 + with: + path: ${{ env.pythonLocation }} + key: ${{ env.pythonLocation }}-${{ hashFiles('tests/requirements.txt') }} + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r tests/requirements.txt + pip install --upgrade --upgrade-strategy eager -r tests/requirements.txt - name: Run pre-commit run: | @@ -40,10 +51,16 @@ jobs: - name: Set up Python uses: actions/setup-python@v2.2.2 + - name: Python Cache + uses: actions/cache@v2 + with: + path: ${{ env.pythonLocation }} + key: ${{ env.pythonLocation }}-${{ hashFiles('tests/requirements.txt') }} + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r tests/requirements.txt + pip install --upgrade --upgrade-strategy eager -r tests/requirements.txt - name: Test with pytest run: pytest diff --git a/.groovylintrc.json b/.groovylintrc.json index 997d0a60d..282b1e858 100644 --- a/.groovylintrc.json +++ b/.groovylintrc.json @@ -1,31 +1,13 @@ { - "extends": "all", + "extends": "recommended", "rules": { - "BlockEndsWithBlankLine": { - "enabled": false - }, - "BlockStartsWithBlankLine": { - "enabled": false - }, "CompileStatic": { "enabled": false }, - "ImplicitClosureParameter": { - "enabled": false - }, - "LineLength": { - "enabled": false - }, "SerializableClassMustDefineSerialVersionUID": { "enabled": false }, - "SpaceAroundMapEntryColon": { - "enabled": false - }, - "UnnecessaryGetter": { - "enabled": false - }, - "UnnecessaryReturnKeyword": { + "UnnecessaryParenthesesForMethodCallWithClosure": { "enabled": false } } diff --git a/jobs/bash/bash_example.groovy b/jobs/bash/bash_example.groovy new file mode 100644 index 000000000..68c70f2a5 --- /dev/null +++ b/jobs/bash/bash_example.groovy @@ -0,0 +1,123 @@ +/* groovylint-disable DuplicateNumberLiteral, DuplicateStringLiteral */ +@Library('pipeline-library') +import org.dsty.bash.BashClient +import org.dsty.bash.ScriptError +import org.dsty.bash.Result + +node() { + String msg + Result result + BashClient bash = new BashClient(this) + + stage('Regular Bash') { + msg = 'Hello from Bash!' + + // bash() prints the output to the console like sh, + // but it also returns the stdOut, stdError, and exitCode. + // It also returns output which is stdOut and stdError combined + // but the order is not guranteed to be perfect. + result = bash("echo '${msg}'") + + if (result.stdOut != msg ) { + error('Did not contain correct output.') + } + + if ( result.stdErr ) { + error('Should not have output anything.') + } + + if ( result.exitCode != 0 ) { + error('Exited with wrong code.') + } + + // In the event of a non 0 exitCode an Exception + // is thrown. The exception will behave just like a normal Result. + // So it will also have stdOut, stdError, output and exitCode. + ScriptError exception = null + + try { + bash('RegularFakeCommand') + } catch (ScriptError e) { + exception = e + } + + if ( !exception.stdErr.contains('RegularFakeCommand: command not found') ) { + error('Command was found.') + } + + if (exception.stdOut) { + error('Should not have stdOut.') + } + + if ( exception.exitCode != 127) { + error('Exited with wrong code.') + } + } + + stage('Silent Bash') { + msg = 'NotShown!' + + result = bash.silent("echo '${msg}'") + + if (result.stdOut != msg ) { + error('Did not contain correct output.') + } + + if ( result.stdErr ) { + error('Should not have output anything.') + } + + if ( result.exitCode != 0 ) { + error('Exited with wrong code.') + } + } + + stage('Ignore Errors') { + // ignoreErrors will not throw an exception. + // Instead is just returns the results after + // it encounters an error. + result = bash.ignoreErrors('fakecommand', true) + + if ( !result.stdErr.contains('fakecommand: command not found') ) { + error('Command was found.') + } + + if (result.stdOut) { + error('Should not have stdOut.') + } + + if ( result.exitCode != 127) { + error('Exited with wrong code.') + } + + // By default ignoreErrors will ignore all errors and run + // the entire script before returning. + + String script = '''\ + fakeCommand + anotherFakeCommand + ''' + + // The false we are passing determines if the command output is to be + // displayed in the build console. + result = bash.ignoreErrors(script, false) + + if ( !result.stdErr.contains('anotherFakeCommand') ) { + error('Should not stop on first error.') + } + + // If you want to return on the first error, + // set failfast to true. + result = bash.ignoreErrors(script, false, true) + + if ( result.stdErr.contains('anotherFakeCommand') ) { + error('Should stop on first error.') + } + + result = bash.ignoreErrors('exit 55', false) + + if ( result.exitCode != 55 ) { + error('Should capture the correct exitCode.') + } + } +} diff --git a/jobs/bash/call b/jobs/bash/call deleted file mode 100644 index 45da97222..000000000 --- a/jobs/bash/call +++ /dev/null @@ -1,45 +0,0 @@ -@Library('pipeline-library') -import org.dsty.bash.BashClient -import org.dsty.bash.ScriptError - -node() { - - String msg = 'TestMessage' - - def bash = new BashClient(this) - - def result = bash("echo '${msg}'") - - - if (result.stdOut != msg ) { - error('Did not contain correct output.') - } - - if ( result.stdErr ) { - error('Should not have output anything.') - } - - if ( result.exitCode != 0 ) { - error('Exited with wrong code.') - } - - def exception = null - try { - bash('fakecommand') - } catch (ScriptError e) { - exception = e - } - - if ( !exception.stdErr.contains('fakecommand: command not found') ) { - error('Command was found.') - } - - if (exception.stdOut) { - error('Should not have stdOut.') - } - - if ( exception.exitCode != 127) { - error('Exited with wrong code.') - } - -} diff --git a/jobs/bash/ignore_errors b/jobs/bash/ignore_errors deleted file mode 100644 index dda001049..000000000 --- a/jobs/bash/ignore_errors +++ /dev/null @@ -1,46 +0,0 @@ -@Library('pipeline-library') -import org.dsty.bash.BashClient - -node() { - - def bash = new BashClient(this) - - String cmd = 'fakecommand' - - result = bash.ignoreErrors(cmd, true) - - if ( !result.stdErr.contains("${cmd}: command not found") ) { - error('Command was found.') - } - - if (result.stdOut) { - error('Should not have stdOut.') - } - - if ( result.exitCode != 127) { - error('Exited with wrong code.') - } - - cmd = 'secretcommand\nanothercommand' - - def result = bash.ignoreErrors(cmd, false) - - if ( !result.stdErr.contains("anothercommand") ) { - error('Should not stop on first error.') - } - - result = bash.ignoreErrors(cmd, false, true) - - if ( result.stdErr.contains('anothercommand') ) { - error('Should stop on first error.') - } - - cmd = 'exit 55' - - result = bash.ignoreErrors(cmd, false) - - if ( result.exitCode != 55 ) { - error('Should capture the correct exitCode.') - } - -} diff --git a/jobs/bash/silent b/jobs/bash/silent deleted file mode 100644 index 09d7ff5a6..000000000 --- a/jobs/bash/silent +++ /dev/null @@ -1,45 +0,0 @@ -@Library('pipeline-library') -import org.dsty.bash.BashClient -import org.dsty.bash.ScriptError - -node() { - - String msg = 'TestMessage' - - def bash = new BashClient(this) - - def result = bash.silent("echo '${msg}'") - - - if (result.stdOut != msg ) { - error('Did not contain correct output.') - } - - if ( result.stdErr ) { - error('Should not have output anything.') - } - - if ( result.exitCode != 0 ) { - error('Exited with wrong code.') - } - - def exception = null - try { - bash.silent('fakecommand') - } catch (ScriptError e) { - exception = e - } - - if ( !exception.stdErr.contains('fakecommand: command not found') ) { - error('Command was found.') - } - - if (exception.stdOut) { - error('Should not have stdOut.') - } - - if ( exception.exitCode != 127) { - error('Exited with wrong code.') - } - -} diff --git a/jobs/github/actions/step_example.groovy b/jobs/github/actions/step_example.groovy index 3ac022b8e..335a08db4 100644 --- a/jobs/github/actions/step_example.groovy +++ b/jobs/github/actions/step_example.groovy @@ -1,10 +1,9 @@ -/* groovylint-disable DuplicateMapLiteral, DuplicateStringLiteral, UnnecessaryParenthesesForMethodCallWithClosure, UnusedVariable */ +/* groovylint-disable, DuplicateMapLiteral, DuplicateStringLiteral, UnusedVariable */ @Library('pipeline-library') import org.dsty.github.actions.Step node() { - // This is needed if you run jenkins in a docker container. // It's the path on the host machine where your docker bind mount is stored. // docker run -v '/tmp/jenkins_home:/var/run/jenkins_home' jenkins/jenkins:lts @@ -19,7 +18,6 @@ node() { Map outputs stage('Docker Action') { - options = [ 'name': 'Test Docker Action', 'uses': 'actions/hello-world-docker-action@master', @@ -33,11 +31,9 @@ node() { if (!outputs.time) { error('Should have an output named time.') } - } stage('JavaScript Action') { - options = [ 'name': 'Test JavaScript Action', 'uses': 'actions/hello-world-javascript-action@master', @@ -51,11 +47,9 @@ node() { if (!outputs.time) { error('Should have an output named time.') } - } stage('Run Action') { - options = [ 'name': 'Test RunAction.', 'run': '''\ @@ -66,14 +60,13 @@ node() { outputs = action(options) - if (!outputs.test) { + if (!outputs.test) { error('Should have an output named test.') } if (outputs.test != 'SomeValue') { error('Should set the correct output Value.') } - } cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) diff --git a/jobs/github/actions/tests/test_step.groovy b/jobs/github/actions/tests/test_step.groovy index c95d70022..92cc5d300 100644 --- a/jobs/github/actions/tests/test_step.groovy +++ b/jobs/github/actions/tests/test_step.groovy @@ -1,10 +1,9 @@ -/* groovylint-disable DuplicateMapLiteral, DuplicateStringLiteral, Println, UnnecessaryParenthesesForMethodCallWithClosure, UnusedVariable */ +/* groovylint-disable, DuplicateMapLiteral, DuplicateStringLiteral, UnusedVariable */ @Library('pipeline-library') import org.dsty.github.actions.Step node() { - String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) Step action = new Step(this) @@ -14,10 +13,10 @@ node() { ] try { - Map outputs = action(options) - error("Should not run if 'uses' or 'run' not provided.") + Map outputs = action(options) + error("Should not run if 'uses' or 'run' not provided.") } catch (IllegalArgumentException ex) { - println('Threw the correct exception.') + println('Threw the correct exception.') } options = [ @@ -32,7 +31,7 @@ node() { Map outputs = action(options) if (outputs) { - error('Should haved skipped running the step.') + error('Should haved skipped running the step.') } cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) diff --git a/jobs/github/actions/tests/test_workflow.groovy b/jobs/github/actions/tests/test_workflow.groovy index 93915f814..5cef20b05 100644 --- a/jobs/github/actions/tests/test_workflow.groovy +++ b/jobs/github/actions/tests/test_workflow.groovy @@ -1,49 +1,41 @@ -/* groovylint-disable DuplicateMapLiteral, DuplicateStringLiteral, UnnecessaryParenthesesForMethodCallWithClosure, UnusedVariable */ +/* groovylint-disable, DuplicateMapLiteral, DuplicateStringLiteral, UnusedVariable */ @Library('pipeline-library') import org.dsty.github.actions.Workflow node() { + String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) - String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) + Workflow workflow = new Workflow(this) - Workflow workflow = new Workflow(this) + // Should install the latest + workflow.install() - // Should install the latest - workflow.install() + String latestVersion = workflow.version - String latestVersion = workflow.version + // Set an older version to install + workflow.version = 'v0.2.21' + workflow.install() - // Set an older version to install - workflow.version = 'v0.2.21' - workflow.install() + String olderVersion = workflow.version - String olderVersion = workflow.version + if (olderVersion == latestVersion) { + error('Should have override the installed version.') + } - if (olderVersion == latestVersion) { + // Should use existing version + workflow.version = '' + workflow.install() - error('Should have override the installed version.') + if (olderVersion != workflow.version) { + error('Should not have changed version.') + } - } + String output = workflow.run('--version') - // Should use existing version - workflow.version = '' - workflow.install() - - if (olderVersion != workflow.version) { - - error('Should not have changed version.') - - } - - String output = workflow.run('--version') - - if (!output.contains(workflow.version.replace('v', ''))) { - - error('Should have ran the version command.') - - } - - cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) + if (!output.contains(workflow.version.replace('v', ''))) { + error('Should have ran the version command.') + } + cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) } diff --git a/jobs/github/actions/workflow_example.groovy b/jobs/github/actions/workflow_example.groovy index 66260f72e..853567073 100644 --- a/jobs/github/actions/workflow_example.groovy +++ b/jobs/github/actions/workflow_example.groovy @@ -1,16 +1,14 @@ -/* groovylint-disable DuplicateMapLiteral, DuplicateStringLiteral, UnnecessaryParenthesesForMethodCallWithClosure, UnusedVariable */ +/* groovylint-disable, DuplicateMapLiteral, DuplicateStringLiteral, UnusedVariable */ @Library('pipeline-library') import org.dsty.github.actions.Workflow node() { + // Ignore this line its for catching CPS issues. + String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) - // Ignore this line its for catching CPS issues. - String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) - - stage('Checkout code') { - - checkout( + stage('Checkout code') { + checkout( changelog: false, poll: false, scm: [ @@ -20,18 +18,14 @@ node() { userRemoteConfigs: [[url: 'https://github.com/cplee/github-actions-demo.git']] ] ) + } - } - - stage('Run Workflow') { - - Workflow workflow = new Workflow(this) - - String output = workflow() - - } + stage('Run Workflow') { + Workflow workflow = new Workflow(this) - // Ignore this line its for catching CPS issues. - cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) + String output = workflow() + } + // Ignore this line its for catching CPS issues. + cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) } diff --git a/jobs/http/requests b/jobs/http/requests deleted file mode 100644 index 345d88dca..000000000 --- a/jobs/http/requests +++ /dev/null @@ -1,133 +0,0 @@ -@Library('pipeline-library') - -import org.dsty.http.Requests -import org.dsty.http.Response - -node() { - - String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) - - Requests request = new Requests() - - Response response - - String url - - Map headers - - Map auth - - url = 'https://httpbin.org/get' - - Map params = [ - 'Param1': [ - 'Value1', - 'Value2' - ], - 'Param2': 'Value 3' - ] - - response = Requests.get(url, params: params) - - if (!response.isOkay()) { - println(response) - error('Should have made a proper HTTP GET request.') - } - - if (response.json.args != params) { - error('Should have set the correct parameters.') - } - - String hello = 'hello world' - - url = "https://httpbin.org/get?key1=${hello}" - - response = Requests.get(url) - - if (!response.isOkay()) { - println(response) - error('Should have made a proper HTTP GET request.') - } - - if (response.json.args['key1'] != hello) { - println(response) - error('Should have set the correct parameters.') - } - - String headerName = 'Example' - String headerValue = 'Value' - - url = 'https://httpbin.org/headers' - - response = request.get(url, headers: [(headerName): headerValue]) - - if (response.json.headers[headerName] != headerValue) { - println(response) - error('Should have set headers correctly.') - } - - headers = [ - 'content-type': 'application/json', - ] - - Map json = [ - 'TestKey': 'TestValue' - ] - - response = request.post('https://httpbin.org/post', headers: headers, json: json) - - if (response.json['json'] != json) { - println(response) - error('Should have posted json.') - } - - auth = [ - 'type': 'Basic', - 'username': 'Test', - 'password': 'secret', - ] - - url = 'https://httpbin.org/basic-auth/Test/secret' - - response = request.get(url, auth: auth) - - if (!response.json.authenticated) { - println(response) - error('Should have autheticated.') - } - - auth = [ - 'type': 'Bearer', - 'token': 'Test', - ] - - url = 'https://httpbin.org/bearer' - - response = request.get(url, auth: auth) - - if (!response.json.authenticated) { - println(response) - error('Should have autheticated.') - } - - url = 'https://httpbin.org/delete' - - response = request.delete(url, auth: auth) - - if (!response.isOkay()) { - println(response) - error('Should have made a proper HTTP DELETE request.') - } - - url = 'https://httpbin.org/put' - - response = request.put(url, auth: auth) - - if (!response.isOkay()) { - println(response) - error('Should have made a proper HTTP PUT request.') - } - - cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) - -} diff --git a/jobs/http/requests_example.groovy b/jobs/http/requests_example.groovy new file mode 100644 index 000000000..d52dfe484 --- /dev/null +++ b/jobs/http/requests_example.groovy @@ -0,0 +1,149 @@ +/* groovylint-disable DuplicateMapLiteral, DuplicateStringLiteral, UnnecessaryGetter, UnusedVariable */ +@Library('pipeline-library') + +import org.dsty.http.Requests +import org.dsty.http.Response + +node() { + String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) + + Requests request = new Requests() + + Response response + + String url + + Map headers + + Map auth + + stage('Get Request') { + url = 'https://httpbin.org/get' + + // These parameters will automaticly be added to the url + // https://httpbin.org/get?Param1=Value1&Param1=Value2&Param2=Value%203 + Map params = [ + 'Param1': [ + 'Value1', + 'Value2' + ], + 'Param2': 'Value 3' + ] + + response = Requests.get(url, params: params) + + if (!response.isOkay()) { + println(response) + error('Should have made a proper HTTP GET request.') + } + + if (response.json.args != params) { + error('Should have set the correct parameters.') + } + + String hello = 'hello world' + + // You can still pass parameters manually as well + url = "https://httpbin.org/get?key1=${hello}" + + response = Requests.get(url) + + if (!response.isOkay()) { + println(response) + error('Should have made a proper HTTP GET request.') + } + + if (response.json.args['key1'] != hello) { + println(response) + error('Should have set the correct parameters.') + } + } + + stage('Pass headers') { + String headerName = 'Example' + String headerValue = 'Value' + + url = 'https://httpbin.org/headers' + + response = request.get(url, headers: [(headerName): headerValue]) + + if (response.json.headers[headerName] != headerValue) { + println(response) + error('Should have set headers correctly.') + } + } + + stage('Use JSON') { + headers = [ + 'content-type': 'application/json', + ] + + Map json = [ + 'TestKey': 'TestValue' + ] + + response = request.post('https://httpbin.org/post', headers: headers, json: json) + + if (response.json['json'] != json) { + println(response) + error('Should have posted json.') + } + } + + stage('Basic Auth') { + auth = [ + 'type': 'Basic', + 'username': 'Test', + 'password': 'secret', + ] + + url = 'https://httpbin.org/basic-auth/Test/secret' + + response = request.get(url, auth: auth) + + if (!response.json.authenticated) { + println(response) + error('Should have autheticated.') + } + } + + stage('Bearer Auth') { + auth = [ + 'type': 'Bearer', + 'token': 'Test', + ] + + url = 'https://httpbin.org/bearer' + + response = request.get(url, auth: auth) + + if (!response.json.authenticated) { + println(response) + error('Should have autheticated.') + } + + // Random delete request + + url = 'https://httpbin.org/delete' + + response = request.delete(url, auth: auth) + + if (!response.isOkay()) { + println(response) + error('Should have made a proper HTTP DELETE request.') + } + + // Random put request + + url = 'https://httpbin.org/put' + + response = request.put(url, auth: auth) + + if (!response.isOkay()) { + println(response) + error('Should have made a proper HTTP PUT request.') + } + } + + cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) +} diff --git a/jobs/jenkins/instance_example.groovy b/jobs/jenkins/instance_example.groovy new file mode 100644 index 000000000..ee78f77af --- /dev/null +++ b/jobs/jenkins/instance_example.groovy @@ -0,0 +1,29 @@ +/* groovylint-disable DuplicateMapLiteral, DuplicateStringLiteral, UnusedVariable */ +@Library('pipeline-library') + +import org.dsty.jenkins.Instance + +node() { + // CPS test can be ignored, it is not part of the example code. + String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) + + Instance jenkins = new Instance() + + stage('Get Installed Plugins') { + List plugins = jenkins.plugins() + + if (!plugins) { + error('Should have found plugins') + } + + if (jenkins.pluginInstalled('fakePlugin')) { + error('Should not find plugin.') + } + + if (!jenkins.pluginInstalled('workflow-job')) { + error('Should have plugin installed.') + } + } + + cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) +} diff --git a/jobs/jenkins/plugins b/jobs/jenkins/plugins deleted file mode 100644 index e13e8a266..000000000 --- a/jobs/jenkins/plugins +++ /dev/null @@ -1,27 +0,0 @@ -@Library('pipeline-library') - -import org.dsty.jenkins.Instance - -node() { - - sh('echo Test for CPS issue') - - Instance jenkins = new Instance() - - List plugins = jenkins.plugins() - - if (!plugins) { - error('Should have found plugins') - } - - if (jenkins.pluginInstalled('fakePlugin')) { - error('Should not find plugin.') - } - - if (!jenkins.pluginInstalled('workflow-job')) { - error('Should have plugin installed.') - } - - sh('echo Test for CPS issue') - -} diff --git a/jobs/logging/levels b/jobs/logging/logging_example.groovy similarity index 93% rename from jobs/logging/levels rename to jobs/logging/logging_example.groovy index 3d855b19a..85dc29df8 100644 --- a/jobs/logging/levels +++ b/jobs/logging/logging_example.groovy @@ -1,9 +1,9 @@ +/* groovylint-disable DuplicateMapLiteral, DuplicateStringLiteral, UnusedVariable */ @Library('pipeline-library') import org.dsty.logging.LogClient node() { - String cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) LogClient log = new LogClient(this) @@ -56,5 +56,4 @@ node() { log.error(level) cps = sh(script: '#!/bin/bash\nset +x; > /dev/null 2>&1\necho Test for CPS issue', returnStdout: true) - } diff --git a/src/org/dsty/bash/BashClient.groovy b/src/org/dsty/bash/BashClient.groovy index 2446764c5..c840b1481 100644 --- a/src/org/dsty/bash/BashClient.groovy +++ b/src/org/dsty/bash/BashClient.groovy @@ -7,133 +7,135 @@ import org.dsty.logging.LogClient */ class BashClient implements Serializable { - /** - * Workflow script representing the jenkins build. - */ - Object steps - - /** - * Logging client - */ - LogClient log - - /** - * Default Constructor - * @param steps The workflow script representing the jenkins build. - */ - BashClient(Object steps) { - this.steps = steps - this.log = new LogClient(steps) - } - - /** - * Formats a bash script by adding the shebang, - * setting the verbose level and sourcing bashrc. - * @param userScript The bash script you want to run. - * @param consoleOutput If you want the script results - * to be printed out to console. - * @param failFast If you want the script to stop on first - * non zero exit code. - * @return The userScript formatted for bash. - */ - String formatScript(String userScript, Boolean consoleOutput=true, Boolean failFast=true) { - String teeOutput = 'exec 2> >(tee -a stderr stdall) 1> >(tee -a stdout stdall)' - String exec = consoleOutput ? teeOutput : 'exec 3>/dev/null 2> >(tee -a stderr stdall >&3) 1> >(tee -a stdout stdall >&3)' - - String script = """\ - #!/bin/bash - source \$HOME/.bashrc > /dev/null 2>&1 || true - { ${failFast ? 'set -e;' : 'set +e;'} } > /dev/null 2>&1 - ${exec} - { ${this.steps.env.PIPELINE_LOG_LEVEL == 'DEBUG' ? 'set -x;' : 'set +x;' } } > /dev/null 2>&1 - - # User Script - """.stripIndent() - - userScript = userScript.stripIndent() - script = "${script}${userScript}" - this.log.debug("Formatted script:\n${script}") - - return script - } - - /** - * Runs a bash script that sends all output to the console - * and returns a result object with the stdOut, stdErr - * exitCode and the combined output of the script. - * @param userScript The bash command or script you want to run. - * @return The results of the bash command or script. - */ - Result call(String userScript) { - String script = formatScript(userScript) - - return execute(script) - } - - /** - * Runs a bash script that sends no output to the console - * and returns a result object with the stdOut, stdErr - * exitCode and the combined output of the script. - * @param userScript The bash command or script you want to run. - * @return The results of the bash command or script. - */ - Result silent(String userScript) { - String script = formatScript(userScript, false) - - return execute(script) - } - - /** - * Runs a bash script that ignores errors. - * @param userScript The bash command or script you want to run. - * @param consoleOutput Determines if output is sent to the console. - * @param failFast Determines if script should stop on first error or not. - * @return The results of the bash command or script. - */ - Result ignoreErrors(String userScript, Boolean consoleOutput=true, Boolean failFast=false) { - String script = formatScript(userScript, consoleOutput, failFast) - Result result - - try { - result = execute(script) - } catch (ScriptError ex) { - result = new Result(ex.stdOut, ex.stdErr, ex.output, ex.exitCode) + /** + * Workflow script representing the jenkins build. + */ + Object steps + + /** + * Logging client + */ + LogClient log + + /** + * Default Constructor + * @param steps The workflow script representing the jenkins build. + */ + BashClient(Object steps) { + this.steps = steps + this.log = new LogClient(steps) } - return result - } + /** + * Formats a bash script by adding the shebang, + * setting the verbose level and sourcing bashrc. + * @param userScript The bash script you want to run. + * @param consoleOutput If you want the script results + * to be printed out to console. + * @param failFast If you want the script to stop on first + * non zero exit code. + * @return The userScript formatted for bash. + */ + String formatScript(String userScript, Boolean consoleOutput=true, Boolean failFast=true) { + String showOutput = 'exec 2> >(tee -a stderr stdall) 1> >(tee -a stdout stdall)' + String hideOutput = 'exec 3>/dev/null 2> >(tee -a stderr stdall >&3) 1> >(tee -a stdout stdall >&3)' + String exec = consoleOutput ? showOutput : hideOutput + + String header = """\ + #!/bin/bash + source \$HOME/.bashrc > /dev/null 2>&1 || true + { ${failFast ? 'set -e;' : 'set +e;'} } > /dev/null 2>&1 + ${exec} + { ${this.steps.env.PIPELINE_LOG_LEVEL == 'DEBUG' ? 'set -x;' : 'set +x;' } } > /dev/null 2>&1 + + # User Script + """.stripIndent() + + String script = "${header}\n${userScript.stripIndent()}" + this.log.debug("Formatted script:\n${script}") + + return script + } - /** - * Executes a bash script. - * @param script The bash command or script you want to run. - * @return The results of the bash command or script. - */ - private Result execute(String script) { - Integer exitCode = this.steps.sh(script: script, returnStatus: true) + /** + * Runs a bash script that sends all output to the console + * and returns a result object with the stdOut, stdErr + * exitCode and the combined output of the script. + * @param userScript The bash command or script you want to run. + * @return The results of the bash command or script. + */ + Result call(String userScript) { + String script = formatScript(userScript) + + return execute(script) + } - def(String stdOut, String stdErr, String output) = readOutputs() + /** + * Runs a bash script that sends no output to the console + * and returns a result object with the stdOut, stdErr + * exitCode and the combined output of the script. + * @param userScript The bash command or script you want to run. + * @return The results of the bash command or script. + */ + Result silent(String userScript) { + String script = formatScript(userScript, false) + + return execute(script) + } - if (exitCode) { - throw new ScriptError(stdOut, stdErr, output, exitCode) + /** + * Runs a bash script that ignores errors. + * @param userScript The bash command or script you want to run. + * @param consoleOutput Determines if output is sent to the console. + * @param failFast Determines if script should stop on first error or not. + * @return The results of the bash command or script. + */ + Result ignoreErrors(String userScript, Boolean consoleOutput=true, Boolean failFast=false) { + String script = formatScript(userScript, consoleOutput, failFast) + Result result + + try { + result = execute(script) + } catch (ScriptError ex) { + result = new Result(ex.stdOut, ex.stdErr, ex.output, ex.exitCode) + } + + return result } - return new Result(stdOut, stdErr, output, exitCode) - } + /** + * Executes a bash script. + * @param script The bash command or script you want to run. + * @return The results of the bash command or script. + */ + private Result execute(String script) { + Integer exitCode = this.steps.sh(script: script, returnStatus: true) + + def(String stdOut, String stdErr, String output) = readOutputs() - /** - * Reads the script outputs from files and then removes them. - * @return The stdout, stderr and combined script output. - */ - private List readOutputs() { - String stdOut = this.steps.readFile('stdout') - String stdErr = this.steps.readFile('stderr') - String output = this.steps.readFile('stdall') + if (exitCode) { + throw new ScriptError(stdOut, stdErr, output, exitCode) + } - this.steps.sh('#!/bin/bash\n{ set +x; } > /dev/null 2>&1 \nrm stdout stderr stdall > /dev/null 2>&1 || true') + return new Result(stdOut, stdErr, output, exitCode) + } + + /** + * Reads the script outputs from files and then removes them. + * @return The stdout, stderr and combined script output. + */ + private List readOutputs() { + String stdOut = this.steps.readFile('stdout') + String stdErr = this.steps.readFile('stderr') + String output = this.steps.readFile('stdall') - List results = [stdOut, stdErr, output].collect { it.replaceAll('(?m)^\\+.*(?:\r?\n)?', '').trim() } + this.steps.sh('#!/bin/bash\n{ set +x; } > /dev/null 2>&1 \nrm stdout stderr stdall > /dev/null 2>&1 || true') - return results - } + List results = [stdOut, stdErr, output].collect { result -> + result.replaceAll('(?m)^\\+.*(?:\r?\n)?', '').trim() + } + + return results + } } diff --git a/src/org/dsty/bash/Result.groovy b/src/org/dsty/bash/Result.groovy index 22f29e850..20ef40b84 100644 --- a/src/org/dsty/bash/Result.groovy +++ b/src/org/dsty/bash/Result.groovy @@ -7,41 +7,41 @@ import com.cloudbees.groovy.cps.NonCPS */ class Result implements Serializable { - /** - * The contents of stdOut from the bash script. - */ - String stdOut - - /** - * The contents of stdErr from the bash script. - */ - String stdErr - - /** - * The combined contents of stdOut and stdErr from the bash script. - */ - String output - - /** - * The exitCode from the bash script. - */ - Integer exitCode - - Result(String stdOut, String stdErr, String output, Integer exitCode) { - this.stdOut = stdOut - this.stdErr = stdErr - this.output = output - this.exitCode = exitCode - } - - /** - * Print the output of the bash script when the class is printed. - * @return The output from the bash script. - */ - @Override - @NonCPS - String toString() { - return this.output - } + /** + * The contents of stdOut from the bash script. + */ + String stdOut + + /** + * The contents of stdErr from the bash script. + */ + String stdErr + + /** + * The combined contents of stdOut and stdErr from the bash script. + */ + String output + + /** + * The exitCode from the bash script. + */ + Integer exitCode + + Result(String stdOut, String stdErr, String output, Integer exitCode) { + this.stdOut = stdOut + this.stdErr = stdErr + this.output = output + this.exitCode = exitCode + } + + /** + * Print the output of the bash script when the class is printed. + * @return The output from the bash script. + */ + @Override + @NonCPS + String toString() { + return this.output + } } diff --git a/src/org/dsty/bash/ScriptError.groovy b/src/org/dsty/bash/ScriptError.groovy index 88d85caed..5bb95970f 100644 --- a/src/org/dsty/bash/ScriptError.groovy +++ b/src/org/dsty/bash/ScriptError.groovy @@ -5,36 +5,36 @@ package org.dsty.bash */ class ScriptError extends Exception { - /** - * The contents of stdOut from the bash script. - */ - String stdOut - - /** - * The contents of stdErr from the bash script. - */ - String stdErr - - /** - * The combined contents of stdOut and stdErr from the bash script. - */ - String output - - /** - * The exitCode from the bash script. - */ - Integer exitCode - - ScriptError(String stdOut, String stdErr, String output, Integer exitCode) { - super("Script exitCode was ${exitCode} and stderr:\n${stdErr}") - this.stdOut = stdOut - this.stdErr = stdErr - this.output = output - this.exitCode = exitCode - } - - String getFullMessage() { - return "Script exitCode was ${this.exitCode}. Output was:\n${this.output}" - } + /** + * The contents of stdOut from the bash script. + */ + String stdOut + + /** + * The contents of stdErr from the bash script. + */ + String stdErr + + /** + * The combined contents of stdOut and stdErr from the bash script. + */ + String output + + /** + * The exitCode from the bash script. + */ + Integer exitCode + + ScriptError(String stdOut, String stdErr, String output, Integer exitCode) { + super("Script exitCode was ${exitCode} and stderr:\n${stdErr}") + this.stdOut = stdOut + this.stdErr = stdErr + this.output = output + this.exitCode = exitCode + } + + String getFullMessage() { + return "Script exitCode was ${this.exitCode}. Output was:\n${this.output}" + } } diff --git a/src/org/dsty/github/actions/Action.groovy b/src/org/dsty/github/actions/Action.groovy index e18fd3104..1f0ec6381 100644 --- a/src/org/dsty/github/actions/Action.groovy +++ b/src/org/dsty/github/actions/Action.groovy @@ -6,76 +6,72 @@ import org.dsty.logging.LogClient import com.cloudbees.groovy.cps.NonCPS /** -* Github Action that uses a Dockerfile. -*/ + * Github Action that uses a Dockerfile. + */ class Action implements Serializable { - /** - * Workflow script representing the jenkins build. - */ - protected Object steps - - /** - * Logging client - */ - protected LogClient log - - /** - * Bash Client - */ - protected BashClient bash - - /** - * The options from the {@link org.dsty.github.actions.Step#call(java.util.Map) Step}. - */ - protected Map options - - /** - * Default Constructor - *

Using this Class directly in a Jenkins pipeline is an Advanced - * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. - * @param steps The workflow script representing the jenkins build. - */ - Action(Object steps) { - this.steps = steps - this.log = new LogClient(steps) - this.bash = new BashClient(steps) - } + /** + * Workflow script representing the jenkins build. + */ + protected Object steps + + /** + * Logging client + */ + protected LogClient log + + /** + * Bash Client + */ + protected BashClient bash + + /** + * The options from the {@link org.dsty.github.actions.Step#call(java.util.Map) Step}. + */ + protected Map options + + /** + * Default Constructor + *

Using this Class directly in a Jenkins pipeline is an Advanced + * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. + * @param steps The workflow script representing the jenkins build. + */ + Action(Object steps) { + this.steps = steps + this.log = new LogClient(steps) + this.bash = new BashClient(steps) + } - /** - * Parses the input for Github Actions outputs. - * @param input to search for Github Actions outputs. - * @returns the outputs found. - */ - @NonCPS - Map parseOutputs(String input) { + /** + * Parses the input for Github Actions outputs. + * @param input to search for Github Actions outputs. + * @returns the outputs found. + */ + @NonCPS + Map parseOutputs(String input) { + Map outputs = [:] - Map outputs = [:] + List matches = (input =~ /(?m)^::.*$/).findAll() - List matches = (input =~ /(?m)^::.*$/).findAll() + for (match in matches) { + String outputName = (match =~ /(?m)(?<=name=).*(?=::)/).findAll().first() + String outputValue = (match =~ /(?m)::.*::(.*$)/).findAll().first()[1] - for (match in matches) { - String outputName = (match =~ /(?m)(?<=name=).*(?=::)/).findAll().first() - String outputValue = (match =~ /(?m)::.*::(.*$)/).findAll().first()[1] + outputs[outputName] = outputValue + } - outputs[outputName] = outputValue + return outputs } - return outputs - - } - - /** - * Removes the Github Actions outputs so it can be displayed - * to the user. - * @param input to search for Github Actions outputs. - * @returns the input free of any Github Action outputs. - */ - @NonCPS - String cleanOutput(String input) { - - return (input =~ /(?m)^::.*$/).replaceAll('') - - } + /** + * Removes the Github Actions outputs so it can be displayed + * to the user. + * @param input to search for Github Actions outputs. + * @returns the input free of any Github Action outputs. + */ + @NonCPS + String cleanOutput(String input) { + return (input =~ /(?m)^::.*$/).replaceAll('') + } } diff --git a/src/org/dsty/github/actions/ActionFactory.groovy b/src/org/dsty/github/actions/ActionFactory.groovy index 62dcb71ed..993b441b0 100644 --- a/src/org/dsty/github/actions/ActionFactory.groovy +++ b/src/org/dsty/github/actions/ActionFactory.groovy @@ -1,141 +1,137 @@ -/* groovylint-disable DuplicateStringLiteral */ +/* groovylint-disable DuplicateNumberLiteral, DuplicateStringLiteral, LineLength */ package org.dsty.github.actions import org.dsty.logging.LogClient /** -* Determines the type of Action being used and returns the -* proper class to represent that action. -*/ + * Determines the type of Action being used and returns the + * proper class to represent that action. + */ class ActionFactory implements Serializable { - /** - * Workflow script representing the jenkins build. - */ - final private Object steps - - /** - * Logging client - */ - final private LogClient log - - /** - * Default Constructor - *

Using this Class directly in a Jenkins pipeline is an Advanced - * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. - * @param steps The workflow script representing the jenkins build. - */ - ActionFactory(Object steps) { - this.steps = steps - this.log = new LogClient(steps) - } - - /** - * Return the proper Github Action. - * @param options The valid keys and values can be found the here. - */ - GithubAction makeAction(Map options) { - - String actionType = this.determineType(options) - - GithubAction action - - if (actionType == 'docker') { - action = new DockerAction(this.steps) + /** + * Workflow script representing the jenkins build. + */ + final private Object steps + + /** + * Logging client + */ + final private LogClient log + + /** + * Default Constructor + *

Using this Class directly in a Jenkins pipeline is an Advanced + * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. + * @param steps The workflow script representing the jenkins build. + */ + ActionFactory(Object steps) { + this.steps = steps + this.log = new LogClient(steps) } - if (actionType == 'run') { - action = new RunAction(this.steps) + /** + * Return the proper Github Action. + * @param options The valid keys and values can be found + * here. + */ + GithubAction makeAction(Map options) { + String actionType = this.determineType(options) + + GithubAction action + + switch (actionType) { + case 'docker': + action = new DockerAction(this.steps) + break + case 'run': + action = new RunAction(this.steps) + break + case 'node12': + action = new JavaScriptAction(this.steps) + break + } + + if (actionType == 'node12') { + action = new JavaScriptAction(this.steps) + } + + if (!action) { + throw new IllegalStateException('Unable to determine the type of Github action.') + } + + action.options = options + + return action } - if (actionType == 'node12') { - action = new JavaScriptAction(this.steps) + /** + * Parses the action's id into a Github Org, Repo and Ref. + * @param actionID The id for the Github action you want to run. + * @return The Github Org, Repo and Ref. + */ + private Map parseActionID(String actionID) { + List parts + String fullName + Map results = [:] + + if (actionID.contains('@')) { + parts = actionID.tokenize('@') + fullName = parts[0] + results.Ref = parts[1] + } + + parts = fullName ? fullName.tokenize('/') : actionID.tokenize('/') + + results.Name = parts[1] + results.Org = parts[0] + results.Ref = results.Ref ?: 'master' + + return results } - if (!action) { - throw new IllegalStateException('Unable to determine the type of Github action.') - } - - action.options = options - - return action - } - - /** - * Parses the action's id into a Github Org, Repo and Ref. - * @param actionID The id for the Github action you want to run. - * @return The Github Org, Repo and Ref. - */ - private Map parseActionID(String actionID) { - - List parts - String fullName - Map results = [:] - - if (actionID.contains('@')) { - parts = actionID.tokenize('@') - fullName = parts[0] - results.Ref = parts[1] - } - - parts = fullName ? fullName.tokenize('/') : actionID.tokenize('/') - - results.Name = parts[1] - results.Org = parts[0] - results.Ref = results.Ref ?: 'master' - - return results - } - - /** - * Determines the type of action to use. - * @param options The valid keys and values can be found the here. - * @return The name of the type of action to use. - */ - private String determineType(Map options) { - - String actionType - - if (options.run) { - - actionType = 'run' - + /** + * Determines the type of action to use. + * @param options The valid keys and values can be found + * here. + * @return The name of the type of action to use. + */ + private String determineType(Map options) { + String actionType + + if (options.run) { + actionType = 'run' } else { + Map scmArgs = this.parseActionID(options.uses) - Map scmArgs = this.parseActionID(options.uses) + options.actionID = scmArgs.Name - options.actionID = scmArgs.Name + Map metadata = readMetadata(scmArgs, options.workspace) + options.metadata = metadata - Map metadata = readMetadata(scmArgs, options.workspace) - options.metadata = metadata + this.log.debug("Metadata for ${options.actionID}:\n${this.log.pprint(metadata)}") - this.log.debug("Metadata for ${options.actionID}:\n${this.log.pprint(metadata)}") + actionType = metadata.runs.using + } - actionType = metadata.runs.using + this.log.debug("The Github action type is ${actionType}.") + return actionType } - this.log.debug("The Github action type is ${actionType}.") - - return actionType - - } + /** + * Reads the action Metadata file. + * @param scmArgs The Github Org, Repo and Ref to checkout the action. + * @param workspace The directory to store the actions code. + * @return The actions metadata. + */ + private Map readMetadata(Map scmArgs, String workspace) { + Map metadata - /** - * Reads the action Metadata file. - * @param scmArgs The Github Org, Repo and Ref to checkout the action. - * @param workspace The directory to store the actions code. - * @return The actions metadata. - */ - private Map readMetadata(Map scmArgs, String workspace) { + this.log.info("Checking out ${scmArgs.Org}/${scmArgs.Name}@${scmArgs.Ref}") - Map metadata - - this.log.info("Checking out ${scmArgs.Org}/${scmArgs.Name}@${scmArgs.Ref}") - - this.steps.dir("${workspace}/${scmArgs.Name}") { - - this.steps.checkout( + this.steps.dir("${workspace}/${scmArgs.Name}") { + this.steps.checkout( changelog: false, poll: false, scm: [ @@ -146,14 +142,12 @@ class ActionFactory implements Serializable { ] ) - String fileName = this.steps.fileExists('action.yml') ? 'action.yml' : 'action.yaml' + String fileName = this.steps.fileExists('action.yml') ? 'action.yml' : 'action.yaml' - metadata = this.steps.readYaml(file: fileName) + metadata = this.steps.readYaml(file: fileName) + } + return metadata } - return metadata - - } - } diff --git a/src/org/dsty/github/actions/DockerAction.groovy b/src/org/dsty/github/actions/DockerAction.groovy index d0684221a..2a347c00e 100644 --- a/src/org/dsty/github/actions/DockerAction.groovy +++ b/src/org/dsty/github/actions/DockerAction.groovy @@ -5,308 +5,304 @@ import org.dsty.bash.Result import com.cloudbees.groovy.cps.NonCPS /** -* Github Action that uses a Docker Container. -*/ + * Github Action that uses a Docker Container. + */ class DockerAction extends Action implements GithubAction { - /** - * The name of the action. - */ - String name - - /** - * The metadata from action.yml/yaml file. - */ - protected Map metadata - - /** - * Docker volume mounts for the container with out '-v' appended. - */ - protected final List mounts = [] - - /** - * Default Constructor - *

Using this Class directly in a Jenkins pipeline is an Advanced - * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. - * @param steps The workflow script representing the jenkins build. - */ - DockerAction(Object steps) { - super(steps) - } - - /** - * Options are mix of values that come from either {@link org.dsty.github.actions.Step#Step Step} - * or the {@link org.dsty.github.actions.ActionFactory#ActionFactory ActionFactory}. They why it's - * prefered to use those classes over this one directly. - * @param options used to configure and run this Github Action. - */ - void setOptions(Map options) { - this.options = options - this.name = options.actionID - this.metadata = options.metadata - } - - /** - * This method will prepare the Action, run the action and then parse the output. - * @returns the outputs from the action. - */ - Map run() { - - String imageID = this.dockerBuild() - - Map outputs = this.actionRun(imageID) - - return outputs - - } - - /** - * Will either build the container image or use the - * image specified in the {@link #metadata}. - * @returns the docker image id. - */ - String dockerBuild() { - - if (this.metadata.runs.image.contains('docker://')) { - imageID = this.metadata.runs.image.replace('docker://', '') - return imageID + /** + * The name of the action. + */ + String name + + /** + * The metadata from action.yml/yaml file. + */ + protected Map metadata + + /** + * Docker volume mounts for the container with out '-v' appended. + */ + protected final List mounts = [] + + /** + * Default Constructor + *

Using this Class directly in a Jenkins pipeline is an Advanced + * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. + * @param steps The workflow script representing the jenkins build. + */ + DockerAction(Object steps) { + super(steps) } - this.log.info("Building Docker Container for ${this.name}") - - this.steps.dir("${this.options.workspace}/${this.name}") { - - Result result = this.bash.silent("docker build -t ${this.name} .") - - this.log.debug(result.output) - + /** + * Options are mix of values that come from either {@link org.dsty.github.actions.Step#Step Step} + * or the {@link org.dsty.github.actions.ActionFactory#ActionFactory ActionFactory}. They why it's + * prefered to use those classes over this one directly. + * @param options used to configure and run this Github Action. + */ + void setOptions(Map options) { + this.options = options + this.name = options.actionID + this.metadata = options.metadata } - return this.name - - } - - /** - * Runs the action using the values configued in - * {@link #metadata metadata.runs} - * @param imageID the docker image to use. - * @returns the outputs from the action. - */ - Map actionRun(String imageID) { - - Map inputs = this.loadDefaults() - - this.log.info("Running Action ${this.name}") - - List runArgs = metadata.runs.get('args', []) - - String containerArgs = runArgs ? this.renderArgs(runArgs, inputs) : '' - - Map renderedEnvVars = inputs.collectEntries { [("INPUT_${ this.normalizeVariable(it.key) }".toString()): it.value] } + /** + * This method will prepare the Action, run the action and then parse the output. + * @returns the outputs from the action. + */ + Map run() { + String imageID = this.dockerBuild() - Map finalEnvVars = renderedEnvVars << this.options.env - - String buildSlug = "${this.steps.env.BUILD_TAG}-${this.name}".replaceAll(' ', '-') - - String entryFlag = '--entrypoint' - - String entryPoint = this.metadata.runs.entrypoint ? "${entryFlag} ${this.metadata.runs.entrypoint}" : '' - - Map outputs = [:] - - if (this.metadata.runs['pre-entrypoint']) { - - this.log.debug("${this.name} - Running pre-entrypoint.") - - String preArgs = "${this.metadata.runs['pre-entrypoint']} ${containerArgs}" - - outputs << this.dockerRun("${buildSlug}-pre", imageID, finalEnvVars, preArgs, entryPoint) + Map outputs = this.actionRun(imageID) + return outputs } - this.log.debug("${this.name} - Running main entrypoint.") - - outputs << this.dockerRun(buildSlug, imageID, finalEnvVars, containerArgs, entryPoint) - - if (this.metadata.runs['post-entrypoint']) { + /** + * Will either build the container image or use the + * image specified in the {@link #metadata}. + * @returns the docker image id. + */ + String dockerBuild() { + if (this.metadata.runs.image.contains('docker://')) { + imageID = this.metadata.runs.image.replace('docker://', '') + return imageID + } - this.log.debug("${this.name} - Running post-entrypoint.") + this.log.info("Building Docker Container for ${this.name}") - String postArgs = "${this.metadata.runs['post-entrypoint']} ${containerArgs}" + this.steps.dir("${this.options.workspace}/${this.name}") { + Result result = this.bash.silent("docker build -t ${this.name} .") - outputs << this.dockerRun("${buildSlug}-pre", imageID, finalEnvVars, postArgs, entryPoint) + this.log.debug(result.output) + } + return this.name } - return outputs + /** + * Runs the action using the values configued in + * {@link #metadata metadata.runs} + * @param imageID the docker image to use. + * @returns the outputs from the action. + */ + Map actionRun(String imageID) { + Map inputs = this.loadDefaults() - } + this.log.info("Running Action ${this.name}") - /** - * Runs a docker container and parses it's outputs. - * @param containerName the name of the container. - * @param imageID the docker image to use. - * @param envVars the environment vars to pass to docker run. - * @param containerArgs the argurments to pass to the entrypoint. - * @param entryPoint the entrypoint to use or an empty string to use containers entrypoint. - * @returns the outputs from the action. - */ - Map dockerRun(String containerName, String imageID, Map finalEnvVars, String containerArgs, String entryPoint) { + List runArgs = metadata.runs.get('args', []) - String envVars = finalEnvVars.collect { "-e ${it.key}" }.join(' ') + String containerArgs = runArgs ? this.renderArgs(runArgs, inputs) : '' - /* groovylint-disable-next-line SpaceAfterOpeningBrace, SpaceBeforeClosingBrace */ - String containerEnv = finalEnvVars.collect {"'${it.key}=${it.value }'"}.join(' ') + Map renderedEnvVars = inputs.collectEntries { key, value -> + [("INPUT_${ this.normalizeVariable(key) }".toString()): value] + } - this.mounts.addAll( - [ - '/var/run/docker.sock:/var/run/docker.sock', - "${this.steps.WORKSPACE}:/github/workspace" - ] - ) + Map finalEnvVars = renderedEnvVars << this.options.env - String volumeMounts = this.mounts.collect { "-v '${it}'" }.join(' ') + String buildSlug = "${this.steps.env.BUILD_TAG}-${this.name}".replaceAll(' ', '-') - Result result = this.bash.silent("env ${containerEnv} docker run --rm --name ${containerName} --group-add \$(getent group docker | cut -d: -f3) ${envVars} ${volumeMounts} ${entryPoint} ${imageID} ${containerArgs}") + String entryFlag = '--entrypoint' - this.log.debug(result.stdOut) + String entryPoint = this.metadata.runs.entrypoint ? "${entryFlag} ${this.metadata.runs.entrypoint}" : '' - this.steps.println(this.cleanOutput(result.stdOut)) + Map outputs = [:] - return this.parseOutputs(result.stdOut) + if (this.metadata.runs['pre-entrypoint']) { + this.log.debug("${this.name} - Running pre-entrypoint.") - } + String preArgs = "${this.metadata.runs['pre-entrypoint']} ${containerArgs}" - /** - * Parses the metadata for inputs and sets the default value - * or used the value from {@link #options}. - * @returns the configued inputs. - */ - Map loadDefaults() { + outputs << this.dockerRun("${buildSlug}-pre", imageID, finalEnvVars, preArgs, entryPoint) + } - Map inputs = [:] + this.log.debug("${this.name} - Running main entrypoint.") - if (this.metadata.inputs) { + outputs << this.dockerRun(buildSlug, imageID, finalEnvVars, containerArgs, entryPoint) - inputs = this.metadata.inputs.collectEntries { inputName, inputMeta -> - String defaultValue = inputMeta.default ?: '' + if (this.metadata.runs['post-entrypoint']) { + this.log.debug("${this.name} - Running post-entrypoint.") - String finalValue = this.options.with["${inputName}"] ?: defaultValue + String postArgs = "${this.metadata.runs['post-entrypoint']} ${containerArgs}" - if (!finalValue && inputMeta.required) { - finalValue = 'REQUIRED' + outputs << this.dockerRun("${buildSlug}-pre", imageID, finalEnvVars, postArgs, entryPoint) } - [(inputName): finalValue] - - } - + return outputs } - List required = inputs.findAll { it.value == 'REQUIRED' }.collect { it.key } - - if (required) { - throw new Exception("No value was provided for these required inputs: ${required.join(', ')}") + /** + * Runs a docker container and parses it's outputs. + * @param containerName the name of the container. + * @param imageID the docker image to use. + * @param envVars the environment vars to pass to docker run. + * @param containerArgs the argurments to pass to the entrypoint. + * @param entryPoint the entrypoint to use or an empty string to use containers entrypoint. + * @returns the outputs from the action. + */ + Map dockerRun(String containerName, String imageID, Map finalEnvVars, String containerArgs, String entryPoint) { + String envVars = finalEnvVars.collect { key, value -> + "-e ${key}" + }.join(' ') + + String containerEnv = finalEnvVars.collect { key, value -> + "'${key}=${value }'" + }.join(' ') + + this.mounts.addAll( + [ + '/var/run/docker.sock:/var/run/docker.sock', + "${this.steps.WORKSPACE}:/github/workspace" + ] + ) + + String volumeMounts = this.mounts.collect { volume -> "-v '${volume}'" }.join(' ') + + String script = """\ + env ${containerEnv} docker run --rm --name ${containerName} \ + --group-add \$(getent group docker | cut -d: -f3) \ + ${envVars} \ + ${volumeMounts} \ + ${entryPoint} \ + ${imageID} \ + ${containerArgs} + """.stripIndent() + + Result result = this.bash.silent(script) + + this.log.debug(result.stdOut) + + this.steps.println(this.cleanOutput(result.stdOut)) + + return this.parseOutputs(result.stdOut) } - Map filteredInputs = inputs.findAll { it.value != 'REQUIRED' } + /** + * Parses the metadata for inputs and sets the default value + * or used the value from {@link #options}. + * @returns the configued inputs. + */ + Map loadDefaults() { + Map inputs = [:] - return filteredInputs + if (this.metadata.inputs) { + inputs = this.metadata.inputs.collectEntries { inputName, inputMeta -> + String defaultValue = inputMeta.default ?: '' - } + String finalValue = this.options.with["${inputName}"] ?: defaultValue - /** - * Creates a string of arguments that can be passed to a shell. - * @param args that are defined in {@link #metadata metadata.runs.args} but can also be an empty list. - * @param inputs the configued inputs from {@link #loadDefaults()}. - * @returns the command line arguments. - */ - String renderArgs(List args, Map inputs) { + if (!finalValue && inputMeta.required) { + finalValue = 'REQUIRED' + } - List renderedArgs = args.collect { it -> renderTemplate(it, inputs) } - - String containerArgs = renderedArgs.collect { "'${it.value}'" }.join(' ') + [(inputName): finalValue] + } + } - return containerArgs + List required = inputs.findAll { key, value -> + value == 'REQUIRED' + }.collect { key, value -> + key + } - } + if (required) { + throw new Exception("No value was provided for these required inputs: ${required.join(', ')}") + } - /** - * Tranforms inputs into environment variables using the format Github actions requires. - * @param inputs the configued inputs from {@link #loadDefaults()}. - * @returns the environment vars in the correct format. - */ - String renderEnvVars(Map inputs) { + Map filteredInputs = inputs.findAll { key, value -> + value != 'REQUIRED' + } - return inputs.collectEntries { [("INPUT_${normalizeVariable(it.key)}".toString()): it.value] } + return filteredInputs + } - } + /** + * Creates a string of arguments that can be passed to a shell. + * @param args that are defined in {@link #metadata metadata.runs.args} but can also be an empty list. + * @param inputs the configued inputs from {@link #loadDefaults()}. + * @returns the command line arguments. + */ + String renderArgs(List args, Map inputs) { + List renderedArgs = args.collect { arg -> + renderTemplate(arg, inputs) + } - /** - * Makes a string match the format required - * by Github Actions for use as an input Map key. - * @param value the value to be transformed. - * @returns the value that is now all uppercase and spaces turned to _. - */ - @NonCPS - String normalizeVariable(String value) { + String containerArgs = renderedArgs.collect { arg -> + "'${arg}'" + }.join(' ') - return value.toUpperCase().replace(' ', '_') + return containerArgs + } - } + /** + * Tranforms inputs into environment variables using the format Github actions requires. + * @param inputs the configued inputs from {@link #loadDefaults()}. + * @returns the environment vars in the correct format. + */ + String renderEnvVars(Map inputs) { + return inputs.collectEntries { key, value -> + [("INPUT_${normalizeVariable(key)}".toString()): value] + } + } - /** - * Replaces all Github variables with Groovy GString variables. - * @param value that contains Github Variables. - * @returns the value but with GString variables. - */ - @NonCPS - String convertVariable(String value) { + /** + * Makes a string match the format required + * by Github Actions for use as an input Map key. + * @param value the value to be transformed. + * @returns the value that is now all uppercase and spaces turned to _. + */ + @NonCPS + String normalizeVariable(String value) { + return value.toUpperCase().replace(' ', '_') + } + /** + * Replaces all Github variables with Groovy GString variables. + * @param value that contains Github Variables. + * @returns the value but with GString variables. + */ + @NonCPS + String convertVariable(String value) { /* groovylint-disable-next-line GStringExpressionWithinString */ - return (value =~ /\$\{\{(.*)}}/).replaceAll('\\${$1}') - - } - - /** - * Changes dot notation variables to bracket notation. - *

This change is important because in the Github Actions - * metadata file variables often have hypens. Hypens don't seem - * to work with dot notation so we reformat them to bracket notation. - * @param template that contains GStrings with dot notation. - * @returns the template with GString that use bracket notation instead. - */ - @NonCPS - String normalizeTemplate(String template) { - - String templateVar = (template =~ /(?<=inputs\.)[\w\-]*/).findAll().first() - - return (template =~ /\.[\w\-]*/).replaceFirst("['${templateVar}']") - - } - - /** - * Takes a String that contains GString variables - * and then renders that String into one where the - * variables have been replaced with their values. - * @param arg that contains GString Variables. - * @param inputs the variables used when rendering the String. - * @returns the arg but with GString variables rendered into values. - */ - @NonCPS - String renderTemplate(String arg, Map inputs) { - - if (! (arg =~ /\$\{.*}/) ) { - return arg + return (value =~ /\$\{\{(.*)}}/).replaceAll('\\${$1}') } - String template = convertVariable(arg) - String normalTemplate = normalizeTemplate(template) + /** + * Changes dot notation variables to bracket notation. + *

This change is important because in the Github Actions + * metadata file variables often have hypens. Hypens don't seem + * to work with dot notation so we reformat them to bracket notation. + * @param template that contains GStrings with dot notation. + * @returns the template with GString that use bracket notation instead. + */ + @NonCPS + String normalizeTemplate(String template) { + String templateVar = (template =~ /(?<=inputs\.)[\w\-]*/).findAll().first() + + return (template =~ /\.[\w\-]*/).replaceFirst("['${templateVar}']") + } - groovy.text.SimpleTemplateEngine engine = new groovy.text.SimpleTemplateEngine() + /** + * Takes a String that contains GString variables + * and then renders that String into one where the + * variables have been replaced with their values. + * @param arg that contains GString Variables. + * @param inputs the variables used when rendering the String. + * @returns the arg but with GString variables rendered into values. + */ + @NonCPS + String renderTemplate(String arg, Map inputs) { + if (! (arg =~ /\$\{.*}/) ) { + return arg + } - return engine.createTemplate(normalTemplate).make(['inputs': inputs]).toString() + String template = convertVariable(arg) + String normalTemplate = normalizeTemplate(template) - } + groovy.text.SimpleTemplateEngine engine = new groovy.text.SimpleTemplateEngine() + + return engine.createTemplate(normalTemplate).make(['inputs': inputs]).toString() + } } diff --git a/src/org/dsty/github/actions/GithubAction.groovy b/src/org/dsty/github/actions/GithubAction.groovy index ac9cd4efc..1b6944a25 100644 --- a/src/org/dsty/github/actions/GithubAction.groovy +++ b/src/org/dsty/github/actions/GithubAction.groovy @@ -5,6 +5,6 @@ package org.dsty.github.actions */ interface GithubAction { - Map run() + Map run() } diff --git a/src/org/dsty/github/actions/JavaScriptAction.groovy b/src/org/dsty/github/actions/JavaScriptAction.groovy index 5d9129657..caf94bf14 100644 --- a/src/org/dsty/github/actions/JavaScriptAction.groovy +++ b/src/org/dsty/github/actions/JavaScriptAction.groovy @@ -8,87 +8,79 @@ import com.cloudbees.groovy.cps.NonCPS */ class JavaScriptAction extends DockerAction implements GithubAction { - /** - * Default Constructor - *

Using this Class directly in a Jenkins pipeline is an Advanced - * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. - * @param steps The workflow script representing the jenkins build. - */ - JavaScriptAction(Object steps) { - super(steps) - } - - /** - * This method will prepare the Action, run the action and then parse the output. - * @returns the outputs from the action. - */ - @Override - Map run() { - - String actionDir = '/github/action' - String workspace = "${this.options.workspace}/${this.name}" - - if (this.steps.env.DIND_JENKINS_HOME) { + /** + * Default Constructor + *

Using this Class directly in a Jenkins pipeline is an Advanced + * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. + * @param steps The workflow script representing the jenkins build. + */ + JavaScriptAction(Object steps) { + super(steps) + } - List parts = this.options.workspace.split('/workspace/') + /** + * This method will prepare the Action, run the action and then parse the output. + * @returns the outputs from the action. + */ + @Override + Map run() { + String actionDir = '/github/action' + String workspace = "${this.options.workspace}/${this.name}" - workspace = "${this.steps.env.DIND_JENKINS_HOME}/workspace/${parts[1]}/${this.name}" + if (this.steps.env.DIND_JENKINS_HOME) { + List parts = this.options.workspace.split('/workspace/') - } + workspace = "${this.steps.env.DIND_JENKINS_HOME}/workspace/${parts[1]}/${this.name}" + } - this.metadata.runs.entrypoint = 'node' - this.metadata.runs.args = ["${actionDir}/${this.metadata.runs.main}" + this.metadata.runs.entrypoint = 'node' + this.metadata.runs.args = ["${actionDir}/${this.metadata.runs.main}" ] - this.metadata.runs['pre-entrypoint'] = this.metadata.runs.pre ?: '' - this.metadata.runs['post-entrypoint'] = this.metadata.runs.post ?: '' - - this.mounts.add("${workspace}:${actionDir}") + this.metadata.runs['pre-entrypoint'] = this.metadata.runs.pre ?: '' + this.metadata.runs['post-entrypoint'] = this.metadata.runs.post ?: '' - this.log.info(this.log.pprint(this.options)) - this.log.info(this.log.pprint(this.metadata)) + this.mounts.add("${workspace}:${actionDir}") - Map outputs = this.actionRun('node:12.20.1-buster-slim') + this.log.info(this.log.pprint(this.options)) + this.log.info(this.log.pprint(this.metadata)) - return outputs + Map outputs = this.actionRun('node:12.20.1-buster-slim') - } + return outputs + } - /** - * Parses the input for Github Actions outputs. - * @param input to search for Github Actions outputs. - * @returns the outputs found. - */ - @NonCPS - @Override - Map parseOutputs(String input) { + /** + * Parses the input for Github Actions outputs. + * @param input to search for Github Actions outputs. + * @returns the outputs found. + */ + @NonCPS + @Override + Map parseOutputs(String input) { + Map outputs = [:] - Map outputs = [:] + List matches = (input =~ /(?m)##\[set-output.*$/).findAll() - List matches = (input =~ /(?m)##\[set-output.*$/).findAll() + for (match in matches) { + String outputName = (match =~ /(?m)(?<=name=).*(?=;])/).findAll().first() + String outputValue = (match =~ /(?m);](.*$)/).findAll().first()[1] - for (match in matches) { - String outputName = (match =~ /(?m)(?<=name=).*(?=;])/).findAll().first() - String outputValue = (match =~ /(?m);](.*$)/).findAll().first()[1] + outputs[outputName] = outputValue + } - outputs[outputName] = outputValue + return outputs } - return outputs - - } - - /** - * Removes the Github Actions outputs so it can be displayed - * to the user. - * @param input to search for Github Actions outputs. - * @returns the input free of any Github Action outputs. - */ - @NonCPS - @Override - String cleanOutput(String input) { - - return (input =~ /(?m)##\[set-output.*$/).replaceAll('') - - } + /** + * Removes the Github Actions outputs so it can be displayed + * to the user. + * @param input to search for Github Actions outputs. + * @returns the input free of any Github Action outputs. + */ + @NonCPS + @Override + String cleanOutput(String input) { + return (input =~ /(?m)##\[set-output.*$/).replaceAll('') + } } diff --git a/src/org/dsty/github/actions/RunAction.groovy b/src/org/dsty/github/actions/RunAction.groovy index 686f08a38..6d895e9ff 100644 --- a/src/org/dsty/github/actions/RunAction.groovy +++ b/src/org/dsty/github/actions/RunAction.groovy @@ -3,85 +3,80 @@ package org.dsty.github.actions import org.dsty.bash.Result /** -* Github Action that uses a executes a shell command. -*

Currently the working-directory and shell options are -* not implemented.

-*/ + * Github Action that uses a executes a shell command. + *

Currently the working-directory and shell options are + * not implemented.

+ */ class RunAction extends Action implements GithubAction { - /** - * The name of the action. - */ - String name - - /** - * The options from the {@link org.dsty.github.actions.Step#call(java.util.Map) Step}. - */ - Map options - - /** - * Default Constructor - *

Using this Class directly in a Jenkins pipeline is an Advanced - * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. - * @param steps The workflow script representing the jenkins build. - */ - RunAction(Object steps) { - super(steps) - } - - /** - * Options are mix of values that come from either {@link org.dsty.github.actions.Step#Step Step} - * or the {@link org.dsty.github.actions.ActionFactory#ActionFactory ActionFactory}. Thats why it's - * prefered to use those classes over this one directly. - * @param options used to configure and run this Github Action. - */ - void setOptions(Map options) { - this.options = options - this.name = options.name ?: options.run - } - - /** - * This method will prepare the Action, run the action and then parse the output. - * @returns the outputs from the action. - */ - @Override - Map run() { - - Map outputs = this.runCMD() - - return outputs + /** + * The name of the action. + */ + String name + + /** + * The options from the {@link org.dsty.github.actions.Step#call(java.util.Map) Step}. + */ + Map options + + /** + * Default Constructor + *

Using this Class directly in a Jenkins pipeline is an Advanced + * use case. Most people should just use {@link org.dsty.github.actions.Step#Step Step}. + * @param steps The workflow script representing the jenkins build. + */ + RunAction(Object steps) { + super(steps) + } - } + /** + * Options are mix of values that come from either {@link org.dsty.github.actions.Step#Step Step} + * or the {@link org.dsty.github.actions.ActionFactory#ActionFactory ActionFactory}. Thats why it's + * prefered to use those classes over this one directly. + * @param options used to configure and run this Github Action. + */ + void setOptions(Map options) { + this.options = options + this.name = options.name ?: options.run + } - /** - * Runs the action using the provided command. - * @returns the outputs from the action. - */ - Map runCMD() { + /** + * This method will prepare the Action, run the action and then parse the output. + * @returns the outputs from the action. + */ + @Override + Map run() { + Map outputs = this.runCMD() - this.log.info("Run ${this.name}") + return outputs + } - this.options.env = this.options.env ?: [:] + /** + * Runs the action using the provided command. + * @returns the outputs from the action. + */ + Map runCMD() { + this.log.info("Run ${this.name}") - /* groovylint-disable-next-line SpaceAfterOpeningBrace, SpaceBeforeClosingBrace */ - List containerEnv = this.options.env.collect {"${it.key}=${it.value }"} + this.options.env = this.options.env ?: [:] - Map outputs = [:] + List containerEnv = this.options.env.collect { key, value -> + "${key}=${value }" + } - this.steps.withEnv(containerEnv) { + Map outputs = [:] - Result result = this.bash.silent(this.options.run) + this.steps.withEnv(containerEnv) { + Result result = this.bash.silent(this.options.run) - this.log.debug(result.stdOut) + this.log.debug(result.stdOut) - this.steps.println(this.cleanOutput(result.stdOut)) + this.steps.println(this.cleanOutput(result.stdOut)) - outputs = this.parseOutputs(result.stdOut) + outputs = this.parseOutputs(result.stdOut) + } + return outputs } - return outputs - - } - } diff --git a/src/org/dsty/github/actions/Step.groovy b/src/org/dsty/github/actions/Step.groovy index e60efb435..928db16bc 100644 --- a/src/org/dsty/github/actions/Step.groovy +++ b/src/org/dsty/github/actions/Step.groovy @@ -1,3 +1,4 @@ +/* groovylint-disable LineLength */ package org.dsty.github.actions import org.dsty.logging.LogClient @@ -7,73 +8,68 @@ import org.dsty.logging.LogClient */ class Step implements Serializable { - /** - * Workflow script representing the jenkins build. - */ - private final Object steps - - /** - * Logging client - */ - private final LogClient log - - /** - * Default Constructor - *

{@code
-   * import org.dsty.github.actions.Step
-   *node() {
-   *  Step action = new Step(this)
-   *  Map options = [
-   *      'uses': 'actions/hello-world-docker-action@master',
-   *      'with': [
-   *          'who-to-greet': 'Mona the Octocat'
-   *      ]
-   *  ]
-   *  action(options)
-   *}}
- * @param steps The workflow script representing the jenkins build. - */ - Step(Object steps) { - this.steps = steps - this.log = new LogClient(steps) - } - - /** - * Runs the Github Action Step. - * @param options The valid keys and values can be found the here. - * @return The outputs from the Action. - */ - Map call(Map options) throws IllegalArgumentException { - - if (!options.run && !options.uses) { - throw new IllegalArgumentException("Unable to run step. Please provide a 'run' or 'uses' value.") + /** + * Workflow script representing the jenkins build. + */ + private final Object steps + + /** + * Logging client + */ + private final LogClient log + + /** + * Default Constructor + *
{@code
+     * import org.dsty.github.actions.Step
+     *node() {
+     *  Step action = new Step(this)
+     *  Map options = [
+     *      'uses': 'actions/hello-world-docker-action@master',
+     *      'with': [
+     *          'who-to-greet': 'Mona the Octocat'
+     *      ]
+     *  ]
+     *  action(options)
+     *}}
+ * @param steps The workflow script representing the jenkins build. + */ + Step(Object steps) { + this.steps = steps + this.log = new LogClient(steps) } - options.env = options.env ?: [:] + /** + * Runs the Github Action Step. + * @param options The valid keys and values can be found + * here. + * @return The outputs from the Action. + */ + Map call(Map options) throws IllegalArgumentException { + if (!options.run && !options.uses) { + throw new IllegalArgumentException("Unable to run step. Please provide a 'run' or 'uses' value.") + } - if (options.if == null) { + options.env = options.env ?: [:] - options.if = true + if (options.if == null) { + options.if = true + } - } + if (!options.if) { + this.log.info('Skipping step.') + return [:] + } - if (!options.if) { + options.workspace = this.steps.env.WORKSPACE_TMP ?: this.steps.env.WORKSPACE - this.log.info('Skipping step.') - return [:] + ActionFactory factory = new ActionFactory(this.steps) - } + GithubAction action = factory.makeAction(options) - options.workspace = this.steps.env.WORKSPACE_TMP ?: this.steps.env.WORKSPACE + Map outputs = action.run() - ActionFactory factory = new ActionFactory(this.steps) - - GithubAction action = factory.makeAction(options) - - Map outputs = action.run() - - return outputs - - } + return outputs + } } diff --git a/src/org/dsty/github/actions/Workflow.groovy b/src/org/dsty/github/actions/Workflow.groovy index bc88b7af0..2305cba93 100644 --- a/src/org/dsty/github/actions/Workflow.groovy +++ b/src/org/dsty/github/actions/Workflow.groovy @@ -19,137 +19,124 @@ import org.dsty.logging.LogClient */ class Workflow implements Serializable { - /** - * Workflow script representing the jenkins build. - */ - private final Object steps - - /** - * Logging client - */ - private final LogClient log - - /** - * Bash Client - */ - private final BashClient bash - - /** - * The version of act to install. - */ - String version - - /** - * Default Constructor - * @param steps The workflow script representing the jenkins build. - */ - Workflow(Object steps) { - this.steps = steps - this.log = new LogClient(steps) - this.bash = new BashClient(steps) - } - - /** - * Installs and then runs act. - *

The default event is push to change it to pull_request:

- *
{@code
-   * import org.dsty.github.actions.Workflow
-   *node() {
-   *  Workflow workflow = new Workflow(this)
-   *  workflow('pull_request')
-   *}}
- * @param args The arguments passed directly to act. See act docs. - * @return The output from the act command. - */ - String call(String args = '') throws ScriptError { - this.install() - - return this.run(args) - } - - /** - * Installs act by first checking if it is already installed and - * if not then installs the latest version. The property {@link #version} can be set to install - * a specific version. - */ - void install() throws ScriptError { - - this.log.info('Installing Act.') - - Result result = this.bash.ignoreErrors(/set -o pipefail; act --version | cut -d' ' -f3/, false, true) - - Closure alreadyInstalled = { - - this.version = "v${result.stdOut}" - - this.log.info("Version ${this.version} of act is already installed.") + /** + * Workflow script representing the jenkins build. + */ + private final Object steps + + /** + * Logging client + */ + private final LogClient log + + /** + * Bash Client + */ + private final BashClient bash + + /** + * The version of act to install. + */ + String version + + /** + * Default Constructor + * @param steps The workflow script representing the jenkins build. + */ + Workflow(Object steps) { + this.steps = steps + this.log = new LogClient(steps) + this.bash = new BashClient(steps) } - Closure installLatest = { - - this.log.info('Downloading the latest version.') - this.bash.call('curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash') - - } - - Closure installVersion = { - - this.log.info("Downloading version ${this.version}.") - this.bash.call("""\ - curl https://raw.githubusercontent.com/nektos/act/master/install.sh -o .install_act - chmod +x .install_act - sudo bash .install_act ${this.version}""" - ) - - this.version = null - - } - - Closure installCMD = this.version ? installVersion : installLatest - - Closure cmd = result.exitCode == 0 && !this.version ? alreadyInstalled : installCMD - - this.steps.dir(this.steps.env.WORKSPACE) { - - cmd() - + /** + * Installs and then runs act. + *

The default event is push to change it to pull_request:

+ *
{@code
+     * import org.dsty.github.actions.Workflow
+     *node() {
+     *  Workflow workflow = new Workflow(this)
+     *  workflow('pull_request')
+     *}}
+ * @param args The arguments passed directly to act. + * See act docs. + * @return The output from the act command. + */ + String call(String args = '') throws ScriptError { + this.install() + + return this.run(args) } - if (!this.version) { - - String version = this.bash.call(/set -o pipefail; act --version | cut -d' ' -f3/).stdOut - this.version = "v${version}" - + /** + * Installs act by first checking if it is already installed and + * if not then installs the latest version. The property {@link #version} can be set to install + * a specific version. + */ + void install() throws ScriptError { + this.log.info('Installing Act.') + + Result result = this.bash.ignoreErrors(/set -o pipefail; act --version | cut -d' ' -f3/, false, true) + + Closure alreadyInstalled = { + this.version = "v${result.stdOut}" + + this.log.info("Version ${this.version} of act is already installed.") + } + + Closure installLatest = { + this.log.info('Downloading the latest version.') + this.bash.call('curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash') + } + + Closure installVersion = { + this.log.info("Downloading version ${this.version}.") + this.bash.call("""\ + curl https://raw.githubusercontent.com/nektos/act/master/install.sh -o .install_act + chmod +x .install_act + sudo bash .install_act ${this.version}""".stripIndent() + ) + + this.version = null + } + + Closure installCMD = this.version ? installVersion : installLatest + + Closure cmd = result.exitCode == 0 && !this.version ? alreadyInstalled : installCMD + + this.steps.dir(this.steps.env.WORKSPACE) { + cmd() + } + + if (!this.version) { + String version = this.bash.call(/set -o pipefail; act --version | cut -d' ' -f3/).stdOut + this.version = "v${version}" + } + + String homeDir = this.bash.call('echo ~').stdOut + + if (!this.steps.fileExists("${homeDir}/.actrc")) { + // We have to write out a config file to make act work headless + this.bash.call('''\ + cat <> ~/.actrc + -P ubuntu-latest=catthehacker/ubuntu:act-latest + -P ubuntu-20.04=catthehacker/ubuntu:act-20.04 + -P ubuntu-18.04=catthehacker/ubuntu:act-18.04 + EOT'''.stripIndent() + ) + } } - String homeDir = this.bash.call('echo ~').stdOut - - if (!this.steps.fileExists("${homeDir}/.actrc")) { - - // We have to write out a config file to make act work headless - this.bash.call('''\ - cat <> ~/.actrc - -P ubuntu-latest=catthehacker/ubuntu:act-latest - -P ubuntu-20.04=catthehacker/ubuntu:act-20.04 - -P ubuntu-18.04=catthehacker/ubuntu:act-18.04 - EOT'''.stripIndent() - ) + /** + * Calls the act binary with the supplied arugments. + * @param args The arguments passed directly to act. + * See act docs. + * @return The output from the act command. + */ + String run(String args) throws ScriptError { + Result result = this.bash.call("act ${args}") + return result.output } - } - - /** - * Calls the act binary with the supplied arugments. - * @param args The arguments passed directly to act. See act docs. - * @return The output from the act command. - */ - String run(String args) throws ScriptError { - - Result result = this.bash.call("act ${args}") - - return result.output - - } - } diff --git a/src/org/dsty/github/actions/package-info.groovy b/src/org/dsty/github/actions/package-info.groovy index 90358d0c8..b88f68313 100644 --- a/src/org/dsty/github/actions/package-info.groovy +++ b/src/org/dsty/github/actions/package-info.groovy @@ -1,4 +1,4 @@ /** * Run Github Actions on Jenkins. */ - package org.dsty.github.actions +package org.dsty.github.actions diff --git a/src/org/dsty/http/Requests.groovy b/src/org/dsty/http/Requests.groovy index 1218bdecd..a17b13951 100644 --- a/src/org/dsty/http/Requests.groovy +++ b/src/org/dsty/http/Requests.groovy @@ -1,4 +1,4 @@ -/* groovylint-disable DuplicateStringLiteral, Instanceof, ParameterReassignment, ThrowException, UnnecessarySetter */ +/* groovylint-disable DuplicateStringLiteral, Instanceof, ThrowException, UnnecessaryGetter, UnnecessarySetter */ package org.dsty.http import static groovy.json.JsonOutput.toJson @@ -35,12 +35,14 @@ import groovy.json.JsonSlurperClassic *
{@code
  * queryParams = ['key1': 'value1', 'key2': ['value2', 'value3']]
  *def response = Requests.get('https://httpbin.org/get', params: queryParams)
- *{@literal /}{@literal /} would create the following url https://httpbin.org/get?key1=value1&key2=value2&key2=value3}
+ *{@literal /}{@literal /} would create the following url + * https://httpbin.org/get?key1=value1&key2=value2&key2=value3} * You can also create the url string manually. *
{@code
  * String url = 'https://httpbin.org/get?key1=hello world'
  *def response = Requests.get(url)
- *{@literal /}{@literal /} would create the following url https://httpbin.org/get?key1=hello%20world}
+ *{@literal /}{@literal /} would create the + * following url https://httpbin.org/get?key1=hello%20world} * *

Headers: *

{@code
@@ -72,196 +74,198 @@ import groovy.json.JsonSlurperClassic
  */
 class Requests implements Serializable {
 
-  /**
-    * Performs an HTTP GET Request.
-    * @param options A map of additional options.
-    * @param url The url you want to target.
-    * @return An HTTP {@link org.dsty.http.Response Response}.
-    */
-  static Response get(Map options = [:], String url) {
-    return execute('GET', url, options)
-  }
-
-  /**
-    * Performs an HTTP POST Request.
-    * @param options A map of additional options.
-    * @param url The url you want to target.
-    * @return An HTTP {@link org.dsty.http.Response Response}.
-    */
-  static Response post(Map options = [:], String url) {
-    return execute('POST', url, options)
-  }
-
-  /**
-    * Performs an HTTP DELETE Request.
-    * @param options A map of additional options.
-    * @param url The url you want to target.
-    * @return An HTTP {@link org.dsty.http.Response Response}.
-    */
-  static Response delete(Map options = [:], String url) {
-    return execute('DELETE', url, options)
-  }
-
-  /**
-    * Performs an HTTP PUT Request.
-    * @param options A map of additional options.
-    * @param url The url you want to target.
-    * @return An HTTP {@link org.dsty.http.Response Response}.
-    */
-  static Response put(Map options = [:], String url) {
-    return execute('PUT', url, options)
-  }
-
-  /**
-    * Performs an HTTP Request.
-    * @param method The HTTP method you want to execute.
-    * @param url Th URL you want to target.
-    * @param options A map of additional options.
-    * @return An HTTP {@link org.dsty.http.Response Response}.
-    */
-  @NonCPS
-  static private Response execute(String method, String url, Map options) {
-    options.headers = options.headers ?: [:]
-
-    if (options.params) {
-      url = "${url}?${encodeParams(options.params)}"
+    /**
+     * Performs an HTTP GET Request.
+     * @param options A map of additional options.
+     * @param url The url you want to target.
+     * @return An HTTP {@link org.dsty.http.Response Response}.
+     */
+    static Response get(Map options = [:], String url) {
+        return execute('GET', url, options)
     }
 
-    if (options.json) {
-      options['body'] = toJson(options.json)
+    /**
+     * Performs an HTTP POST Request.
+     * @param options A map of additional options.
+     * @param url The url you want to target.
+     * @return An HTTP {@link org.dsty.http.Response Response}.
+     */
+    static Response post(Map options = [:], String url) {
+        return execute('POST', url, options)
     }
 
-    if (options.auth) {
-      options.headers['Authorization'] = configureAuth(options.auth)
+    /**
+     * Performs an HTTP DELETE Request.
+     * @param options A map of additional options.
+     * @param url The url you want to target.
+     * @return An HTTP {@link org.dsty.http.Response Response}.
+     */
+    static Response delete(Map options = [:], String url) {
+        return execute('DELETE', url, options)
     }
 
-    URL validURL = validateURL(url)
+    /**
+     * Performs an HTTP PUT Request.
+     * @param options A map of additional options.
+     * @param url The url you want to target.
+     * @return An HTTP {@link org.dsty.http.Response Response}.
+     */
+    static Response put(Map options = [:], String url) {
+        return execute('PUT', url, options)
+    }
 
-    HttpURLConnection connection = validURL.openConnection() as HttpURLConnection
+    /**
+     * Performs an HTTP Request.
+     * @param method The HTTP method you want to execute.
+     * @param url Th URL you want to target.
+     * @param options A map of additional options.
+     * @return An HTTP {@link org.dsty.http.Response Response}.
+     */
+    @NonCPS
+    static private Response execute(String method, String url, Map options) {
+        options.headers = options.headers ?: [:]
 
-    connection.setRequestMethod(method)
+        if (options.params) {
+            /* groovylint-disable-next-line ParameterReassignment */
+            url = "${url}?${encodeParams(options.params)}"
+        }
 
-    connection = options.headers ? applyHeaders(connection, options.headers) : connection
+        if (options.json) {
+            options['body'] = toJson(options.json)
+        }
 
-    if (options.body) {
-      connection.setDoOutput(true)
-      connection.getOutputStream().write(options.body.getBytes('UTF-8'))
-    }
+        if (options.auth) {
+            options.headers['Authorization'] = configureAuth(options.auth)
+        }
+
+        URL validURL = validateURL(url)
+
+        HttpURLConnection connection = validURL.openConnection() as HttpURLConnection
+
+        connection.setRequestMethod(method)
+
+        connection = options.headers ? applyHeaders(connection, options.headers) : connection
+
+        if (options.body) {
+            connection.setDoOutput(true)
+            connection.getOutputStream().write(options.body.getBytes('UTF-8'))
+        }
+
+        Response response = handleRequest(connection)
 
-    Response response = handleRequest(connection)
+        if (response.headers.find { key, value -> value == 'application/json' } ) {
+            JsonSlurperClassic slurper = new JsonSlurperClassic()
+            response.json = slurper.parseText(response.body)
+        }
 
-    if (response.headers.find { it.value == 'application/json' } ) {
-      JsonSlurperClassic slurper = new JsonSlurperClassic()
-      response.json = slurper.parseText(response.body)
+        return response
     }
 
-    return response
-  }
-
-  /**
-  * Applys a Map of Headers to an HTTP conntection.
-  * @param connection Connection you want to set headers on.
-  * @param headers The headers you want to set.
-  * @return The connection with the headers set.
-  */
-  @NonCPS
-  static private HttpURLConnection applyHeaders(HttpURLConnection connection, Map headers) {
-    for (header in headers) {
-      connection.setRequestProperty(header.key, header.value)
+    /**
+     * Applys a Map of Headers to an HTTP conntection.
+     * @param connection Connection you want to set headers on.
+     * @param headers The headers you want to set.
+     * @return The connection with the headers set.
+     */
+    @NonCPS
+    static private HttpURLConnection applyHeaders(HttpURLConnection connection, Map headers) {
+        for (header in headers) {
+            connection.setRequestProperty(header.key, header.value)
+        }
+        return connection
     }
-    return connection
-  }
-
-  /**
-  * Creates URL encoded parameters.
-  * @param params The HTTP query parameters.
-  * @return The HTTP query string.
-  */
-  @NonCPS
-  static private String encodeParams(Map params) {
-    return params.collect { it ->
-      if ( !(it.value instanceof List) && !(it.value instanceof String) ) {
-        throw new Exception('Parameter values must be String or List.')
-      }
-
-      if ( it.value instanceof List ) {
-        return it.value.collect { value ->
-          if ( !(value instanceof String) ) {
-            throw new Exception('List values must be of type String.')
-          }
-          return "${it.key}=${value}"
+
+    /**
+     * Creates URL encoded parameters.
+     * @param params The HTTP query parameters.
+     * @return The HTTP query string.
+     */
+    @NonCPS
+    static private String encodeParams(Map params) {
+        return params.collect { param ->
+            if ( !(param.value instanceof List) && !(param.value instanceof String) ) {
+                throw new Exception('Parameter values must be String or List.')
+            }
+
+            if ( param.value instanceof List ) {
+                return param.value.collect { value ->
+                    if ( !(value instanceof String) ) {
+                        throw new Exception('List values must be of type String.')
+                    }
+                    return "${param.key}=${value}"
+                }.join('&')
+            }
+
+            return param
         }.join('&')
-      }
-      return it
-    }.join('&')
-  }
-
-  /**
-  * Configure HTTP Authentication.
-  * @param auth The auth configuration.
-  * @return The correct Authorization header.
-  */
-  @NonCPS
-  static private String configureAuth(Map auth) {
-    if (auth.type == 'Basic') {
-      String creds = "${auth.username}:${auth.password}".getBytes().encodeBase64()
-      return "${auth.type} ${creds}"
     }
 
-    if (auth.type == 'Bearer') {
-      return "${auth.type} ${auth.token}"
-    }
-  }
-
-  /**
-  * Turns the raw server response into Response object.
-  * @param connection Connection that is ready to make a request.
-  * @return An HTTP {@link org.dsty.http.Response Response}.
-  */
-  @NonCPS
-  static private Response handleRequest(HttpURLConnection connection) {
-    Integer responseCode = connection.getResponseCode()
-
-    String body
-
-    if (connection.getErrorStream()) {
-      body = connection.getErrorStream().getText()
-    } else {
-      body = connection.getInputStream().getText()
+    /**
+     * Configure HTTP Authentication.
+     * @param auth The auth configuration.
+     * @return The correct Authorization header.
+     */
+    @NonCPS
+    static private String configureAuth(Map auth) {
+        if (auth.type == 'Basic') {
+            String creds = "${auth.username}:${auth.password}".getBytes().encodeBase64()
+            return "${auth.type} ${creds}"
+        }
+
+        if (auth.type == 'Bearer') {
+            return "${auth.type} ${auth.token}"
+        }
     }
 
-    String url = connection.url
+    /**
+     * Turns the raw server response into Response object.
+     * @param connection Connection that is ready to make a request.
+     * @return An HTTP {@link org.dsty.http.Response Response}.
+     */
+    @NonCPS
+    static private Response handleRequest(HttpURLConnection connection) {
+        Integer responseCode = connection.getResponseCode()
+
+        String body
+
+        if (connection.getErrorStream()) {
+            body = connection.getErrorStream().getText()
+        } else {
+            body = connection.getInputStream().getText()
+        }
 
-    Map rawHeaders = connection.getHeaderFields().collectEntries { k, v ->
-      [(k): v.join(',')]
+        String url = connection.url
+
+        Map rawHeaders = connection.getHeaderFields().collectEntries { k, v ->
+            [(k): v.join(',')]
+        }
+
+        Map headers = rawHeaders.findAll { k, v -> k != null }
+
+        return new Response(url, headers, body, responseCode)
     }
 
-    Map headers = rawHeaders.findAll { it.key != null }
-
-    return new Response(url, headers, body, responseCode)
-  }
-
-  /**
-  * Validates and encodes an HTTP URL.
-  * @param urk A URL to an HTTP server.
-  * @return A valid and properly encoded URL instance.
-  */
-  @NonCPS
-  static private URL validateURL(String url) throws java.net.URISyntaxException {
-    // best way I found to convert `https://httpbin.org/get?test=hello world`
-    // to `https://httpbin.org/get?test=hello%20world` with little effort.
-    URL tempURL = new URL(url)
-    URI uri = new URI(
-      tempURL.getProtocol(),
-      tempURL.getUserInfo(),
-      tempURL.getHost(),
-      tempURL.getPort(),
-      tempURL.getPath(),
-      tempURL.getQuery(),
-      tempURL.getRef()
-    )
-
-    return uri.toURL()
-  }
+    /**
+     * Validates and encodes an HTTP URL.
+     * @param urk A URL to an HTTP server.
+     * @return A valid and properly encoded URL instance.
+     */
+    @NonCPS
+    static private URL validateURL(String url) throws java.net.URISyntaxException {
+        // best way I found to convert `https://httpbin.org/get?test=hello world`
+        // to `https://httpbin.org/get?test=hello%20world` with little effort.
+        URL tempURL = new URL(url)
+        URI uri = new URI(
+            tempURL.getProtocol(),
+            tempURL.getUserInfo(),
+            tempURL.getHost(),
+            tempURL.getPort(),
+            tempURL.getPath(),
+            tempURL.getQuery(),
+            tempURL.getRef()
+        )
+
+        return uri.toURL()
+    }
 
 }
diff --git a/src/org/dsty/http/Response.groovy b/src/org/dsty/http/Response.groovy
index 860d4dcbf..b174023de 100644
--- a/src/org/dsty/http/Response.groovy
+++ b/src/org/dsty/http/Response.groovy
@@ -7,54 +7,54 @@ import com.cloudbees.groovy.cps.NonCPS
  */
 class Response implements Serializable {
 
-  /**
-   * The url used in the original request.
-   */
-  String url
-
-  /**
-   * The response headers.
-   */
-  Map headers
-
-  /**
-   * The raw response recieved from the server.
-   */
-  String body
-
-  /**
-   * The response status code.
-   */
-  Integer statusCode
-
-  /**
-   * Set if the reponse was valid json.
-   */
-  Map json
-
-  Response(String url, Map headers, String body, Integer statusCode) {
-    this.url = url
-    this.headers = headers
-    this.body = body
-    this.statusCode = statusCode
-  }
-
-  /**
-   * Checks if the statusCode is 2xx
-   * @return True if the statusCode is 2xx.
-   */
-  Boolean isOkay() {
-    return this.statusCode.intdiv(100) == 2
-  }
-
-  /**
-   * Prints the response body.
-   * @return The output from the bash script.
-   */
-  @Override
-  @NonCPS
-  String toString() {
-      return this.body
-  }
+    /**
+     * The url used in the original request.
+     */
+    String url
+
+    /**
+     * The response headers.
+     */
+    Map headers
+
+    /**
+     * The raw response recieved from the server.
+     */
+    String body
+
+    /**
+     * The response status code.
+     */
+    Integer statusCode
+
+    /**
+     * Set if the reponse was valid json.
+     */
+    Map json
+
+    Response(String url, Map headers, String body, Integer statusCode) {
+        this.url = url
+        this.headers = headers
+        this.body = body
+        this.statusCode = statusCode
+    }
+
+    /**
+     * Checks if the statusCode is 2xx
+     * @return True if the statusCode is 2xx.
+     */
+    Boolean isOkay() {
+        return this.statusCode.intdiv(100) == 2
+    }
+
+    /**
+     * Prints the response body.
+     * @return The output from the bash script.
+     */
+    @Override
+    @NonCPS
+    String toString() {
+        return this.body
+    }
 
 }
diff --git a/src/org/dsty/http/package-info.groovy b/src/org/dsty/http/package-info.groovy
index 6a2bc3dff..c64d7423c 100644
--- a/src/org/dsty/http/package-info.groovy
+++ b/src/org/dsty/http/package-info.groovy
@@ -1,4 +1,4 @@
 /**
  * Make HTTP requests
  */
- package org.dsty.http
+package org.dsty.http
diff --git a/src/org/dsty/jenkins/Instance.groovy b/src/org/dsty/jenkins/Instance.groovy
index 7f9f7624b..8886242b2 100644
--- a/src/org/dsty/jenkins/Instance.groovy
+++ b/src/org/dsty/jenkins/Instance.groovy
@@ -1,3 +1,4 @@
+/* groovylint-disable UnnecessaryGetter */
 package org.dsty.jenkins
 
 import com.cloudbees.groovy.cps.NonCPS
@@ -9,29 +10,29 @@ import jenkins.model.Jenkins
  */
 class Instance implements Serializable {
 
-  /**
-    * Checks if a plugin is installed.
-    * @param shortName The name of the plugin.
-    * @return True if the plugin is installed.
-    */
-  @NonCPS
-  static Boolean pluginInstalled(String shortName) {
-    List plugins = plugins()
+    /**
+     * Checks if a plugin is installed.
+     * @param shortName The name of the plugin.
+     * @return True if the plugin is installed.
+     */
+    @NonCPS
+    static Boolean pluginInstalled(String shortName) {
+        List plugins = plugins()
 
-    String plugin = plugins.find { it == shortName }
+        String plugin = plugins.find { pluginName -> pluginName == shortName }
 
-    return plugin as Boolean
-  }
+        return plugin as Boolean
+    }
 
-  /**
-    * Returns the plugins currently installed on the
-    * Jenkins. This does not check if a plugin is enabled
-    * or active in the current build.
-    * @return List of plugin shortNames/ID.
-    */
-  @NonCPS
-  static List plugins() {
-    return Jenkins.instance.pluginManager.plugins*.getShortName()
-  }
+    /**
+     * Returns the plugins currently installed on the
+     * Jenkins. This does not check if a plugin is enabled
+     * or active in the current build.
+     * @return List of plugin shortNames/ID.
+     */
+    @NonCPS
+    static List plugins() {
+        return Jenkins.instance.pluginManager.plugins*.getShortName()
+    }
 
 }
diff --git a/src/org/dsty/jenkins/package-info.groovy b/src/org/dsty/jenkins/package-info.groovy
index a0a3fa835..a02de5b0b 100644
--- a/src/org/dsty/jenkins/package-info.groovy
+++ b/src/org/dsty/jenkins/package-info.groovy
@@ -1,4 +1,4 @@
 /**
  * Get information about the Jenkins Instance.
  */
- package org.dsty.jenkins
+package org.dsty.jenkins
diff --git a/src/org/dsty/logging/LogClient.groovy b/src/org/dsty/logging/LogClient.groovy
index ec0674386..7fc9a7bef 100644
--- a/src/org/dsty/logging/LogClient.groovy
+++ b/src/org/dsty/logging/LogClient.groovy
@@ -18,131 +18,131 @@ import com.cloudbees.groovy.cps.NonCPS
  */
 class LogClient implements Serializable {
 
-  /**
-   * Workflow script representing the jenkins build.
-   */
-  private final Object steps
-
-  /**
-   * If we should print in color.
-   */
-  Boolean printColor
-
-  /**
-   * Default Constructor
-   * 

- * Example: - *

{@code
-   * import org.dsty.logging.LogClient
-   *LogClient log = new LogClient(this)
-   * }
- * @param steps The workflow script representing the jenkins build. - */ - LogClient(Object steps) { - this.steps = steps - useColor() - } - - /** - * Logs a message to the console in green. - * @param input The item you want to output to console. - */ - void debug(Object input) { - if (levelCheck(['DEBUG'])) { - writeMsg("[Debug] ${getString(input)}", '32') + /** + * Workflow script representing the jenkins build. + */ + private final Object steps + + /** + * If we should print in color. + */ + Boolean printColor + + /** + * Default Constructor + *

+ * Example: + *

{@code
+     * import org.dsty.logging.LogClient
+     *LogClient log = new LogClient(this)
+     * }
+ * @param steps The workflow script representing the jenkins build. + */ + LogClient(Object steps) { + this.steps = steps + useColor() } - } - - /** - * Logs a message to the console in blue. - * @param input The item you want to output to console. - */ - void info(Object input) { - if (levelCheck(['DEBUG', 'INFO'])) { - writeMsg("[Info] ${getString(input)}", '34') + + /** + * Logs a message to the console in green. + * @param input The item you want to output to console. + */ + void debug(Object input) { + if (levelCheck(['DEBUG'])) { + writeMsg("[Debug] ${getString(input)}", '32') + } + } + + /** + * Logs a message to the console in blue. + * @param input The item you want to output to console. + */ + void info(Object input) { + if (levelCheck(['DEBUG', 'INFO'])) { + writeMsg("[Info] ${getString(input)}", '34') + } + } + + /** + * Logs a message to the console in yellow. + * @param input The item you want to output to console. + */ + void warn(Object input) { + if (levelCheck(['DEBUG', 'INFO', 'WARN'])) { + writeMsg("[Warning] ${getString(input)}", '33') + } + } + + /** + * Logs a message to the console in red. + * @param input The item you want to output to console. + */ + void error(Object input) { + if (levelCheck(['DEBUG', 'INFO', 'WARN', 'ERROR'])) { + writeMsg("[Error] ${getString(input)}", '31') + } } - } - - /** - * Logs a message to the console in yellow. - * @param input The item you want to output to console. - */ - void warn(Object input) { - if (levelCheck(['DEBUG', 'INFO', 'WARN'])) { - writeMsg("[Warning] ${getString(input)}", '33') + + /** + * Returns a Map or List as a pretty JSON String. + *

+ * Example: + *

{@code
+     * Map test = [:]
+     *test['List'] = [1,2,3,4]
+     *log.debug(log.pprint(test))
+     * }
+ * Results: + *
{@code
+     * [Debug] {
+     * "List": [
+     *     1,
+     *     2,
+     *     3,
+     *     4
+     *  ]
+     *}
+     * }
+ * @param item The List or Map you want to format as pretty JSON. + * @return A JSON String that is pretty. + */ + String pprint(Object item) { + return prettyPrint(toJson(item)) + } + + String writeMsg(Object input, String colorCode) { + if (this.printColor) { + this.steps.ansiColor('xterm') { + this.steps.println("\u001B[${colorCode}m${input}\u001B[0m") + } + } else { + this.steps.println(input) + } } - } - - /** - * Logs a message to the console in red. - * @param input The item you want to output to console. - */ - void error(Object input) { - if (levelCheck(['DEBUG', 'INFO', 'WARN', 'ERROR'])) { - writeMsg("[Error] ${getString(input)}", '31') + + /** + * Check if the current level should be logged. + * @param levels The levels that you want to log to. + * @return true If the current level is in + * levels param and false if not. + */ + private Boolean levelCheck(List levels) { + String level = this.steps.env.PIPELINE_LOG_LEVEL ?: 'INFO' + return levels.contains(level) } - } - - /** - * Returns a Map or List as a pretty JSON String. - *

- * Example: - *

{@code
-  * Map test = [:]
-  *test['List'] = [1,2,3,4]
-  *log.debug(log.pprint(test))
-  * }
- * Results: - *
{@code
-  * [Debug] {
-  * "List": [
-  *     1,
-  *     2,
-  *     3,
-  *     4
-  *  ]
-  *}
-  * }
- * @param item The List or Map you want to format as pretty JSON. - * @return A JSON String that is pretty. - */ - String pprint(Object item) { - return prettyPrint(toJson(item)) - } - - String writeMsg(Object input, String colorCode) { - if (this.printColor) { - this.steps.ansiColor('xterm') { - this.steps.println("\u001B[${colorCode}m${input}\u001B[0m") - } - } else { - this.steps.println(input) + + /** + * Returns a string of the input object + * @param input Any object. + * @return The string version of the object. + */ + private String getString(Object input) { + return input.toString() + } + + @NonCPS + private void useColor() { + this.printColor = pluginInstalled('ansicolor') } - } - - /** - * Check if the current level should be logged. - * @param levels The levels that you want to log to. - * @return true If the current level is in - * levels param and false if not. - */ - private Boolean levelCheck(List levels) { - String level = this.steps.env.PIPELINE_LOG_LEVEL ?: 'INFO' - return levels.contains(level) - } - - /** - * Returns a string of the input object - * @param input Any object. - * @return The string version of the object. - */ - private String getString(Object input) { - return input.toString() - } - - @NonCPS - private void useColor() { - this.printColor = pluginInstalled('ansicolor') - } } diff --git a/tests/requirements.txt b/tests/requirements.txt index 5b48ce02b..c372c825c 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,18 +1,18 @@ appdirs==1.4.4 attrs==21.2.0 certifi==2020.12.5 -cfgv==3.2.0 +cfgv==3.3.0 chardet==4.0.0 -distlib==0.3.1 +distlib==0.3.2 docker==5.0.0 filelock==3.0.12 -identify==2.2.4 +identify==2.2.7 idna==2.10 iniconfig==1.1.1 nodeenv==1.6.0 packaging==20.9 pluggy==0.13.1 -pre-commit==2.12.1 +pre-commit==2.13.0 py==1.10.0 pyparsing==2.4.7 pytest==6.2.4 @@ -21,6 +21,6 @@ requests==2.25.1 semver==2.13.0 six==1.16.0 toml==0.10.2 -urllib3==1.26.4 +urllib3==1.26.5 virtualenv==20.4.6 -websocket-client==0.59.0 +websocket-client==1.0.1 diff --git a/tests/test_bash/test_bashClient.py b/tests/test_bash/test_bashClient.py index cdb18ed00..90bba8ba6 100644 --- a/tests/test_bash/test_bashClient.py +++ b/tests/test_bash/test_bashClient.py @@ -1,22 +1,15 @@ -def test_call(container): +def test_bash_example(container): - job_output = container('bash/call') + job_output = container('bash/bash_example.groovy') - assert b"TestMessage" in job_output - assert b"fakecommand: command not found" in job_output - -def test_silent(container): - - job_output = container('bash/silent') - - assert b"TestMessage" not in job_output - assert b"fakecommand: command not found" not in job_output - - -def test_ignore_errors(container): + # Test regular bash + assert b"Hello from Bash!" in job_output + assert b"RegularFakeCommand: command not found" in job_output - job_output = container('bash/ignore_errors') + # Test silent bash + assert b"NotShown!" not in job_output + # Test ignoreErrors bash assert b"fakecommand: command not found" in job_output assert b"secretcommand" not in job_output assert b"anothercommand" not in job_output diff --git a/tests/test_http/test_requests.py b/tests/test_http/test_requests.py index d37af0a8d..4dcecffd6 100644 --- a/tests/test_http/test_requests.py +++ b/tests/test_http/test_requests.py @@ -1,3 +1,3 @@ def test_requests(container): - container('http/requests') + container('http/requests_example.groovy') diff --git a/tests/test_jenkins/test_Instance.py b/tests/test_jenkins/test_Instance.py index eda16f182..693877988 100644 --- a/tests/test_jenkins/test_Instance.py +++ b/tests/test_jenkins/test_Instance.py @@ -1,3 +1,3 @@ def test_plugins(container): - container('jenkins/plugins') + container('jenkins/instance_example.groovy') diff --git a/tests/test_logging/test_LogClient.py b/tests/test_logging/test_LogClient.py index 18bf261e2..ef60dfafd 100644 --- a/tests/test_logging/test_LogClient.py +++ b/tests/test_logging/test_LogClient.py @@ -1,6 +1,6 @@ def test_levels(container): - job_output = container('logging/levels') + job_output = container('logging/logging_example.groovy') assert b"[Debug] default" not in job_output assert b"[Info] default" in job_output