diff --git a/README.md b/README.md index c0f85e0..bf33645 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ The class has a variety of knobs to tweak when interacting with GitHub. | config.https | Boolean | false | Is the Screwdriver API running over HTTPS | | config.oauthClientId | String | | OAuth Client ID provided by GitHub application | | config.oauthClientSecret | String | | OAuth Client Secret provided by GitHub application | +| config.readOnly | Object | {} | Config with readOnly info: enabled, username, accessToken, cloneType | | config.fusebox | Object | {} | [Circuit Breaker configuration][circuitbreaker] | | config.secret | String | | Secret to validate the signature of webhook events | | config.privateRepo | Boolean | false | Request 'repo' scope, which allows read/write access for public & private repos diff --git a/index.js b/index.js index 010c1b0..3d72d36 100644 --- a/index.js +++ b/index.js @@ -120,6 +120,7 @@ class GithubScm extends Scm { * @param {String} [config.gheProtocol=https] If using GitHub Enterprise, the protocol to use * @param {String} [config.username=sd-buildbot] GitHub username for checkout * @param {String} [config.email=dev-null@screwdriver.cd] GitHub user email for checkout + * @param {Object} [options.readOnly={}] Read-only SCM instance config with: enabled, username, accessToken, cloneType * @param {Boolean} [config.https=false] Is the Screwdriver API running over HTTPS * @param {String} config.oauthClientId OAuth Client ID provided by GitHub application * @param {String} config.oauthClientSecret OAuth Client Secret provided by GitHub application @@ -139,6 +140,12 @@ class GithubScm extends Scm { email: joi.string().optional().default('dev-null@screwdriver.cd'), commentUserToken: joi.string().optional().description('Token for PR comments'), autoDeployKeyGeneration: joi.boolean().optional().default(false), + readOnly: joi.object().keys({ + enabled: joi.boolean().optional(), + username: joi.string().optional(), + accessToken: joi.string().optional(), + cloneType: joi.string().valid('https', 'ssh').optional().default('https') + }).optional().default({}), https: joi.boolean().optional().default(false), oauthClientId: joi.string().required(), oauthClientSecret: joi.string().required(), @@ -555,11 +562,22 @@ class GithubScm extends Scm { // Export environment variables command.push('echo Exporting environment variables'); - command.push('if [ ! -z $SCM_CLONE_TYPE ] && [ $SCM_CLONE_TYPE = ssh ]; ' + - `then export SCM_URL=${sshCheckoutUrl}; ` + - 'elif [ ! -z $SCM_USERNAME ] && [ ! -z $SCM_ACCESS_TOKEN ]; ' + - `then export SCM_URL=https://$SCM_USERNAME:$SCM_ACCESS_TOKEN@${checkoutUrl}; ` + - `else export SCM_URL=https://${checkoutUrl}; fi`); + // Use read-only clone type + if (hoek.reach(this.config, 'readOnly.enabled')) { + if (hoek.reach(this.config, 'readOnly.cloneType') === 'ssh') { + command.push(`export SCM_URL=${sshCheckoutUrl}`); + } else { + command.push('if [ ! -z $SCM_USERNAME ] && [ ! -z $SCM_ACCESS_TOKEN ]; ' + + `then export SCM_URL=https://$SCM_USERNAME:$SCM_ACCESS_TOKEN@${checkoutUrl}; ` + + `else export SCM_URL=https://${checkoutUrl}; fi`); + } + } else { + command.push('if [ ! -z $SCM_CLONE_TYPE ] && [ $SCM_CLONE_TYPE = ssh ]; ' + + `then export SCM_URL=${sshCheckoutUrl}; ` + + 'elif [ ! -z $SCM_USERNAME ] && [ ! -z $SCM_ACCESS_TOKEN ]; ' + + `then export SCM_URL=https://$SCM_USERNAME:$SCM_ACCESS_TOKEN@${checkoutUrl}; ` + + `else export SCM_URL=https://${checkoutUrl}; fi`); + } command.push('export GIT_URL=$SCM_URL.git'); // git 1.7.1 doesn't support --no-edit with merge, this should do same thing command.push('export GIT_MERGE_AUTOEDIT=no'); diff --git a/package.json b/package.json index a880ba5..f829058 100644 --- a/package.json +++ b/package.json @@ -35,22 +35,22 @@ "chai": "^3.5.0", "eslint": "^4.19.1", "eslint-config-screwdriver": "^3.0.1", - "mocha": "^8.2.1", + "mocha": "^8.4.0", "mocha-multi-reporters": "^1.5.1", "mocha-sonarqube-reporter": "^1.0.2", - "nyc": "^15.0.0", "mockery": "^2.0.0", + "nyc": "^15.0.0", "sinon": "^7.5.0" }, "dependencies": { - "@hapi/hoek": "^9.1.1", - "@octokit/rest": "^18.5.2", + "@hapi/hoek": "^9.2.0", + "@octokit/rest": "^18.6.2", "@octokit/webhooks": "^7.24.3", - "circuit-fuses": "^4.0.5", + "circuit-fuses": "^4.0.6", "joi": "^17.4.0", "screwdriver-data-schema": "^21.3.0", "screwdriver-logger": "^1.0.2", - "screwdriver-scm-base": "^7.1.3", + "screwdriver-scm-base": "^7.2.1", "ssh-keygen": "^0.5.0" }, "release": { diff --git a/test/data/readOnlyCommandsHttps.json b/test/data/readOnlyCommandsHttps.json new file mode 100644 index 0000000..a0b0984 --- /dev/null +++ b/test/data/readOnlyCommandsHttps.json @@ -0,0 +1,4 @@ +{ + "name": "sd-checkout-code", + "command": "export SD_GIT_WRAPPER=\"$(if [ `uname` = 'Darwin' ]; then echo 'eval'; else echo 'sd-step exec core/git'; fi)\" && if [ ! -z $SD_SCM_DEPLOY_KEY ]; then export SCM_CLONE_TYPE=ssh; fi && echo Exporting environment variables && if [ ! -z $SCM_USERNAME ] && [ ! -z $SCM_ACCESS_TOKEN ]; then export SCM_URL=https://$SCM_USERNAME:$SCM_ACCESS_TOKEN@github.com/screwdriver-cd/guide; else export SCM_URL=https://github.com/screwdriver-cd/guide; fi && export GIT_URL=$SCM_URL.git && export GIT_MERGE_AUTOEDIT=no && if [ ! -z $SD_SCM_DEPLOY_KEY ] && [ $SCM_CLONE_TYPE = ssh ]; then echo $SD_SCM_DEPLOY_KEY | base64 -d > /tmp/git_key && echo \"\" >> /tmp/git_key && chmod 600 /tmp/git_key && export GIT_SSH_COMMAND=\"ssh -i /tmp/git_key\" && mkdir -p ~/.ssh/ && printf \"%s\n\" \"CiAgICAgICAgSG9zdCBnaXRodWIuY29tCiAgICAgICAgICAgIFN0cmljdEhvc3RLZXlDaGVja2luZyBubwogICAgICAgIA==\" | base64 -d >> ~/.ssh/config; fi && echo Setting user name and user email && $SD_GIT_WRAPPER \"git config --global user.name sd-buildbot\" && $SD_GIT_WRAPPER \"git config --global user.email dev-null@screwdriver.cd\" && export SD_CHECKOUT_DIR_FINAL=$SD_SOURCE_DIR && if [ ! -z $SD_CHECKOUT_DIR ]; then export SD_CHECKOUT_DIR_FINAL=$SD_CHECKOUT_DIR; fi && echo 'Cloning github.com/screwdriver-cd/guide, on branch branchName' && if [ ! -z $GIT_SHALLOW_CLONE ] && [ $GIT_SHALLOW_CLONE = false ]; then $SD_GIT_WRAPPER \"git clone --recursive --quiet --progress --branch 'branchName' $SCM_URL $SD_CHECKOUT_DIR_FINAL\"; else if [ ! -z \"$GIT_SHALLOW_CLONE_SINCE\" ]; then export GIT_SHALLOW_CLONE_DEPTH_OPTION=\"--shallow-since='$GIT_SHALLOW_CLONE_SINCE'\"; else if [ -z $GIT_SHALLOW_CLONE_DEPTH ]; then export GIT_SHALLOW_CLONE_DEPTH=50; fi; export GIT_SHALLOW_CLONE_DEPTH_OPTION=\"--depth=$GIT_SHALLOW_CLONE_DEPTH\"; fi; export GIT_SHALLOW_CLONE_BRANCH=\"--no-single-branch\"; if [ \"$GIT_SHALLOW_CLONE_SINGLE_BRANCH\" = true ]; then export GIT_SHALLOW_CLONE_BRANCH=\"\"; fi; $SD_GIT_WRAPPER \"git clone $GIT_SHALLOW_CLONE_DEPTH_OPTION $GIT_SHALLOW_CLONE_BRANCH --recursive --quiet --progress --branch 'branchName' $SCM_URL $SD_CHECKOUT_DIR_FINAL\"; fi && $SD_GIT_WRAPPER \"git reset --hard '12345' --\" && echo 'Reset to 12345' && export GIT_BRANCH='origin/branchName' && $SD_GIT_WRAPPER \"git submodule init\" && $SD_GIT_WRAPPER \"git submodule update --recursive\"" +} diff --git a/test/data/readOnlyCommandsSsh.json b/test/data/readOnlyCommandsSsh.json new file mode 100644 index 0000000..8fc8d2b --- /dev/null +++ b/test/data/readOnlyCommandsSsh.json @@ -0,0 +1,4 @@ +{ + "name": "sd-checkout-code", + "command": "export SD_GIT_WRAPPER=\"$(if [ `uname` = 'Darwin' ]; then echo 'eval'; else echo 'sd-step exec core/git'; fi)\" && if [ ! -z $SD_SCM_DEPLOY_KEY ]; then export SCM_CLONE_TYPE=ssh; fi && echo Exporting environment variables && export SCM_URL=git@github.com:screwdriver-cd/guide && export GIT_URL=$SCM_URL.git && export GIT_MERGE_AUTOEDIT=no && if [ ! -z $SD_SCM_DEPLOY_KEY ] && [ $SCM_CLONE_TYPE = ssh ]; then echo $SD_SCM_DEPLOY_KEY | base64 -d > /tmp/git_key && echo \"\" >> /tmp/git_key && chmod 600 /tmp/git_key && export GIT_SSH_COMMAND=\"ssh -i /tmp/git_key\" && mkdir -p ~/.ssh/ && printf \"%s\n\" \"CiAgICAgICAgSG9zdCBnaXRodWIuY29tCiAgICAgICAgICAgIFN0cmljdEhvc3RLZXlDaGVja2luZyBubwogICAgICAgIA==\" | base64 -d >> ~/.ssh/config; fi && echo Setting user name and user email && $SD_GIT_WRAPPER \"git config --global user.name sd-buildbot\" && $SD_GIT_WRAPPER \"git config --global user.email dev-null@screwdriver.cd\" && export SD_CHECKOUT_DIR_FINAL=$SD_SOURCE_DIR && if [ ! -z $SD_CHECKOUT_DIR ]; then export SD_CHECKOUT_DIR_FINAL=$SD_CHECKOUT_DIR; fi && echo 'Cloning github.com/screwdriver-cd/guide, on branch branchName' && if [ ! -z $GIT_SHALLOW_CLONE ] && [ $GIT_SHALLOW_CLONE = false ]; then $SD_GIT_WRAPPER \"git clone --recursive --quiet --progress --branch 'branchName' $SCM_URL $SD_CHECKOUT_DIR_FINAL\"; else if [ ! -z \"$GIT_SHALLOW_CLONE_SINCE\" ]; then export GIT_SHALLOW_CLONE_DEPTH_OPTION=\"--shallow-since='$GIT_SHALLOW_CLONE_SINCE'\"; else if [ -z $GIT_SHALLOW_CLONE_DEPTH ]; then export GIT_SHALLOW_CLONE_DEPTH=50; fi; export GIT_SHALLOW_CLONE_DEPTH_OPTION=\"--depth=$GIT_SHALLOW_CLONE_DEPTH\"; fi; export GIT_SHALLOW_CLONE_BRANCH=\"--no-single-branch\"; if [ \"$GIT_SHALLOW_CLONE_SINGLE_BRANCH\" = true ]; then export GIT_SHALLOW_CLONE_BRANCH=\"\"; fi; $SD_GIT_WRAPPER \"git clone $GIT_SHALLOW_CLONE_DEPTH_OPTION $GIT_SHALLOW_CLONE_BRANCH --recursive --quiet --progress --branch 'branchName' $SCM_URL $SD_CHECKOUT_DIR_FINAL\"; fi && $SD_GIT_WRAPPER \"git reset --hard '12345' --\" && echo 'Reset to 12345' && export GIT_BRANCH='origin/branchName' && $SD_GIT_WRAPPER \"git submodule init\" && $SD_GIT_WRAPPER \"git submodule update --recursive\"" +} diff --git a/test/index.test.js b/test/index.test.js index 4f2df58..f8f4692 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -19,6 +19,8 @@ const testPayloadBadAction = require('./data/github.pull_request.badAction.json' const testPayloadPing = require('./data/github.ping.json'); const testPayloadPingBadSshHost = require('./data/github.ping.badSshHost.json'); const testCommands = require('./data/commands.json'); +const testReadOnlyCommandsSsh = require('./data/readOnlyCommandsSsh.json'); +const testReadOnlyCommandsHttps = require('./data/readOnlyCommandsHttps.json'); const testPrCommands = require('./data/prCommands.json'); const testForkPrCommands = require('./data/forkPrCommands.json'); const testCustomPrCommands = require('./data/customPrCommands.json'); @@ -110,6 +112,7 @@ describe('index', function () { minTimeout: 1 } }, + readOnly: {}, oauthClientId: 'abcdefg', oauthClientSecret: 'hijklmno', secret: 'somesecret', @@ -208,6 +211,42 @@ describe('index', function () { }) ); + it('gets the checkout command with https clone type when read-only is enabled', () => { + scm = new GithubScm({ + oauthClientId: 'abcdefg', + oauthClientSecret: 'hijklmno', + gheHost: 'github.screwdriver.cd', + secret: 'somesecret', + readOnly: { + enabled: true, + cloneType: 'https' + } + }); + + return scm.getCheckoutCommand(config) + .then((command) => { + assert.deepEqual(command, testReadOnlyCommandsHttps); + }); + }); + + it('gets the checkout command with ssh clone type when read-only is enabled', () => { + scm = new GithubScm({ + oauthClientId: 'abcdefg', + oauthClientSecret: 'hijklmno', + gheHost: 'github.screwdriver.cd', + secret: 'somesecret', + readOnly: { + enabled: true, + cloneType: 'ssh' + } + }); + + return scm.getCheckoutCommand(config) + .then((command) => { + assert.deepEqual(command, testReadOnlyCommandsSsh); + }); + }); + it('promises to get the checkout command for a pull request', () => { config.prRef = 'pull/3/merge';