diff --git a/src/test/groovy/ApmBasePipelineTest.groovy b/src/test/groovy/ApmBasePipelineTest.groovy index a886b01c4..c6ee540d5 100644 --- a/src/test/groovy/ApmBasePipelineTest.groovy +++ b/src/test/groovy/ApmBasePipelineTest.groovy @@ -39,6 +39,7 @@ class ApmBasePipelineTest extends DeclarativePipelineTest { ALLOWED('secret/observability-team/ci/temp/allowed'), BENCHMARK('secret/apm-team/ci/benchmark-cloud'), SECRET('secret'), SECRET_ALT_USERNAME('secret-alt-username'), SECRET_ALT_PASSKEY('secret-alt-passkey'), + SECRET_AZURE('secret/apm-team/ci/apm-agent-dotnet-azure'), SECRET_CODECOV('secret-codecov'), SECRET_ERROR('secretError'), SECRET_NAME('secret/team/ci/secret-name'), SECRET_NOT_VALID('secretNotValid'), SECRET_GITHUB_APP('secret/observability-team/ci/github-app'), SECRET_NPMJS('secret/apm-team/ci/elastic-observability-npmjs'), SECRET_NPMRC('secret-npmrc'), @@ -581,6 +582,9 @@ class ApmBasePipelineTest extends DeclarativePipelineTest { if(VaultSecret.SECRET_ALT_USERNAME.equals(s)){ return [data: [alt_user_key: 'username', password: 'user_password']] } + if(VaultSecret.SECRET_AZURE.equals(s)){ + return [data: [ client_id: 'client_id_1', client_secret: 'client_secret_1', subscription_id: 'subscription_id_1', tenant_id: 'tenant_id_1' ]] + } if(VaultSecret.SECRET_CODECOV.equals(s)){ return [data: [ value: 'codecov-token']] } diff --git a/src/test/groovy/WithAzureCredentialsStepTests.groovy b/src/test/groovy/WithAzureCredentialsStepTests.groovy new file mode 100644 index 000000000..eca838733 --- /dev/null +++ b/src/test/groovy/WithAzureCredentialsStepTests.groovy @@ -0,0 +1,115 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import org.junit.Before +import org.junit.Test +import static org.junit.Assert.assertTrue + +class WithAzureCredentialsStepTests extends ApmBasePipelineTest { + + @Override + @Before + void setUp() throws Exception { + super.setUp() + script = loadScript('vars/withAzureCredentials.groovy') + env.HOME = '/foo' + } + + @Test + void test_default_parameters() throws Exception { + def isOK = false + script.call { + isOK = true + } + printCallStack() + assertTrue(isOK) + assertTrue(assertMethodCallContainsPattern('dir', '/foo')) + assertTrue(assertMethodCallContainsPattern('writeFile', '"clientId": "client_id_1"')) + assertTrue(assertMethodCallContainsPattern('sh', 'rm .credentials.json')) + assertJobStatusSuccess() + } + + @Test + void test_default_parameters_on_windows() throws Exception { + helper.registerAllowedMethod('isUnix', [ ], { false }) + def isOK = false + script.call { + isOK = true + } + printCallStack() + assertTrue(isOK) + assertTrue(assertMethodCallContainsPattern('bat', 'del .credentials.json')) + assertJobStatusSuccess() + } + + @Test + void test_with_all_parameters() throws Exception { + def isOK = false + script.call(path: '/bar', credentialsFile: 'mytoken', secret: VaultSecret.SECRET_AZURE.toString()) { + isOK = true + } + printCallStack() + assertTrue(isOK) + assertTrue(assertMethodCallContainsPattern('dir', '/bar')) + assertTrue(assertMethodCallContainsPattern('writeFile', '"clientId": "client_id_1"')) + assertTrue(assertMethodCallContainsPattern('sh', 'rm mytoken')) + assertJobStatusSuccess() + } + + @Test + void test_with_body_error() throws Exception { + try { + script.call { + throw new Exception('Mock an error') + } + } catch(e){ + //NOOP + } + printCallStack() + assertTrue(assertMethodCallContainsPattern('error', 'withAzureCredentials: error')) + assertTrue(assertMethodCallContainsPattern('sh', 'rm .credentials.json')) + assertJobStatusFailure() + } + + @Test + void test_secret_error() throws Exception { + try { + script.call(secret: VaultSecret.SECRET_ERROR.toString()){ + //NOOP + } + } catch(e){ + //NOOP + } + printCallStack() + assertTrue(assertMethodCallContainsPattern('error', 'withAzureCredentials: Unable to get credentials from the vault: Error message')) + assertJobStatusFailure() + } + + @Test + void test_secret_not_found() throws Exception { + try{ + script.call(secret: 'secretNotExists'){ + //NOOP + } + } catch(e){ + //NOOP + } + printCallStack() + assertTrue(assertMethodCallContainsPattern('error', 'withAzureCredentials: was not possible to get authentication info')) + assertJobStatusFailure() + } +} diff --git a/vars/README.md b/vars/README.md index dcdcdd85f..f3eca4a52 100644 --- a/vars/README.md +++ b/vars/README.md @@ -175,14 +175,14 @@ buildKibanaDockerImage(refspec: 'PR/12345') ``` * refspec: A branch (i.e. master), or a pull request identified by the "pr/" prefix and the pull request ID. -* packageJSON: Full name of the package.json file. Defaults to `package.json` -* baseDir: Directory where to clone the Kibana repository. Defaults to`${env.BASE_DIR}/build` +* packageJSON: Full name of the package.json file. Defaults to 'package.json' +* baseDir: Directory where to clone the Kibana repository. Defaults to "${env.BASE_DIR}/build" * credentialsId: Credentials used access Github repositories. -* targetTag: Docker tag to be used in the image. Defaults to `the commit SHA`. -* dockerRegistry: Name of the Docker registry. Defaults to `docker.elastic.co` -* dockerRegistrySecret: Name of the Vault secret with the credentials for logining into the registry. Defaults to `secret/observability-team/ci/docker-registry/prod` -* dockerImageSource: Name of the source Docker image when tagging. Defaults to `${dockerRegistry}/kibana/kibana` -* dockerImageTarget: Name of the target Docker image to be tagged. Defaults to `${dockerRegistry}/observability-ci/kibana` +* targetTag: Docker tag to be used in the image. Defaults to the commit SHA. +* dockerRegistry: Name of the Docker registry. Defaults to 'docker.elastic.co' +* dockerRegistrySecret: Name of the Vault secret with the credentials for logining into the registry. Defaults to 'secret/observability-team/ci/docker-registry/prod' +* dockerImageSource: Name of the source Docker image when tagging. Defaults to '${dockerRegistry}/kibana/kibana' +* dockerImageTarget: Name of the target Docker image to be tagged. Defaults to '${dockerRegistry}/observability-ci/kibana' ## buildStatus Fetch the current build status for a given job @@ -2491,6 +2491,23 @@ withAPM(serviceName: 'apm-traces', transactionNAme: 'test') { } ``` +## withAzureCredentials +Wrap the azure credentials + +``` +withAzureCredentials() { + // block +} + +withAzureCredentials(path: '/foo', credentialsFile: '.credentials.json') { + // block +} +``` + +* path: root folder where the credentials file will be stored. (Optional). Default: ${HOME} env variable +* credentialsFile: name of the file with the credentials. (Optional). Default: .credentials.json +* secret: Name of the secret on the the vault root path. (Optional). Default: 'secret/apm-team/ci/apm-agent-dotnet-azure' + ## withEnvMask This step will define some environment variables and mask their content in the console output, it simplifies Declarative syntax @@ -2875,4 +2892,3 @@ writeVaultSecret(secret: 'secret/apm-team/ci/temp/github-comment', data: ['secre * secret: Name of the secret on the the vault root path. Mandatory * data: What's the data to be written. Mandatory - diff --git a/vars/withAzureCredentials.groovy b/vars/withAzureCredentials.groovy new file mode 100644 index 000000000..3430ce375 --- /dev/null +++ b/vars/withAzureCredentials.groovy @@ -0,0 +1,67 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + Wrap the .credentials.json token + withAzureCredentials(path: '/foo', credentialsFile: '.credentials.json') { + // block + } +*/ + +def call(Map args = [:], Closure body) { + def path = args.containsKey('path') ? args.path : env.HOME + def credentialsFile = args.containsKey('credentialsFile') ? args.credentialsFile : '.credentials.json' + def secret = args.containsKey('secret') ? args.secret : 'secret/apm-team/ci/apm-agent-dotnet-azure' + + def props = getVaultSecret(secret: secret) + if (props?.errors) { + error "withAzureCredentials: Unable to get credentials from the vault: ${props.errors.toString()}" + } + def client_id = props?.data?.client_id + def client_secret = props?.data?.client_secret + def subscription_id = props?.data?.subscription_id + def tenant_id = props?.data?.tenant_id + if (client_id == null || client_secret == null || subscription_id == null || tenant_id == null) { + error 'withAzureCredentials: was not possible to get authentication info' + } + dir(path) { + writeFile file: credentialsFile, text: """ +{ + "clientId": "${client_id}", + "clientSecret": "${client_secret}", + "subscriptionId": "${subscription_id}", + "tenantId": "${tenant_id}" +} +""" + } + try { + body() + } catch(err) { + error("withAzureCredentials: error ${err.toString()}") + } finally { + // ensure any sensitive details are deleted + dir(path) { + if (fileExists("${credentialsFile}")) { + if(isUnix()){ + sh "rm ${credentialsFile}" + } else { + bat "del ${credentialsFile}" + } + } + } + } +} diff --git a/vars/withAzureCredentials.txt b/vars/withAzureCredentials.txt new file mode 100644 index 000000000..cf955da32 --- /dev/null +++ b/vars/withAzureCredentials.txt @@ -0,0 +1,15 @@ +Wrap the azure credentials + +``` +withAzureCredentials() { + // block +} + +withAzureCredentials(path: '/foo', credentialsFile: '.credentials.json') { + // block +} +``` + +* path: root folder where the credentials file will be stored. (Optional). Default: ${HOME} env variable +* credentialsFile: name of the file with the credentials. (Optional). Default: .credentials.json +* secret: Name of the secret on the the vault root path. (Optional). Default: 'secret/apm-team/ci/apm-agent-dotnet-azure'