Add appengine-deploy action and refactor setup-gcloud

initial

Add test app and tests

add license

Update ReadMe and runner

Fix lint errors

Missed fixes

Update tests to run integration on PR

rename pkg -> setupGloudSDK

Update ts congig

Remove project id requirement

Update gcloud set project id func

Add unit tests for pkg

Add set project to action

rebuild

Fix gcloud typo

lint pkg and update project id dependency

Update tmp JSON writer

Add cleanup to integration tests

Update get input validation

fix json string

Fix test order and version name

add project id to test

remove clean up step

Update project url

Add no promote flag

test stderr vs stdout

Update pkg build

refactor setup-gcloud

clean up

Fix tool command for isAuthenticated

debugging

test command

Fix isInstalled method

Clean up
This commit is contained in:
averikitsch 2020-03-10 17:22:21 -07:00
parent 2c7422e8e9
commit d153f05d28
42 changed files with 38575 additions and 6601 deletions

View file

@ -1,5 +1,5 @@
name: appengine-deploy Integration
on: [push]
on: [push, pull_request]
jobs:
gcloud:
name: with setup-gcloud
@ -8,44 +8,53 @@ jobs:
- uses: actions/checkout@v2
- uses: ./setup-gcloud
with:
service_account_email: ${{ secrets.GET_SECRETMANAGER_SECRETS_SA_EMAIL }}
service_account_key: ${{ secrets.GET_SECRETMANAGER_SECRETS_SA_KEY_B64 }}
export_default_credentials: true
service_account_key: ${{ secrets.APPENGINE_DEPLOY_SA_KEY_JSON }}
- id: deploy
name: Deploy to App Engine
uses: ./appengine-deploy
with:
# secrets: |-
# token:${{ secrets.GET_SECRETMANAGER_SECRETS_SECRET_REF }}
# password:${{ secrets.GET_SECRETMANAGER_SECRETS_SECRET_VERSION_REF }}
- name: get output
# run: echo '${{ steps.secrets.outputs.token }}${{ steps.secrets.outputs.password }}'
project_id: ${{ secrets.APPENGINE_DEPLOY_PROJECT_ID }}
deliverables: ./appengine-deploy/test/app.yaml
version: gcloud
- name: Test Output
run: curl '${{ steps.deploy.outputs.url }}'
b64_json:
needs: gcloud
name: with base64 json creds
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- id: deploy
name: Deploy to App Engine
uses: ./appengine-deploy
with:
credentials: ${{ secrets.GET_SECRETMANAGER_SECRETS_SA_KEY_JSON }}
# secrets: |-
# token:${{ secrets.GET_SECRETMANAGER_SECRETS_SECRET_REF }}
# password:${{ secrets.GET_SECRETMANAGER_SECRETS_SECRET_VERSION_REF }}
- name: get output
# run: echo '${{ steps.secrets.outputs.token }}${{ steps.secrets.outputs.password }}'
credentials: ${{ secrets.APPENGINE_DEPLOY_SA_KEY_B64 }}
deliverables: ./appengine-deploy/test/app.yaml
version: b64-json
promote: false # Allows for deletion
- name: Test Output
run: curl '${{ steps.deploy.outputs.url }}'
- name: Clean Up
run: |
gcloud app versions delete b64-json --quiet
json:
needs: [gcloud, b64_json]
name: with json creds
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- id: deploy
name: Deploy to App Engine
uses: ./appengine-deploy
with:
credentials: ${{ secrets.GET_SECRETMANAGER_SECRETS_SA_KEY_JSON }}
# secrets: |-
# token:${{ secrets.GET_SECRETMANAGER_SECRETS_SECRET_REF }}
# password:${{ secrets.GET_SECRETMANAGER_SECRETS_SECRET_VERSION_REF }}
- name: get output
# run: echo '${{ steps.secrets.outputs.token }}${{ steps.secrets.outputs.password }}'
credentials: ${{ secrets.APPENGINE_DEPLOY_SA_KEY_JSON }}
deliverables: ./appengine-deploy/test/app.yaml
version: json
promote: false # Allows for deletion
- name: Test Output
run: curl '${{ steps.deploy.outputs.url }}'
- name: Clean Up
run: |
gcloud app versions delete json --quiet

View file

@ -21,10 +21,3 @@ jobs:
- name: npm lint
run: npm run lint
working-directory: ./appengine-deploy
- name: npm test
run: npm run test
working-directory: ./appengine-deploy
env:
TEST_GET_SECRETMANAGER_SECRETS_CREDENTIALS: ${{ secrets.GET_SECRETMANAGER_SECRETS_SA_KEY_JSON }}
TEST_GET_SECRETMANAGER_SECRETS_SECRET_VERSION_REF: ${{ secrets.GET_SECRETMANAGER_SECRETS_SECRET_VERSION_REF }}

View file

@ -25,7 +25,6 @@ jobs:
uses: ./setup-gcloud/
with:
version: '286.0.0'
service_account_email: ${{ secrets.SETUP_GCLOUD_IT_EMAIL }}
service_account_key: ${{ secrets.SETUP_GCLOUD_IT_KEY }}
- name: Integration Tests
@ -46,7 +45,6 @@ jobs:
uses: ./setup-gcloud/
with:
version: 'latest'
service_account_email: ${{ secrets.SETUP_GCLOUD_IT_EMAIL }}
service_account_key: ${{ secrets.SETUP_GCLOUD_IT_KEY }}
- name: Integration Tests
@ -67,7 +65,6 @@ jobs:
uses: ./setup-gcloud/
with:
version: 'latest'
service_account_email: ${{ secrets.SETUP_GCLOUD_IT_EMAIL }}
service_account_key: ${{ secrets.SETUP_GCLOUD_IT_KEY }}
export_default_credentials: true

View file

@ -31,7 +31,3 @@ jobs:
- name: Lint
working-directory: ./setup-gcloud
run: npm run lint
- name: Unit tests
working-directory: ./setup-gcloud
run: npm test

28
.github/workflows/setupGcloudSDK.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: setupGcloudSDK Unit
on: [push, pull_request]
jobs:
run:
name: setupGcloudSDK
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- name: Set Node.js 10.x
uses: actions/setup-node@master
with:
node-version: 10.x
- name: npm install
working-directory: ./setupGcloudSDK
run: npm install
- name: Lint
working-directory: ./setupGcloudSDK
run: npm run lint
- name: Unit tests
working-directory: ./setupGcloudSDK
run: npm test

View file

@ -1,19 +1,137 @@
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
# appengine-deploy
This action deploys your source code to App Engine and makes the URL available to
later build steps via outputs. This allows you to parameterize your App Engine
deployments.
What does it do:
**Note** This action will install gcloud in the background if not using in with
the [`setup-gcloud` action](../setup-gclou/README.md).
## Prerequisites
## inputs
This action requires Google Cloud credentials that are authorized to deploy an
App Engine Application. See the Authorization section below for more information.
## outputs
## Usage
## Example Usage
```yaml
steps:
- id: deploy
uses: GoogleCloudPlatform/github-actions/appengine-deploy@master
with:
credentials: ${{ secrets.gcp_credentials }}
Used with setup-gcloud
or add Credentials
# Example of using the output
- id: test
run: curl "${{ steps.deploy.outputs.url }}"
```
## Inputs
- `project_id`: (Optional) ID of the Google Cloud project. If provided, this
will override the project configured by gcloud.
- `deliverables`: (Optional) The [yaml files](https://cloud.google.com/appengine/docs/standard/nodejs/configuration-files#optional_configuration_files)
for the services or configurations you want to deploy. If not given, defaults
to app.yaml in the current directory. If that is not found, attempts to
automatically generate necessary configuration files (such as app.yaml) in
the current directory.
- `image-url`: (Optional) Deploy with a specific container image. The image url
must be from one of the valid GCR hostnames (example, `gcr.io/`).
- `version`: (Optional) The version of the app that will be created or replaced
by this deployment. If you do not specify a version, one will be generated for
you.
- `promote`: (Optional) Promote the deployed version to receive all traffic.
### app.yaml customizations
Other application configurations can be customized through the app.yaml, ie the
service name. See [app.yaml Configuration File](https://cloud.google.com/appengine/docs/standard/nodejs/config/appref)
for more information.
## Outputs
- `url`: The URL of your App Engine Application.
## Authorization
There are a few ways to authenticate this action. The caller must have
permissions to access the secrets being requested.
Roles needed:
- Storage Admin (`roles/compute.storageAdmin`):to upload files
- Cloud Build Editor (`cloudbuild.builds.editor`): to build the application
- App Engine roles:
- Deployer (`roles/appengine.deployer`): Can deploy but can not promote
- Admin (`roles/appengine.appAdmin`): Can manage all App Engine resources (not recommended)
*Note:* An owner will be needed to create the App Engine application
### Used with setup-gcloud
You can provide credentials using the [setup-gcloud][setup-gcloud] action,
however you must provide your Project ID to the `appeninge-deploy` action:
```yaml
- uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
with:
export_default_credentials: true
- uses: GoogleCloudPlatform/github-actions/appengine-deploy@master
with:
project_id: ${{ secrets.project_id }}
```
### Via Credentials
You can provide [Google Cloud Service Account JSON][sa] directly to the action
by specifying the `credentials` input. First, create a [GitHub
Secret][gh-secret] that contains the JSON content, then import it into the
action:
```yaml
- id: secrets
uses: GoogleCloudPlatform/github-actions/appengine-deploy@master
with:
credentials: ${{ secrets.gcp_credentials }}
```
### Via Application Default Credentials
If you are hosting your own runners, **and** those runners are on Google Cloud,
you can leverage the Application Default Credentials of the instance. This will
authenticate requests as the service account attached to the instance. **This
only works using a custom runner hosted on GCP.**
```yaml
- id: secrets
uses: GoogleCloudPlatform/github-actions/appengine-deploy@master
```
The action will automatically detect and use the Application Default
Credentials.
[sm]: https://cloud.google.com/secret-manager
[sa]: https://cloud.google.com/iam/docs/creating-managing-service-accounts
[gh-runners]: https://help.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners
[gh-secret]: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets
[setup-gcloud]: ../setup-gcloud

View file

@ -1,17 +1,30 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: 'Deploy to Google App Engine'
description: 'Deploy to Google App Engine'
author: 'GoogleCloudPlatform'
inputs:
project_id:
description: 'The GCP project ID'
description: 'The GCP project ID. Overrides project ID set by credentials.'
require: true
deliverables:
description: |-
The yaml files for the services or configurations you want to deploy.
If not given, defaults to app.yaml in the current directory. If that is
not found, attempts to automatically generate necessary configuration
files (such as app.yaml) in the current directory.'
files (such as app.yaml) in the current directory.
required: false
image_url:
description: |-
@ -22,7 +35,7 @@ inputs:
description: |-
The version of the app that will be created or replaced by this
deployment. If you do not specify a version, one will be generated for
you.'
you.
required: false
promote:
description: 'Promote the deployed version to receive all traffic.'

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,14 @@
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.2.tgz",
"integrity": "sha512-IbCx7oefq+Gi6FWbSs2Fnw8VkEI6Y4gvjrYprY3RV//ksq/KPMlClOerJ4jRosyal6zkUIc8R9fS/cpRMlGClg=="
},
"@actions/exec": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
"requires": {
"@actions/io": "^1.0.1"
}
},
"@actions/github": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-2.1.1.tgz",
@ -27,6 +35,31 @@
"tunnel": "0.0.6"
}
},
"@actions/io": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
},
"@actions/tool-cache": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.1.tgz",
"integrity": "sha512-sKoEJv0/c7WzjPEq2PO12Sc8QdEp58XIBHMm3c4lUn/iZWgLz9HBeCuFGpLQjDvXJNfLZ4g+WD+rMjgOmpH4Ag==",
"requires": {
"@actions/core": "^1.2.0",
"@actions/exec": "^1.0.0",
"@actions/http-client": "^1.0.3",
"@actions/io": "^1.0.1",
"semver": "^6.1.0",
"uuid": "^3.3.2"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
"@babel/code-frame": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
@ -200,6 +233,11 @@
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
"dev": true
},
"@types/js-base64": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@types/js-base64/-/js-base64-2.3.1.tgz",
"integrity": "sha512-4RKbhIDGC87s4EBy2Cp2/5S2O6kmCRcZnD5KRCq1q9z2GhBte1+BdsfVKCpG8yKpDGNyEE2G6IqFIh6W2YwWPA=="
},
"@types/json-schema": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
@ -223,6 +261,11 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
"@types/tmp": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA=="
},
"@typescript-eslint/eslint-plugin": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.21.0.tgz",
@ -387,8 +430,7 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"before-after-hook": {
"version": "2.1.0",
@ -405,7 +447,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -588,8 +629,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"cosmiconfig": {
"version": "6.0.0",
@ -906,6 +946,17 @@
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
},
"dependencies": {
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
}
}
}
},
"fast-deep-equal": {
@ -1007,8 +1058,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "2.1.2",
@ -1059,7 +1109,6 @@
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -1225,7 +1274,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@ -1234,8 +1282,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"inquirer": {
"version": "7.0.4",
@ -1365,6 +1412,11 @@
"resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
"integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA=="
},
"js-base64": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
"integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -1475,7 +1527,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -1809,8 +1860,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-key": {
"version": "2.0.1",
@ -1948,7 +1998,6 @@
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
@ -2194,12 +2243,11 @@
"dev": true
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
"requires": {
"os-tmpdir": "~1.0.2"
"rimraf": "^2.6.3"
}
},
"to-regex-range": {
@ -2274,9 +2322,9 @@
"dev": true
},
"typescript": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.2.tgz",
"integrity": "sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==",
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
"dev": true
},
"universal-user-agent": {
@ -2296,6 +2344,11 @@
"punycode": "^2.1.0"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"v8-compile-cache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",

View file

@ -6,7 +6,7 @@
"scripts": {
"build": "ncc build src/main.ts",
"lint": "eslint . --ext .ts,.tsx",
"test": "mocha -r ts-node/register 'tests/*.test.ts'"
"format": "prettier --write **/*.ts"
},
"repository": {
"type": "git",
@ -22,8 +22,14 @@
"license": "Apache-2.0",
"dependencies": {
"@actions/core": "^1.2.2",
"@actions/exec": "^1.0.3",
"@actions/github": "^2.1.1",
"child_process": "^1.0.2"
"@actions/tool-cache": "^1.3.1",
"@types/js-base64": "^2.3.1",
"@types/tmp": "^0.1.0",
"child_process": "^1.0.2",
"js-base64": "^2.5.2",
"tmp": "^0.1.0"
},
"devDependencies": {
"@types/chai": "^4.2.x",

View file

@ -15,37 +15,91 @@
*/
import * as core from '@actions/core';
import { execSync } from 'child_process';
/**
* Executes the main action. It includes the main business logic and is the
* primary entry point. It is documented inline.
*/
import * as exec from '@actions/exec';
import * as setupGcloud from '../../setupGcloudSDK/dist/index';
async function run(): Promise<void> {
try {
// Get action inputs.
const projectId = core.getInput('project_id', { required: true });
let projectId = core.getInput('project_id');
const deliverables = core.getInput('deliverables');
const imageUrl = core.getInput('image-url');
const version = core.getInput('version');
const promote = core.getInput('promote');
const serviceAccountKey = core.getInput('credentials');
// Get credentials, if any.
const credentials = core.getInput('credentials');
// Install gcloud if not already installed.
if (!setupGcloud.isInstalled()) {
const gcloudVersion = await setupGcloud.getLatestGcloudSDKVersion();
await setupGcloud.installGcloudSDK(gcloudVersion);
}
// gcloud Flag set up.
const addImageUrl = imageUrl ? `--image-url=${imageUrl}` : '';
const addVersion = version ? `--version=${version}` : '';
const addPromote = promote ? '--promote' : '--no-promote';
// Fail if no Project Id is provided.
if (projectId === '' && serviceAccountKey === '') {
core.setFailed('No project Id provided.');
}
// Run gcloud
execSync(`gcloud app deploy ${deliverables} --project=${projectId} \
${addImageUrl} \
${addVersion} \
${addPromote}`);
// Authenticate gcloud SDK.
if (serviceAccountKey) {
await setupGcloud.authenticateGcloudSDK(serviceAccountKey);
// Set and retrieve Project Id if not provided
if (projectId === '') {
projectId = await setupGcloud.setProject(serviceAccountKey);
}
}
if (!setupGcloud.isAuthenticated()) {
core.setFailed('Error authenticating the Cloud SDK.');
}
// Set output url.
// Note: Any implications of just setting this??
core.setOutput('url', `https://${projectId}.appspot.com/`);
const toolCommand = setupGcloud.getToolCommand();
// Create app engine gcloud cmd.
const appDeployCmd = ['app', 'deploy', '--quiet', deliverables];
// Add gcloud flags.
if (projectId !== '') {
appDeployCmd.push('--project', projectId);
}
if (imageUrl !== '') {
appDeployCmd.push('--image-url', imageUrl);
}
if (version !== '') {
appDeployCmd.push('--version', version);
}
if (promote === '' || String(promote).toLowerCase() === 'true') {
appDeployCmd.push('--promote');
} else {
appDeployCmd.push('--no-promote');
}
// Get output of gcloud cmd.
let output = '';
const stdout = (data: Buffer): void => {
output += data.toString();
};
const options = {
listeners: {
stderr: stdout,
},
};
// Run gcloud cmd.
await exec.exec(toolCommand, appDeployCmd, options);
// Set url as output.
const urlMatch = output.match(
/https:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.com/,
);
if (urlMatch) {
const url = urlMatch[0];
core.setOutput('url', url);
} else {
core.info(
'Can not find URL, defaulting to https://PROJECT_ID.appspot.com',
);
core.setOutput('url', `https://${projectId}.appspot.com`);
}
} catch (error) {
core.setFailed(error.message);
}

View file

@ -0,0 +1 @@
runtime: python37

View file

@ -0,0 +1,35 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# [START gae_python37_app]
from flask import Flask
# If `entrypoint` is not defined in app.yaml, App Engine will look for an app
# called `app` in `main.py`.
app = Flask(__name__)
@app.route('/')
def hello():
"""Return a friendly HTTP greeting."""
return 'Hello World!'
if __name__ == '__main__':
# This is used when running locally only. When deploying to Google App
# Engine, a webserver process such as Gunicorn will serve the app. This
# can be configured by adding an `entrypoint` to app.yaml.
app.run(host='127.0.0.1', port=8080, debug=True)
# [END gae_python37_app]

View file

@ -0,0 +1 @@
Flask==1.1.1

View file

@ -24,7 +24,12 @@
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true
"esModuleInterop": true,
},
"references": [
{
"path": "../setupGcloudSDK"
}
],
"exclude": ["node_modules", "**/*.test.ts"]
}

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,7 @@
"scripts": {
"build": "ncc build src/setup-gcloud.ts",
"lint": "eslint . --ext .ts,.tsx",
"format": "prettier --write **/*.ts",
"test": "mocha -r ts-node/register -t 120s 'tests/*.test.ts'"
"format": "prettier --write **/*.ts"
},
"husky": {
"hooks": {

View file

@ -13,113 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as toolCache from '@actions/tool-cache';
import * as setupGcloud from '../../setupGcloudSDK/dist/index';
import { promises as fs } from 'fs';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import * as tmp from 'tmp';
import * as os from 'os';
import { getReleaseURL } from '../src/format-url';
import { getLatestGcloudSDKVersion } from '../src/version-util';
import * as downloadUtil from './download-util';
import * as installUtil from './install-util';
async function installGcloudSDK(version: string): Promise<string> {
// retreive the release corresponding to the specified version and the current env
const osPlat = os.platform();
const osArch = os.arch();
const url = await getReleaseURL(osPlat, osArch, version);
// download and extract the release
const extPath = await downloadUtil.downloadAndExtractTool(url);
if (!extPath) {
throw new Error(`Failed to download release, url: ${url}`);
}
// install the downloaded release into the github action env
return await installUtil.installGcloudSDK(version, extPath);
}
async function run(): Promise<void> {
try {
tmp.setGracefulCleanup();
let version = core.getInput('version');
if (!version || version == 'latest') {
version = await getLatestGcloudSDKVersion();
version = await setupGcloud.getLatestGcloudSDKVersion();
}
// install the gcloud if not already present
let toolPath = toolCache.find('gcloud', version);
if (!toolPath) {
toolPath = await installGcloudSDK(version);
core.info('Successfully installed gcloud Cloud SDK');
// Install the gcloud if not already present
if (!setupGcloud.isInstalled()) {
await setupGcloud.installGcloudSDK(version);
} else {
const toolPath = toolCache.find('gcloud', version);
core.addPath(path.join(toolPath, 'bin'));
}
// A workaround for https://github.com/actions/toolkit/issues/229
// Currently exec on windows runs as cmd shell.
let toolCommand = 'gcloud';
if (process.platform == 'win32') {
toolCommand = 'gcloud.cmd';
}
// Set the project ID, if given.
const projectId = core.getInput('project_id');
if (projectId) {
await exec.exec(toolCommand, [
'--quiet',
'config',
'set',
'core/project',
projectId,
]);
core.info('Successfully set default project');
}
const serviceAccountEmail = core.getInput('service_account_email') || '';
const serviceAccountKey = core.getInput('service_account_key');
// if a service account key isn't provided, log an un-authenticated notice
// If a service account key isn't provided, log an un-authenticated notice
if (!serviceAccountKey) {
core.info('No credentials provided, skipping authentication');
return;
} else {
await setupGcloud.authenticateGcloudSDK(serviceAccountKey);
}
// Handle base64-encoded credentials
let serviceAccountJSON = serviceAccountKey;
if (!serviceAccountKey.trim().startsWith('{')) {
serviceAccountJSON = Buffer.from(serviceAccountKey, 'base64').toString();
}
// write the service account key to a temporary file
//
// TODO: if actions/toolkit#164 is fixed, pass the key in on stdin and avoid
// writing a file to disk.
const tmpKeyFilePath = await new Promise<string>((resolve, reject) => {
tmp.file((err, path) => {
if (err) {
reject(err);
}
resolve(path);
});
});
await fs.writeFile(tmpKeyFilePath, serviceAccountJSON);
// Authenticate as the specified service account.
await exec.exec(toolCommand, [
'--quiet',
'auth',
'activate-service-account',
serviceAccountEmail,
'--key-file',
tmpKeyFilePath,
]);
// Export credentials if requested - these credentials must be exported in
// the shared workspace directory, since the filesystem must be shared among
// all steps.
@ -131,7 +55,10 @@ async function run(): Promise<void> {
}
const credsPath = path.join(workspace, uuidv4());
await fs.writeFile(credsPath, serviceAccountJSON);
await fs.writeFile(
credsPath,
setupGcloud.parseServiceAccountKey(serviceAccountKey),
);
core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credsPath);
core.info('Successfully exported Default Application Credentials');

View file

@ -1,68 +0,0 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Tests install-util.
*/
import 'mocha';
import { expect } from 'chai';
import fs from 'fs';
import path from 'path';
import os from 'os';
import * as io from '@actions/io';
import { TestToolCache, TEST_SDK_VERSION } from '../src/test-util';
const [toolDir, tempDir] = TestToolCache.override();
import { getReleaseURL } from '../src/format-url';
import {
GCLOUD_METRICS_ENV_VAR,
GCLOUD_METRICS_LABEL,
installGcloudSDK,
} from '../src/install-util';
import { downloadAndExtractTool } from '../src/download-util';
describe('#installGcloudSDK', () => {
before(async function() {
await io.rmRF(toolDir);
await io.rmRF(tempDir);
});
after(async function() {
await io.rmRF(toolDir);
await io.rmRF(tempDir);
});
it('installs gcloud for current env', async function() {
const url = await getReleaseURL(os.platform(), os.arch(), TEST_SDK_VERSION);
const extPath = await downloadAndExtractTool(url);
await installGcloudSDK(TEST_SDK_VERSION, extPath);
const gcloudDir = path.join(toolDir, 'gcloud', TEST_SDK_VERSION, os.arch());
expect(fs.existsSync(gcloudDir)).to.be.true;
expect(fs.existsSync(path.join(gcloudDir, 'bin', 'gcloud'))).to.be.true;
});
it('installs metrics tracking env var', async function() {
const url = await getReleaseURL(os.platform(), os.arch(), TEST_SDK_VERSION);
const extPath = await downloadAndExtractTool(url);
await installGcloudSDK(TEST_SDK_VERSION, extPath);
expect(process.env[GCLOUD_METRICS_ENV_VAR]).to.equal(GCLOUD_METRICS_LABEL);
});
});

View file

@ -26,5 +26,10 @@
"noImplicitAny": true,
"esModuleInterop": true
},
"references": [
{
"path": "../setupGcloudSDK"
}
],
"exclude": ["node_modules", "**/*.test.ts"]
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier/@typescript-eslint',
],
};

45
setupGcloudSDK/.gitignore vendored Normal file
View file

@ -0,0 +1,45 @@
node_modules/
runner/
# Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz

View file

@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
arrowParens: 'always',
bracketSpacing: true,
endOfLine: 'auto',
jsxBracketSameLine: true,
jsxSingleQuote: true,
printWidth: 80,
quoteProps: 'consistent',
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'all',
useTabs: false,
};

View file

@ -0,0 +1,7 @@
/**
* Downloads and extracts the tool at the specified URL.
*
* @url The URL of the tool to be downloaded.
* @returns The path to the locally extracted tool.
*/
export declare function downloadAndExtractTool(url: string): Promise<string>;

10
setupGcloudSDK/dist/format-url.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
/**
* Creates the gcloud SDK release URL for the specified arguments, verifying
* its existence.
*
* @param os The OS of the requested release.
* @param arch The system architecture of the requested release.
* @param version The version of the requested release.
* @returns The verified gcloud SDK release URL.
*/
export declare function getReleaseURL(os: string, arch: string, version: string): Promise<string>;

62
setupGcloudSDK/dist/index.d.ts vendored Normal file
View file

@ -0,0 +1,62 @@
import { getLatestGcloudSDKVersion } from './version-util';
export { getLatestGcloudSDKVersion };
/**
* Checks if gcloud is installed
*
* @param version (Optional) Cloud SDK version
* @return true if gcloud is found in toolpath
*/
export declare function isInstalled(version?: string): boolean;
/**
* Returns the correct gcloud command for OS
*
* @returns gcloud command
*/
export declare function getToolCommand(): string;
/**
* Checks if gcloud is authenticated
*
* @returns true is gcloud is authenticated
*/
export declare function isAuthenticated(): Promise<boolean>;
/**
* Installs the gcloud SDK into the actions environment.
*
* @param version The version being installed.
* @param gcloudExtPath The extraction path for the gcloud SDK.
* @returns The path of the installed tool.
*/
export declare function installGcloudSDK(version: string): Promise<string>;
/**
* Parses the service account string into JSON
*
* @param serviceAccountKey The service account key used for authentication.
* @returns ServiceAccountKey object
*/
export declare function parseServiceAccountKey(serviceAccountKey: string): ServiceAccountKey;
/**
* Authenticates the gcloud tool using a service account key
*
* @param serviceAccountKey The service account key used for authentication.
* @returns exit code
*/
export declare function authenticateGcloudSDK(serviceAccountKey: string): Promise<number>;
/**
* Sets the GCP Project Id in the gcloud config
*
* @param serviceAccountKey The service account key used for authentication.
* @returns project ID
*/
export declare function setProject(serviceAccountKey: string): Promise<string>;
interface ServiceAccountKey {
type: string;
project_id: string;
project_key_id: string;
private_key: string;
client_email: string;
client_id: string;
auth_uri: string;
token_uri: string;
auth_provider_x509_cert_url: string;
client_x509_cert_url: string;
}

11398
setupGcloudSDK/dist/index.js vendored Normal file

File diff suppressed because it is too large Load diff

10
setupGcloudSDK/dist/install-util.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
export declare const GCLOUD_METRICS_ENV_VAR = "CLOUDSDK_METRICS_ENVIRONMENT";
export declare const GCLOUD_METRICS_LABEL = "github-actions-setup-gcloud";
/**
* Installs the gcloud SDK into the actions environment.
*
* @param version The version being installed.
* @param gcloudExtPath The extraction path for the gcloud SDK.
* @returns The path of the installed tool.
*/
export declare function installGcloudSDK(version: string, gcloudExtPath: string): Promise<string>;

22
setupGcloudSDK/dist/test-util.d.ts vendored Normal file
View file

@ -0,0 +1,22 @@
/**
* Creates an overridden runner cache and tool path. This is slightly
* complicated by the fact that the runner initializes its cache path exactly
* once at startup, so this must be imported and called BEFORE the toolcache is
* used.
*/
export declare class TestToolCache {
private static paths;
/**
* Creates temporary directories for the runner cache and temp, and configures
* the Action's runner to use said directories.
*
* @returns two strings - first is overridden toolsPath, second is tempPath.
*/
static override(): [string, string];
private static randomStr;
}
/**
* The version of the gcloud SDK being tested against.
*/
export declare const TEST_SDK_VERSIONS: string[];
export declare const TEST_SDK_VERSION: string;

4
setupGcloudSDK/dist/version-util.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
/**
* @returns The latest stable version of the gcloud SDK.
*/
export declare function getLatestGcloudSDKVersion(): Promise<string>;

2466
setupGcloudSDK/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
{
"name": "setupGcloudSDK",
"version": "1.0.0",
"description": "Common tools needed to install and use the Cloud SDK",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "ncc build src/index.ts",
"lint": "eslint ./src --ext .ts,.tsx",
"format": "prettier --write **/*.ts",
"test": "mocha -r ts-node/register -t 30s 'tests/*.test.ts'"
},
"repository": {
"type": "git",
"url": "git+https://github.com/GoogleCloudPlatform/github-actions.git"
},
"author": "GoogleCloudPlatform",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/GoogleCloudPlatform/github-actions/issues"
},
"homepage": "https://github.com/GoogleCloudPlatform/github-actions#readme",
"dependencies": {
"@actions/core": "^1.2.3",
"@actions/exec": "^1.0.3",
"@actions/tool-cache": "^1.3.3",
"@lifeomic/attempt": "^3.0.0",
"@types/tmp": "^0.1.0",
"tmp": "^0.1.0",
"typed-rest-client": "^1.7.0"
},
"devDependencies": {
"@types/chai": "^4.2.x",
"@types/mocha": "^7.0.x",
"@types/node": "^12.12.27",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",
"@zeit/ncc": "^0.20.x",
"chai": "^4.2.x",
"eslint": "^6.8.x",
"eslint-config-prettier": "^6.10.x",
"eslint-plugin-prettier": "^3.1.x",
"husky": "^4.2.3",
"mocha": "^7.0.x",
"prettier": "^1.19.1",
"ts-node": "^8.6.x",
"typescript": "^3.7.x"
}
}

197
setupGcloudSDK/src/index.ts Normal file
View file

@ -0,0 +1,197 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as exec from '@actions/exec';
import * as toolCache from '@actions/tool-cache';
import * as os from 'os';
import * as tmp from 'tmp';
import { promises as fs } from 'fs';
import { getReleaseURL } from './format-url';
import * as downloadUtil from './download-util';
import * as installUtil from './install-util';
import { getLatestGcloudSDKVersion } from './version-util';
export { getLatestGcloudSDKVersion };
/**
* Checks if gcloud is installed
*
* @param version (Optional) Cloud SDK version
* @return true if gcloud is found in toolpath
*/
export function isInstalled(version?: string): boolean {
let toolPath;
if (version) {
toolPath = toolCache.find('gcloud', version);
return toolPath != undefined && toolPath !== '';
} else {
toolPath = toolCache.findAllVersions('gcloud');
return toolPath.length > 0;
}
}
/**
* Returns the correct gcloud command for OS
*
* @returns gcloud command
*/
export function getToolCommand(): string {
// A workaround for https://github.com/actions/toolkit/issues/229
// Currently exec on windows runs as cmd shell.
let toolCommand = 'gcloud';
if (process.platform == 'win32') {
toolCommand = 'gcloud.cmd';
}
return toolCommand;
}
/**
* Checks if gcloud is authenticated
*
* @returns true is gcloud is authenticated
*/
export async function isAuthenticated(): Promise<boolean> {
let output = '';
const stdout = (data: Buffer): void => {
output += data.toString();
};
const options = {
listeners: {
stdout,
},
};
const toolCommand = getToolCommand();
await exec.exec(toolCommand, ['auth', 'list'], options);
return !output.includes('No credentialed accounts.');
}
/**
* Installs the gcloud SDK into the actions environment.
*
* @param version The version being installed.
* @param gcloudExtPath The extraction path for the gcloud SDK.
* @returns The path of the installed tool.
*/
export async function installGcloudSDK(version: string): Promise<string> {
// Retreive the release corresponding to the specified version and OS
const osPlat = os.platform();
const osArch = os.arch();
const url = await getReleaseURL(osPlat, osArch, version);
// Download and extract the release
const extPath = await downloadUtil.downloadAndExtractTool(url);
if (!extPath) {
throw new Error(`Failed to download release, url: ${url}`);
}
// Install the downloaded release into the github action env
return await installUtil.installGcloudSDK(version, extPath);
}
/**
* Parses the service account string into JSON
*
* @param serviceAccountKey The service account key used for authentication.
* @returns ServiceAccountKey object
*/
export function parseServiceAccountKey(
serviceAccountKey: string,
): ServiceAccountKey {
let serviceAccount = serviceAccountKey;
// Handle base64-encoded credentials
if (!serviceAccountKey.trim().startsWith('{')) {
serviceAccount = Buffer.from(serviceAccountKey, 'base64').toString('utf8');
}
return JSON.parse(serviceAccount);
}
/**
* Authenticates the gcloud tool using a service account key
*
* @param serviceAccountKey The service account key used for authentication.
* @returns exit code
*/
export async function authenticateGcloudSDK(
serviceAccountKey: string,
): Promise<number> {
tmp.setGracefulCleanup();
const serviceAccountJson = parseServiceAccountKey(serviceAccountKey);
const serviceAccountEmail = serviceAccountJson.client_email;
// write the service account key to a temporary file
//
// TODO: if actions/toolkit#164 is fixed, pass the key in on stdin and avoid
// writing a file to disk.
const tmpKeyFilePath = await new Promise<string>((resolve, reject) => {
tmp.file((err, path) => {
if (err) {
reject(err);
}
resolve(path);
});
});
await fs.writeFile(tmpKeyFilePath, JSON.stringify(serviceAccountJson));
const toolCommand = getToolCommand();
// Authenticate as the specified service account.
return await exec.exec(toolCommand, [
'--quiet',
'auth',
'activate-service-account',
serviceAccountEmail,
'--key-file',
tmpKeyFilePath,
]);
}
/**
* Sets the GCP Project Id in the gcloud config
*
* @param serviceAccountKey The service account key used for authentication.
* @returns project ID
*/
export async function setProject(serviceAccountKey: string): Promise<string> {
tmp.setGracefulCleanup();
const serviceAccountJson = parseServiceAccountKey(serviceAccountKey);
const toolCommand = getToolCommand();
await exec.exec(toolCommand, [
'--quiet',
'config',
'set',
'project',
serviceAccountJson.project_id,
]);
return serviceAccountJson.project_id;
}
interface ServiceAccountKey {
type: string;
project_id: string;
project_key_id: string;
private_key: string;
client_email: string;
client_id: string;
auth_uri: string;
token_uri: string;
auth_provider_x509_cert_url: string;
client_x509_cert_url: string;
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"es6"
],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"declaration": true,
"composite": true
},
"exclude": ["node_modules", "**/*.test.ts"]
}