Reputation: 11091
Is there any - ideally simple and quick - way how to migrate Google Application Script project to another account?
Let's say that I work on a project for a client and once it is ready I want to migrate it. But the script connects to Google Sheets and there is also a time trigger. Also the project acts as a web application.
I thought that if the customer creates a project and shares that with me and authorises the script to access the Google Sheet by running the script by themselves it would solve "the migration". But it does not look like. When I run the script it asks me to authorise the access to Sheets too. And I have no idea how it would work with the trigger and web application.
Could someone explain? How to make sure that at the end the project runs with end customer privileges, uses their quota etc?
Upvotes: 1
Views: 1745
Reputation: 2851
There are multiple points to talk about:
The documentation has a nice table that is a good overview on what permissions execute when.
Quoting the documentation:
When you collaborate on a project, any installable triggers that you create are not shared with those who have access to your project. […]
So basically if you make an installable trigger, that trigger executes as you and it uses your quota.
You can collaborate on web apps using shared drive. When a web app in a shared is deployed, choosing to "execute as you" causes the web app to execute under the authority of the user that deployed it (since there is no script owner).
You can use ScriptApp
to set the triggers and use the Apps Scripts API to deploy. This will recur configuration of a GCP and manually setup the project manifest (see manifest’s structure reference). Here is a code I’ve done that works when everything is set up properly:
//
// This file is for easy setup of the project.
//
/**
* Remove any installed trigger and makes the ones on the list
*/
function remakeTriggers() {
// Remove all triggers for this project for this user
ScriptApp.getProjectTriggers().forEach(trigger => ScriptApp.deleteTrigger(trigger))
// Remake triggers
// Change as desired to set the triggers you want
ScriptApp.newTrigger('myTimer')
.timeBased()
.atHour(2)
.everyDays(1)
.create()
ScriptApp.newTrigger('myTimer')
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onEdit()
.create()
}
const DEPLOYMENT_ID_KEY = 'deploymentId'
/**
* Deploys this script as a WebApp.
*/
function deploy() {
const properties = PropertiesService.getScriptProperties()
const version = newVersion_()
const deploymentId = properties.getProperty(DEPLOYMENT_ID_KEY)
if (!deploymentId) {
console.log(`Creating new deployment to version ${version.versionNumber}`)
const deployment = newDeploy_(version.versionNumber)
properties.setProperty(DEPLOYMENT_ID_KEY, deployment.deploymentId)
} else {
console.log(`Updating deployment to version ${version.versionNumber}`)
updateDeploy_(deploymentId, version.versionNumber)
}
}
/**
* Makes a new version.
*/
function newVersion_() {
const response = UrlFetchApp.fetch(
`https://script.googleapis.com/v1/projects/${ScriptApp.getScriptId()}/versions`,
{
method: 'POST',
muteHttpExceptions: true,
headers: {
'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
},
},
)
return parseResponse_(response)
}
/**
* Makes a new deployment
*/
function newDeploy_(versionNumber) {
const response = UrlFetchApp.fetch(
`https://script.googleapis.com/v1/projects/${ScriptApp.getScriptId()}/deployments`,
{
method: 'POST',
payload: JSON.stringify({ versionNumber }),
contentType: 'application/json; charset=utf-8',
headers: {
'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
},
muteHttpExceptions: true,
},
)
return parseResponse_(response)
}
/**
* Redeploys
*/
function updateDeploy_(deploymentId, versionNumber) {
const response = UrlFetchApp.fetch(
`https://script.googleapis.com/v1/projects/${ScriptApp.getScriptId()}/deployments/${deploymentId}`,
{
method: 'PUT',
payload: JSON.stringify({
deploymentConfig: {
versionNumber
}
}),
contentType: 'application/json; charset=utf-8',
headers: {
'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
},
muteHttpExceptions: true,
},
)
return parseResponse_(response)
}
/**
* Helper function to parse a fetch's JSON response. Handles errors.
*
* @param {UrlFetchApp.HTTPResponse} response to parse
* @returns {Object} Parsed JSON response
*/
function parseResponse_(response) {
const result = response.getContentText()
const code = response.getResponseCode()
if (code < 200 || code >= 400) {
throw result
}
return JSON.parse(result)
}
Remember to change remakeTriggers
so it creates the triggers that you want.
And my manifest looks like this:
{
[…]
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/script.deployments"
],
"webapp": {
"access": "ANYONE",
"executeAs": "USER_DEPLOYING"
}
}
Notice that we are configuring the webapp
here (cannot do it via API) and that the OAuth2 scopes need to be defined manually. Once everything is set up, the final user would only need to go the script file and execute the functions as required (remakeTriggers
to remake the triggers and deploy
to do so).
You could also make this two function private (add _
at the end of their name) and make a function that calls them both, so the user only needs to go to the Deploy.gs
file a click Run
.
Upvotes: 3