firebase / extensions

Source code for official Firebase extensions
https://firebase.google.com/products/extensions
Apache License 2.0
889 stars 375 forks source link

Cannot Install or Uninstall Firebase Extensions #522

Closed fergusfrl closed 3 years ago

fergusfrl commented 3 years ago

[REQUIRED] Step 2: Describe your configuration

[REQUIRED] Step 3: Describe the problem

When installing/uninstalling firebase extensions I can see the following errors in my console: firebase-extension-errors

As a part of troubleshooting I ran: firebase ext:list --json --project=[projectId-or-alias] > result.json, and received this file:

{
  "status": "success",
  "result": {
    "instances": [
      {
        "name": "projects/akeela-development-291705/instances/firestore-translate-text",
        "createTime": "2020-11-27T06:48:33.726546Z",
        "updateTime": "2020-11-27T06:49:16.298458Z",
        "state": "ERRORED",
        "config": {
          "name": "projects/akeela-development-291705/instances/firestore-translate-text/configurations/6b1cf0c0-67da-4e31-9179-31f5612d92f0",
          "createTime": "2020-11-27T06:48:33.726546Z",
          "source": {
            "name": "projects/firebasemods/sources/ce834759-46a8-4a88-b2c5-eb14e59063e8",
            "packageUri": "https://storage.googleapis.com/firebase-ext-eap-uploads/firebase-archive-44939SdaXrmLzokKp.zip?alt=media",
            "hash": "8eb4a45070146435843c5d06abad8e3beb049b7382298c7ea23704def7c2413c",
            "extensionRoot": "/",
            "spec": {
              "specVersion": "v1beta",
              "name": "firestore-translate-text",
              "version": "0.1.5",
              "description": "Translates strings written to a Cloud Firestore collection into multiple languages (uses Cloud Translation API).",
              "apis": [
                {
                  "apiName": "translate.googleapis.com",
                  "reason": "To use Google Translate to translate strings into your specified target languages."
                }
              ],
              "roles": [
                {
                  "role": "datastore.user",
                  "reason": "Allows the extension to write translated strings to Cloud Firestore."
                }
              ],
              "resources": [
                {
                  "name": "fstranslate",
                  "type": "firebaseextensions.v1beta.function",
                  "propertiesYaml": "eventTrigger:\n  eventType: providers/cloud.firestore/eventTypes/document.write\n  resource: projects/${PROJECT_ID}/databases/(default)/documents/${COLLECTION_PATH}/{messageId}\nlocation: ${LOCATION}\nruntime: nodejs10\n",
                  "description": "Listens for writes of new strings to your specified Cloud Firestore collection, translates the strings, then writes the translated strings back to the same document.",
                  "deletionPolicy": "DELETE"
                }
              ],
              "billingRequired": true,
              "author": {
                "authorName": "Firebase",
                "url": "https://firebase.google.com"
              },
              "contributors": [
                {
                  "authorName": "Chris Bianca",
                  "email": "chris@csfrequency.com",
                  "url": "https://github.com/chrisbianca"
                },
                {
                  "authorName": "Invertase",
                  "email": "oss@invertase.io",
                  "url": "https://github.com/invertase"
                }
              ],
              "license": "Apache-2.0",
              "releaseNotesUrl": "https://github.com/firebase/extensions/blob/master/firestore-translate-text/CHANGELOG.md",
              "sourceUrl": "https://github.com/firebase/extensions/tree/master/firestore-translate-text",
              "params": [
                {
                  "param": "LOCATION",
                  "label": "Cloud Functions location",
                  "type": "SELECT",
                  "description": "Where do you want to deploy the functions created for this extension? You usually want a location close to your database. For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).",
                  "required": true,
                  "options": [
                    {
                      "value": "us-central1",
                      "label": "Iowa (us-central1)"
                    },
                    {
                      "value": "us-east1",
                      "label": "South Carolina (us-east1)"
                    },
                    {
                      "value": "us-east4",
                      "label": "Northern Virginia (us-east4)"
                    },
                    {
                      "value": "us-west2",
                      "label": "Los Angeles (us-west2)"
                    },
                    {
                      "value": "us-west3",
                      "label": "Salt Lake City (us-west3)"
                    },
                    {
                      "value": "us-west4",
                      "label": "Las Vegas (us-west4)"
                    },
                    {
                      "value": "europe-west1",
                      "label": "Belgium (europe-west1)"
                    },
                    {
                      "value": "europe-west2",
                      "label": "London (europe-west2)"
                    },
                    {
                      "value": "europe-west3",
                      "label": "Frankfurt (europe-west3)"
                    },
                    {
                      "value": "europe-west6",
                      "label": "Zurich (europe-west6)"
                    },
                    {
                      "value": "asia-east2",
                      "label": "Hong Kong (asia-east2)"
                    },
                    {
                      "value": "asia-northeast1",
                      "label": "Tokyo (asia-northeast1)"
                    },
                    {
                      "value": "asia-northeast2",
                      "label": "Osaka (asia-northeast2)"
                    },
                    {
                      "value": "asia-northeast3",
                      "label": "Seoul (asia-northeast3)"
                    },
                    {
                      "value": "asia-south1",
                      "label": "Mumbai (asia-south1)"
                    },
                    {
                      "value": "asia-southeast2",
                      "label": "Jakarta (asia-southeast2)"
                    },
                    {
                      "value": "northamerica-northeast1",
                      "label": "Montreal (northamerica-northeast1)"
                    },
                    {
                      "value": "southamerica-east1",
                      "label": "Sao Paulo (southamerica-east1)"
                    },
                    {
                      "value": "australia-southeast1",
                      "label": "Sydney (australia-southeast1)"
                    }
                  ],
                  "default": "us-central1",
                  "immutable": true
                },
                {
                  "param": "LANGUAGES",
                  "label": "Target languages for translations, as a comma-separated list",
                  "type": "STRING",
                  "description": "Into which target languages do you want to translate new strings? The languages are identified using ISO-639-1 codes in a comma-separated list, for example: en,es,de,fr. For these codes, visit the [supported languages list](https://cloud.google.com/translate/docs/languages).\n",
                  "required": true,
                  "default": "en,es,de,fr",
                  "example": "en,es,de,fr",
                  "validationRegex": "^[a-zA-Z,-]*[a-zA-Z-]{2,}$",
                  "validationErrorMessage": "Languages must be a comma-separated list of ISO-639-1 language codes."
                },
                {
                  "param": "COLLECTION_PATH",
                  "label": "Collection path",
                  "type": "STRING",
                  "description": "What is the path to the collection that contains the strings that you want to translate?\n",
                  "required": true,
                  "default": "translations",
                  "example": "translations",
                  "validationRegex": "^[^/]+(/[^/]+/[^/]+)*$",
                  "validationErrorMessage": "Must be a valid Cloud Firestore Collection"
                },
                {
                  "param": "INPUT_FIELD_NAME",
                  "label": "Input field name",
                  "type": "STRING",
                  "description": "What is the name of the field that contains the string that you want to translate?\n",
                  "required": true,
                  "default": "input",
                  "example": "input"
                },
                {
                  "param": "OUTPUT_FIELD_NAME",
                  "label": "Translations output field name",
                  "type": "STRING",
                  "description": "What is the name of the field where you want to store your translations?\n",
                  "required": true,
                  "default": "translated",
                  "example": "translated"
                }
              ],
              "preinstallContent": "Use this extension to translate strings (for example, text messages) written to a Cloud Firestore collection.\n\nThis extension listens to your specified Cloud Firestore collection. If you add a string to a specified field in any document within that collection, this extension:\n\n- Translates the string into your specified target language(s); the source language of the string is automatically detected.\n- Adds the translation(s) of the string to a separate specified field in the same document.\n\nYou specify the desired target languages using ISO-639-1 codes. You can find a list of valid languages and their corresponding codes in the [Cloud Translation API documentation](https://cloud.google.com/translate/docs/languages).\n\nIf the original non-translated field of the document is updated, then the translations will be automatically updated, as well.\n\n#### Additional setup\n\nBefore installing this extension, make sure that you've [set up a Cloud Firestore database](https://firebase.google.com/docs/firestore/quickstart) in your Firebase project.\n\n#### Billing\nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n\n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n  - Cloud Translation API\n  - Cloud Firestore\n  - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n",
              "postinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Cloud Firestore dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/firestore/data) in the Firebase console.\n\n1.  If it doesn't exist already, create a collection called `${param:COLLECTION_PATH}`.\n\n1.  Create a document with a field named `${param:INPUT_FIELD_NAME}`, then make its value a word or phrase that you want to translate.\n\n1.  In a few seconds, you'll see a new field called `${param:OUTPUT_FIELD_NAME}` pop up in the same document you just created. It will contain the translations for each language you specified during installation. \n\n### Using the extension\n\nThis extension translates the input string(s) into your specified target language(s); the source language of the string is automatically detected. If the `${param:INPUT_FIELD_NAME}` field of the document is updated,\n then the translations will be automatically updated as well.\n\n\n#### Input field as a string\n\nWrite the string \"My name is Bob\" to the field `${param:INPUT_FIELD_NAME}` in `${param:COLLECTION_PATH}` will result in the following translated output in `${param:OUTPUT_FIELD_NAME}`:\n\n```js\n{\n  ${param:INPUT_FIELD_NAME}: 'My name is Bob',\n  ${param:OUTPUT_FIELD_NAME}: {\n    de: 'Ich heiße Bob',\n    en: 'My name is Bob',\n    es: 'Mi nombre es Bob',\n    fr: 'Je m'appelle Bob',\n  },\n}\n```\n\n#### Input field as a map of input strings\n\nCreate or update a document in `${param:COLLECTION_PATH}` with the field `${param:INPUT_FIELD_NAME}` value like the following:\n\n```js\n{\n  first: \"My name is Bob\",\n  second: \"Hello, friend\"\n}\n```\n\nwill result in the following translated output in `${param:OUTPUT_FIELD_NAME}`:\n\n```js\n{\n  ${param:INPUT_FIELD_NAME}: {\n    first: \"My name is Bob\",\n    second: \"Hello, friend\"\n  },\n  \n  ${param:OUTPUT_FIELD_NAME}: {\n    first:{\n      de: \"Ich heiße Bob\",\n      en: \"My name is Bob\",\n      es: \"Mi nombre es Bob\",\n      fr: \"Je m'appelle Bob\",\n    },\n    second:{\n      de: \"Hallo Freund\",\n      en: \"Hello, friend\",\n      es: \"Hola amigo\",\n      fr: \"Salut l'ami\",\n    },   \n  },\n}\n```\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n",
              "readmeContent": "# Translate Text\n\n**Author**: Firebase (**[https://firebase.google.com](https://firebase.google.com)**)\n\n**Description**: Translates strings written to a Cloud Firestore collection into multiple languages (uses Cloud Translation API).\n\n\n\n**Details**: Use this extension to translate strings (for example, text messages) written to a Cloud Firestore collection.\n\nThis extension listens to your specified Cloud Firestore collection. If you add a string to a specified field in any document within that collection, this extension:\n\n- Translates the string into your specified target language(s); the source language of the string is automatically detected.\n- Adds the translation(s) of the string to a separate specified field in the same document.\n\nYou specify the desired target languages using ISO-639-1 codes. You can find a list of valid languages and their corresponding codes in the [Cloud Translation API documentation](https://cloud.google.com/translate/docs/languages).\n\nIf the original non-translated field of the document is updated, then the translations will be automatically updated, as well.\n\n#### Additional setup\n\nBefore installing this extension, make sure that you've [set up a Cloud Firestore database](https://firebase.google.com/docs/firestore/quickstart) in your Firebase project.\n\n#### Billing\nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n\n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n  - Cloud Translation API\n  - Cloud Firestore\n  - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n\n\n\n\n**Configuration Parameters:**\n\n* Cloud Functions location: Where do you want to deploy the functions created for this extension? You usually want a location close to your database. For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).\n\n* Target languages for translations, as a comma-separated list: Into which target languages do you want to translate new strings? The languages are identified using ISO-639-1 codes in a comma-separated list, for example: en,es,de,fr. For these codes, visit the [supported languages list](https://cloud.google.com/translate/docs/languages).\n\n\n* Collection path: What is the path to the collection that contains the strings that you want to translate?\n\n\n* Input field name: What is the name of the field that contains the string that you want to translate?\n\n\n* Translations output field name: What is the name of the field where you want to store your translations?\n\n\n\n\n**Cloud Functions:**\n\n* **fstranslate:** Listens for writes of new strings to your specified Cloud Firestore collection, translates the strings, then writes the translated strings back to the same document.\n\n\n\n**APIs Used**:\n\n* translate.googleapis.com (Reason: To use Google Translate to translate strings into your specified target languages.)\n\n\n\n**Access Required**:\n\n\n\nThis extension will operate with the following project IAM roles:\n\n* datastore.user (Reason: Allows the extension to write translated strings to Cloud Firestore.)\n",
              "displayName": "Translate Text"
            },
            "fetchTime": "2020-09-24T17:14:55.321442Z",
            "lastOperationName": "projects/firebasemods/operations/9b5e716a-2244-46e7-b42b-7b91eb9b3d67",
            "state": "ACTIVE"
          },
          "params": {
            "LOCATION": "us-central1",
            "LANGUAGES": "en,es,de,fr",
            "COLLECTION_PATH": "translations",
            "INPUT_FIELD_NAME": "input",
            "OUTPUT_FIELD_NAME": "translated"
          },
          "populatedPostinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Cloud Firestore dashboard](https://console.firebase.google.com/project/akeela-development-291705/firestore/data) in the Firebase console.\n\n1.  If it doesn't exist already, create a collection called `translations`.\n\n1.  Create a document with a field named `input`, then make its value a word or phrase that you want to translate.\n\n1.  In a few seconds, you'll see a new field called `translated` pop up in the same document you just created. It will contain the translations for each language you specified during installation. \n\n### Using the extension\n\nThis extension translates the input string(s) into your specified target language(s); the source language of the string is automatically detected. If the `input` field of the document is updated,\n then the translations will be automatically updated as well.\n\n\n#### Input field as a string\n\nWrite the string \"My name is Bob\" to the field `input` in `translations` will result in the following translated output in `translated`:\n\n```js\n{\n  input: 'My name is Bob',\n  translated: {\n    de: 'Ich heiße Bob',\n    en: 'My name is Bob',\n    es: 'Mi nombre es Bob',\n    fr: 'Je m'appelle Bob',\n  },\n}\n```\n\n#### Input field as a map of input strings\n\nCreate or update a document in `translations` with the field `input` value like the following:\n\n```js\n{\n  first: \"My name is Bob\",\n  second: \"Hello, friend\"\n}\n```\n\nwill result in the following translated output in `translated`:\n\n```js\n{\n  input: {\n    first: \"My name is Bob\",\n    second: \"Hello, friend\"\n  },\n  \n  translated: {\n    first:{\n      de: \"Ich heiße Bob\",\n      en: \"My name is Bob\",\n      es: \"Mi nombre es Bob\",\n      fr: \"Je m'appelle Bob\",\n    },\n    second:{\n      de: \"Hallo Freund\",\n      en: \"Hello, friend\",\n      es: \"Hola amigo\",\n      fr: \"Salut l'ami\",\n    },   \n  },\n}\n```\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n"
        },
        "errorStatus": {
          "code": 13,
          "message": "; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-translate: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/translate.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-firebase: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/firebase.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-iam: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/iam.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-cloudfunctions: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudfunctions.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-cloudbuild: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudbuild.googleapis.com:enable\",\"httpMethod\":\"POST\"}}"
        },
        "lastOperationName": "projects/akeela-development-291705/operations/e449421c-5329-4909-bfbf-a917c1337a43",
        "serviceAccountEmail": "ext-firestore-translate-text@akeela-development-291705.iam.gserviceaccount.com",
        "lastOperationType": "CREATE"
      },
      {
        "name": "projects/akeela-development-291705/instances/delete-user-data",
        "createTime": "2020-11-27T06:46:10.159284Z",
        "updateTime": "2020-11-27T06:46:52.937353Z",
        "state": "ERRORED",
        "config": {
          "name": "projects/akeela-development-291705/instances/delete-user-data/configurations/e4068180-0e95-4ced-97bc-ce1bb1814b90",
          "createTime": "2020-11-27T06:46:10.159284Z",
          "source": {
            "name": "projects/firebasemods/sources/d4312b99-f4d0-4eca-b37d-fcf016cf7997",
            "packageUri": "https://storage.googleapis.com/firebase-ext-eap-uploads/firebase-archive-94327kutId30hp88z.zip?alt=media",
            "hash": "2781c94454827f5418b372cb01d7abe3ec36146e3098987f27717353b386f9ce",
            "extensionRoot": "/",
            "spec": {
              "specVersion": "v1beta",
              "name": "delete-user-data",
              "version": "0.1.6",
              "description": "Deletes data keyed on a userId from Cloud Firestore, Realtime Database, and/or Cloud Storage when a user deletes their account.",
              "roles": [
                {
                  "role": "datastore.owner",
                  "reason": "Allows the extension to delete (user) data from Cloud Firestore."
                },
                {
                  "role": "firebasedatabase.admin",
                  "reason": "Allows the extension to delete (user) data from Realtime Database."
                },
                {
                  "role": "storage.admin",
                  "reason": "Allows the extension to delete (user) data from Cloud Storage."
                }
              ],
              "resources": [
                {
                  "name": "clearData",
                  "type": "firebaseextensions.v1beta.function",
                  "propertiesYaml": "eventTrigger:\n  eventType: providers/firebase.auth/eventTypes/user.delete\n  resource: projects/${PROJECT_ID}\nlocation: ${LOCATION}\nruntime: nodejs10\n",
                  "description": "Listens for user accounts to be deleted from your project's authenticated users, then removes any associated user data (based on Firebase Authentication's User ID) from Realtime Database, Cloud Firestore, and/or Cloud Storage.",
                  "deletionPolicy": "DELETE"
                }
              ],
              "billingRequired": true,
              "author": {
                "authorName": "Firebase",
                "url": "https://firebase.google.com"
              },
              "contributors": [
                {
                  "authorName": "Lauren Long",
                  "url": "https://github.com/laurenzlong"
                },
                {
                  "authorName": "Chris Bianca",
                  "email": "chris@csfrequency.com",
                  "url": "https://github.com/chrisbianca"
                },
                {
                  "authorName": "Invertase",
                  "email": "oss@invertase.io",
                  "url": "https://github.com/invertase"
                }
              ],
              "license": "Apache-2.0",
              "releaseNotesUrl": "https://github.com/firebase/extensions/blob/master/delete-user-data/CHANGELOG.md",
              "sourceUrl": "https://github.com/firebase/extensions/tree/master/delete-user-data",
              "params": [
                {
                  "param": "LOCATION",
                  "label": "Cloud Functions location",
                  "type": "SELECT",
                  "description": "Where do you want to deploy the functions created for this extension?  You usually want a location close to your database or Storage bucket. For help selecting a location, refer to the [location selection  guide](https://firebase.google.com/docs/functions/locations).",
                  "required": true,
                  "options": [
                    {
                      "value": "us-central1",
                      "label": "Iowa (us-central1)"
                    },
                    {
                      "value": "us-east1",
                      "label": "South Carolina (us-east1)"
                    },
                    {
                      "value": "us-east4",
                      "label": "Northern Virginia (us-east4)"
                    },
                    {
                      "value": "us-west2",
                      "label": "Los Angeles (us-west2)"
                    },
                    {
                      "value": "us-west3",
                      "label": "Salt Lake City (us-west3)"
                    },
                    {
                      "value": "us-west4",
                      "label": "Las Vegas (us-west4)"
                    },
                    {
                      "value": "europe-west1",
                      "label": "Belgium (europe-west1)"
                    },
                    {
                      "value": "europe-west2",
                      "label": "London (europe-west2)"
                    },
                    {
                      "value": "europe-west3",
                      "label": "Frankfurt (europe-west3)"
                    },
                    {
                      "value": "europe-west6",
                      "label": "Zurich (europe-west6)"
                    },
                    {
                      "value": "asia-east2",
                      "label": "Hong Kong (asia-east2)"
                    },
                    {
                      "value": "asia-northeast1",
                      "label": "Tokyo (asia-northeast1)"
                    },
                    {
                      "value": "asia-northeast2",
                      "label": "Osaka (asia-northeast2)"
                    },
                    {
                      "value": "asia-northeast3",
                      "label": "Seoul (asia-northeast3)"
                    },
                    {
                      "value": "asia-south1",
                      "label": "Mumbai (asia-south1)"
                    },
                    {
                      "value": "asia-southeast2",
                      "label": "Jakarta (asia-southeast2)"
                    },
                    {
                      "value": "northamerica-northeast1",
                      "label": "Montreal (northamerica-northeast1)"
                    },
                    {
                      "value": "southamerica-east1",
                      "label": "Sao Paulo (southamerica-east1)"
                    },
                    {
                      "value": "australia-southeast1",
                      "label": "Sydney (australia-southeast1)"
                    }
                  ],
                  "default": "us-central1",
                  "immutable": true
                },
                {
                  "param": "FIRESTORE_PATHS",
                  "label": "Cloud Firestore paths",
                  "type": "STRING",
                  "description": "Which paths in your Cloud Firestore instance contain user data? Leave empty if you don't use Cloud Firestore.\nEnter the full paths, separated by commas. You can represent the User ID of the deleted user with `{UID}`.\nFor example, if you have the collections `users` and `admins`, and each collection has documents with User ID as document IDs, then you can enter `users/{UID},admins/{UID}`.",
                  "example": "users/{UID},admins/{UID}"
                },
                {
                  "param": "FIRESTORE_DELETE_MODE",
                  "label": "Cloud Firestore delete mode",
                  "type": "SELECT",
                  "description": "(Only applicable if you use the `Cloud Firestore paths` parameter.) How do you want to delete Cloud Firestore documents? To also delete documents in subcollections, set this parameter to `recursive`.",
                  "required": true,
                  "options": [
                    {
                      "value": "recursive",
                      "label": "Recursive"
                    },
                    {
                      "value": "shallow",
                      "label": "Shallow"
                    }
                  ],
                  "default": "shallow"
                },
                {
                  "param": "RTDB_PATHS",
                  "label": "Realtime Database paths",
                  "type": "STRING",
                  "description": "Which paths in your Realtime Database instance contain user data? Leave empty if you don't use Realtime Database.\nEnter the full paths, separated by commas. You can represent the User ID of the deleted user with `{UID}`.\nFor example: `users/{UID},admins/{UID}`.",
                  "example": "users/{UID},admins/{UID}"
                },
                {
                  "param": "STORAGE_PATHS",
                  "label": "Cloud Storage paths",
                  "type": "STRING",
                  "description": "Where in Google Cloud Storage do you store user data? Leave empty if you don't use Cloud Storage.\nEnter the full paths to files or directories in your Storage buckets, separated by commas. Use `{UID}` to represent the User ID of the deleted user, and use `{DEFAULT}` to represent your default Storage bucket.\nHere's a series of examples. To delete all the files in your default bucket with the file naming scheme `{UID}-pic.png`, enter `{DEFAULT}/{UID}-pic.png`. To also delete all the files in another bucket called my-app-logs with the file naming scheme `{UID}-logs.txt`, enter `{DEFAULT}/{UID}-pic.png,my-app-logs/{UID}-logs.txt`. To *also* delete a User ID-labeled directory and all its files (like `media/{UID}`), enter `{DEFAULT}/{UID}-pic.png,my-app-logs/{UID}-logs.txt,{DEFAULT}/media/{UID}`.",
                  "example": "{DEFAULT}/{UID}-pic.png,my-awesome-app-logs/{UID}-logs.txt"
                }
              ],
              "preinstallContent": "Use this extension to automatically delete a user's data if the user is deleted from your authenticated users.\n\nYou can configure this extension to delete user data from any or all of the following: Cloud Firestore, Realtime Database, or Cloud Storage. Each trigger of the extension to delete data is keyed to the user's UserId.\n\n**Note:** To use this extension, you need to manage your users with Firebase Authentication.\n\nThis extension is useful in respecting user privacy and fulfilling compliance requirements. However, using this extension does not guarantee compliance with government and industry regulations.\n\n#### Additional setup\n\nDepending on where you'd like to delete user data from, make sure that you've set up [Cloud Firestore](https://firebase.google.com/docs/firestore), [Realtime Database](https://firebase.google.com/docs/database), or [Cloud Storage](https://firebase.google.com/docs/storage) in your Firebase project before installing this extension.\n\nAlso, make sure that you've set up [Firebase Authentication](https://firebase.google.com/docs/auth) to manage your users.\n\n#### Billing\n \nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n \n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n  - Cloud Firestore\n  - Firebase Realtime Database\n  - Cloud Storage\n  - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n",
              "postinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Authentication dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/authentication/users) in the Firebase console.\n\n1.  Click **Add User** to add a test user, then copy the test user's UID to your clipboard.\n\n1.  Create a new Cloud Firestore document, a new Realtime Database entry, or upload a new file to Storage - incorporating the user's UID into the path according to the schema that you configured.\n\n1.  Go back to your [Authentication dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/authentication/users), then delete the test user.\n\n1.  In a few seconds, the new data you added above will be deleted from Cloud Firestore, Realtime Database, and/or Storage (depending on what you configured).\n\n### Using the extension\n\nWhen a user's account is deleted from your project's authenticated users, this extension automatically deletes their data from Cloud Firestore, Realtime Database, and/or Cloud Storage.\n\n* Cloud Firestore path(s): `${param:FIRESTORE_PATHS}`\n* Realtime Database path(s): `${param:RTDB_PATHS}`\n* Cloud Storage path(s): `${param:STORAGE_PATHS}`\n\nYou can delete a user directly in your [Authentication dashboard]((https://console.firebase.google.com/project/${param:PROJECT_ID}/authentication/users)) or by using one of the Firebase Authentication SDKs. Learn more in the [Authentication documentation](https://firebase.google.com/docs/auth).\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n",
              "readmeContent": "# Delete User Data\n\n**Description**: Deletes data keyed on a userId from Cloud Firestore, Realtime Database, and/or Cloud Storage when a user deletes their account.\n\n\n\n**Details**: Use this extension to automatically delete a user's data if the user is deleted from your authenticated users.\n\nYou can configure this extension to delete user data from any or all of the following: Cloud Firestore, Realtime Database, or Cloud Storage. Each trigger of the extension to delete data is keyed to the user's UserId.\n\n**Note:** To use this extension, you need to manage your users with Firebase Authentication.\n\nThis extension is useful in respecting user privacy and fulfilling compliance requirements. However, using this extension does not guarantee compliance with government and industry regulations.\n\n#### Additional setup\n\nDepending on where you'd like to delete user data from, make sure that you've set up [Cloud Firestore](https://firebase.google.com/docs/firestore), [Realtime Database](https://firebase.google.com/docs/database), or [Cloud Storage](https://firebase.google.com/docs/storage) in your Firebase project before installing this extension.\n\nAlso, make sure that you've set up [Firebase Authentication](https://firebase.google.com/docs/auth) to manage your users.\n\n#### Billing\n \nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n \n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n  - Cloud Firestore\n  - Firebase Realtime Database\n  - Cloud Storage\n  - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n\n\n\n\n**Configuration Parameters:**\n\n* Cloud Functions location: Where do you want to deploy the functions created for this extension?  You usually want a location close to your database or Storage bucket. For help selecting a location, refer to the [location selection  guide](https://firebase.google.com/docs/functions/locations).\n\n* Cloud Firestore paths: Which paths in your Cloud Firestore instance contain user data? Leave empty if you don't use Cloud Firestore.\nEnter the full paths, separated by commas. You can represent the User ID of the deleted user with `{UID}`.\nFor example, if you have the collections `users` and `admins`, and each collection has documents with User ID as document IDs, then you can enter `users/{UID},admins/{UID}`.\n\n* Cloud Firestore delete mode: (Only applicable if you use the `Cloud Firestore paths` parameter.) How do you want to delete Cloud Firestore documents? To also delete documents in subcollections, set this parameter to `recursive`.\n\n* Realtime Database paths: Which paths in your Realtime Database instance contain user data? Leave empty if you don't use Realtime Database.\nEnter the full paths, separated by commas. You can represent the User ID of the deleted user with `{UID}`.\nFor example: `users/{UID},admins/{UID}`.\n\n* Cloud Storage paths: Where in Google Cloud Storage do you store user data? Leave empty if you don't use Cloud Storage.\nEnter the full paths to files or directories in your Storage buckets, separated by commas. Use `{UID}` to represent the User ID of the deleted user, and use `{DEFAULT}` to represent your default Storage bucket.\nHere's a series of examples. To delete all the files in your default bucket with the file naming scheme `{UID}-pic.png`, enter `{DEFAULT}/{UID}-pic.png`. To also delete all the files in another bucket called my-app-logs with the file naming scheme `{UID}-logs.txt`, enter `{DEFAULT}/{UID}-pic.png,my-app-logs/{UID}-logs.txt`. To *also* delete a User ID-labeled directory and all its files (like `media/{UID}`), enter `{DEFAULT}/{UID}-pic.png,my-app-logs/{UID}-logs.txt,{DEFAULT}/media/{UID}`.\n\n\n\n**Cloud Functions:**\n\n* **clearData:** Listens for user accounts to be deleted from your project's authenticated users, then removes any associated user data (based on Firebase Authentication's User ID) from Realtime Database, Cloud Firestore, and/or Cloud Storage.\n\n\n\n**Access Required**:\n\n\n\nThis extension will operate with the following project IAM roles:\n\n* datastore.owner (Reason: Allows the extension to delete (user) data from Cloud Firestore.)\n\n* firebasedatabase.admin (Reason: Allows the extension to delete (user) data from Realtime Database.)\n\n* storage.admin (Reason: Allows the extension to delete (user) data from Cloud Storage.)\n",
              "displayName": "Delete User Data"
            },
            "fetchTime": "2020-09-10T17:15:34.524525Z",
            "lastOperationName": "projects/firebasemods/operations/9f15b6b8-3bc7-477f-b343-6ed33ecc1e42",
            "state": "ACTIVE"
          },
          "params": {
            "FIRESTORE_DELETE_MODE": "shallow",
            "LOCATION": "us-central1"
          },
          "populatedPostinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Authentication dashboard](https://console.firebase.google.com/project/akeela-development-291705/authentication/users) in the Firebase console.\n\n1.  Click **Add User** to add a test user, then copy the test user's UID to your clipboard.\n\n1.  Create a new Cloud Firestore document, a new Realtime Database entry, or upload a new file to Storage - incorporating the user's UID into the path according to the schema that you configured.\n\n1.  Go back to your [Authentication dashboard](https://console.firebase.google.com/project/akeela-development-291705/authentication/users), then delete the test user.\n\n1.  In a few seconds, the new data you added above will be deleted from Cloud Firestore, Realtime Database, and/or Storage (depending on what you configured).\n\n### Using the extension\n\nWhen a user's account is deleted from your project's authenticated users, this extension automatically deletes their data from Cloud Firestore, Realtime Database, and/or Cloud Storage.\n\n* Cloud Firestore path(s): `{ unspecified parameter }`\n* Realtime Database path(s): `{ unspecified parameter }`\n* Cloud Storage path(s): `{ unspecified parameter }`\n\nYou can delete a user directly in your [Authentication dashboard]((https://console.firebase.google.com/project/akeela-development-291705/authentication/users)) or by using one of the Firebase Authentication SDKs. Learn more in the [Authentication documentation](https://firebase.google.com/docs/auth).\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n"
        },
        "errorStatus": {
          "code": 13,
          "message": "; RESOURCE_ERROR at /deployments/firebase-ext-delete-user-data/resources/mods-api-enable-iam: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/iam.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-delete-user-data/resources/mods-api-enable-firebase: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/firebase.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-delete-user-data/resources/mods-api-enable-cloudfunctions: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudfunctions.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-delete-user-data/resources/mods-api-enable-cloudbuild: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudbuild.googleapis.com:enable\",\"httpMethod\":\"POST\"}}"
        },
        "lastOperationName": "projects/akeela-development-291705/operations/e93ed3c5-bbd7-42ab-8c46-9f7d9e33f7b8",
        "serviceAccountEmail": "ext-delete-user-data@akeela-development-291705.iam.gserviceaccount.com",
        "lastOperationType": "CREATE"
      },
      {
        "name": "projects/akeela-development-291705/instances/storage-resize-images-mpny",
        "createTime": "2020-11-21T21:12:05.787099Z",
        "updateTime": "2020-11-27T06:46:09.108864Z",
        "state": "ERRORED",
        "config": {
          "name": "projects/akeela-development-291705/instances/storage-resize-images-mpny/configurations/623a7713-38ef-4eda-b211-a53809d04c7d",
          "createTime": "2020-11-21T21:12:05.787099Z",
          "source": {
            "name": "projects/firebasemods/sources/5aeb07f0-3f2e-42d6-b5f5-ae63b9e5fd99",
            "packageUri": "https://storage.googleapis.com/firebase-ext-eap-uploads/firebase-archive-901800eqysDNSgElb.zip?alt=media",
            "hash": "bd8a63899ae4900c93c1f7f937c6ce1980572cb1fab47e6b2fe2ebb9766d4806",
            "extensionRoot": "/",
            "spec": {
              "specVersion": "v1beta",
              "name": "storage-resize-images",
              "version": "0.1.14",
              "description": "Resizes images uploaded to Cloud Storage to a specified size, and optionally keeps or deletes the original image.",
              "apis": [
                {
                  "apiName": "storage-component.googleapis.com",
                  "reason": "Needed to use Cloud Storage"
                }
              ],
              "roles": [
                {
                  "role": "storage.admin",
                  "reason": "Allows the extension to store resized images in Cloud Storage"
                }
              ],
              "resources": [
                {
                  "name": "generateResizedImage",
                  "type": "firebaseextensions.v1beta.function",
                  "propertiesYaml": "availableMemoryMb: 1024\neventTrigger:\n  eventType: google.storage.object.finalize\n  resource: projects/_/buckets/${param:IMG_BUCKET}\nlocation: ${param:LOCATION}\nruntime: nodejs10\nsourceDirectory: .\n",
                  "description": "Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, then stores the resized images in the same bucket. Optionally keeps or deletes the original images.",
                  "deletionPolicy": "DELETE"
                }
              ],
              "billingRequired": true,
              "author": {
                "authorName": "Firebase",
                "url": "https://firebase.google.com"
              },
              "contributors": [
                {
                  "authorName": "Tina Liang",
                  "url": "https://github.com/tinaliang"
                },
                {
                  "authorName": "Chris Bianca",
                  "email": "chris@csfrequency.com",
                  "url": "https://github.com/chrisbianca"
                },
                {
                  "authorName": "Mike Diarmid",
                  "email": "mike@invertase.io",
                  "url": "https://github.com/salakar"
                }
              ],
              "license": "Apache-2.0",
              "releaseNotesUrl": "https://github.com/firebase/extensions/blob/master/storage-resize-images/CHANGELOG.md",
              "sourceUrl": "https://github.com/firebase/extensions/tree/master/storage-resize-images",
              "params": [
                {
                  "param": "LOCATION",
                  "label": "Cloud Functions location",
                  "type": "SELECT",
                  "description": "Where do you want to deploy the functions created for this extension? You usually want a location close to your Storage bucket. For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).",
                  "required": true,
                  "options": [
                    {
                      "value": "us-central1",
                      "label": "Iowa (us-central1)"
                    },
                    {
                      "value": "us-east1",
                      "label": "South Carolina (us-east1)"
                    },
                    {
                      "value": "us-east4",
                      "label": "Northern Virginia (us-east4)"
                    },
                    {
                      "value": "us-west2",
                      "label": "Los Angeles (us-west2)"
                    },
                    {
                      "value": "us-west3",
                      "label": "Salt Lake City (us-west3)"
                    },
                    {
                      "value": "us-west4",
                      "label": "Las Vegas (us-west4)"
                    },
                    {
                      "value": "europe-west1",
                      "label": "Belgium (europe-west1)"
                    },
                    {
                      "value": "europe-west2",
                      "label": "London (europe-west2)"
                    },
                    {
                      "value": "europe-west3",
                      "label": "Frankfurt (europe-west3)"
                    },
                    {
                      "value": "europe-west6",
                      "label": "Zurich (europe-west6)"
                    },
                    {
                      "value": "asia-east2",
                      "label": "Hong Kong (asia-east2)"
                    },
                    {
                      "value": "asia-northeast1",
                      "label": "Tokyo (asia-northeast1)"
                    },
                    {
                      "value": "asia-northeast2",
                      "label": "Osaka (asia-northeast2)"
                    },
                    {
                      "value": "asia-northeast3",
                      "label": "Seoul (asia-northeast3)"
                    },
                    {
                      "value": "asia-south1",
                      "label": "Mumbai (asia-south1)"
                    },
                    {
                      "value": "asia-southeast2",
                      "label": "Jakarta (asia-southeast2)"
                    },
                    {
                      "value": "northamerica-northeast1",
                      "label": "Montreal (northamerica-northeast1)"
                    },
                    {
                      "value": "southamerica-east1",
                      "label": "Sao Paulo (southamerica-east1)"
                    },
                    {
                      "value": "australia-southeast1",
                      "label": "Sydney (australia-southeast1)"
                    }
                  ],
                  "default": "us-central1",
                  "immutable": true
                },
                {
                  "param": "IMG_BUCKET",
                  "label": "Cloud Storage bucket for images",
                  "type": "STRING",
                  "description": "To which Cloud Storage bucket will you upload images that you want to resize? Resized images will be stored in this bucket. Depending on your extension configuration, original images are either kept or deleted.\n",
                  "required": true,
                  "default": "${STORAGE_BUCKET}",
                  "example": "my-project-12345.appspot.com",
                  "validationRegex": "^([0-9a-z_.-]*)$",
                  "validationErrorMessage": "Invalid storage bucket"
                },
                {
                  "param": "IMG_SIZES",
                  "label": "Sizes of resized images",
                  "type": "STRING",
                  "description": "What sizes of images would you like (in pixels)? Enter the sizes as a comma-separated list of WIDTHxHEIGHT values. Learn more about [how this parameter works](https://firebase.google.com/products/extensions/storage-resize-images).\n",
                  "required": true,
                  "default": "200x200",
                  "example": "200x200",
                  "validationRegex": "^\\d+x(\\d+,\\d+x)*\\d+$",
                  "validationErrorMessage": "Invalid sizes, must be a comma-separated list of WIDTHxHEIGHT values."
                },
                {
                  "param": "DELETE_ORIGINAL_FILE",
                  "label": "Deletion of original file",
                  "type": "SELECT",
                  "description": "Do you want to automatically delete the original file from the Cloud Storage bucket? Note that these deletions cannot be undone.",
                  "required": true,
                  "options": [
                    {
                      "value": "true",
                      "label": "Yes"
                    },
                    {
                      "value": "false",
                      "label": "No"
                    },
                    {
                      "value": "on_success",
                      "label": "Delete on successful resize"
                    }
                  ],
                  "default": "false"
                },
                {
                  "param": "RESIZED_IMAGES_PATH",
                  "label": "Cloud Storage path for resized images",
                  "type": "STRING",
                  "description": "A relative path in which to store resized images. For example, if you specify a path here of `thumbs` and you upload an image to `/images/original.jpg`, then the resized image is stored at `/images/thumbs/original_200x200.jpg`. If you prefer to store resized images at the root of your bucket, leave this field empty.\n",
                  "example": "thumbnails"
                },
                {
                  "param": "CACHE_CONTROL_HEADER",
                  "label": "Cache-Control header for resized images",
                  "type": "STRING",
                  "description": "This extension automatically copies any `Cache-Control` metadata from the original image to the resized images. For the resized images, do you want to overwrite this copied `Cache-Control` metadata or add `Cache-Control` metadata? Learn more about [`Cache-Control` headers](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). If you prefer not to overwrite or add `Cache-Control` metadata, leave this field empty.\n",
                  "example": "max-age=86400"
                },
                {
                  "param": "IMAGE_TYPE",
                  "label": "Convert image to preferred type",
                  "type": "SELECT",
                  "description": "The image type you'd like your source image to convert to. The default for this option will  be to keep the original file type.\n",
                  "options": [
                    {
                      "value": "jpg",
                      "label": "jpg"
                    },
                    {
                      "value": "png",
                      "label": "png"
                    },
                    {
                      "value": "webp",
                      "label": "webp"
                    },
                    {
                      "value": "tiff",
                      "label": "tiff"
                    },
                    {
                      "value": "false",
                      "label": "Do not convert"
                    }
                  ],
                  "default": "false"
                }
              ],
              "preinstallContent": "Use this extension to create resized versions of an image uploaded to a Cloud Storage bucket.\n\nWhen you upload an image file to your specified Cloud Storage bucket, this extension:\n\n- Creates a resized image with your specified dimensions.\n- Names the resized image using the same name as the original uploaded image, but suffixed with your specified width and height.\n- Stores the resized image in the same Storage bucket as the original uploaded image.\n\nYou can even configure the extension to create resized images of different dimensions for each original image upload. For example, you might want images that are 200x200, 400x400, and 680x680 - this extension can create these three resized images then store them in your bucket.\n\nThe extension automatically copies the following metadata, if present, from the original image to the resized image(s): `Cache-Control`, `Content-Disposition`, `Content-Encoding`, `Content-Language`, `Content-Type`, and user-provided metadata (a new Firebase storage download token will be generated on the resized image(s) if the original metadata contains a token). Note that you can optionally configure the extension to overwrite the [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control) value for the resized image(s).\n\n#### Detailed configuration information\n\nTo configure this extension, you specify a maximum width and a maximum height (in pixels, px). This extension keeps the aspect ratio of uploaded images constant and shrinks the image until the resized image's dimensions are at or under your specified max width and height.\n\nFor example, say that you specify a max width of 200px and a max height of 100px. You upload an image that is 480px wide by 640px high, which means a 0.75 aspect ratio. The final resized image will be 75px wide by 100px high to maintain the aspect ratio while also being at or under both of your maximum specified dimensions.\n\n#### Additional setup\n\nBefore installing this extension, make sure that you've [set up a Cloud Storage bucket](https://firebase.google.com/docs/storage) in your Firebase project.\n\n#### Billing\n \nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n \n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n - Cloud Storage\n - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n",
              "postinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Storage dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/storage) in the Firebase console.\n\n1.  Upload an image file to the bucket: `${param:IMG_BUCKET}`\n\n1.  In a few seconds, the resized image(s) appear in the same bucket.\n\n    Note that you might need to refresh the page to see changes.\n\n### Using the extension\n\nYou can upload images using the [Cloud Storage for Firebase SDK](https://firebase.google.com/docs/storage/) for your platform (iOS, Android, or Web). Alternatively, you can upload images directly in the Firebase console's Storage dashboard.\n\nWhenever you upload an image file to `${param:IMG_BUCKET}`, this extension does the following:\n\n- Creates resized image(s) with your specfied dimensions.\n- Names resized image(s) using the same name as the original uploaded image, but suffixed with the specified width and height.\n- Stores the resized image(s) in the bucket `${param:IMG_BUCKET}` (and, if configured, under the path `${param:RESIZED_IMAGES_PATH}`).\n\nThe extension also copies the following [metadata](https://cloud.google.com/storage/docs/metadata#mutable), if present, from the original image to the resized image(s):\n\n- `Cache-Control`\n- `Content-Disposition`\n- `Content-Encoding`\n- `Content-Language`\n- `Content-Type`\n- [user-provided metadata](https://cloud.google.com/storage/docs/metadata#custom-metadata)\n - If the original image contains a download token (publically accessible via a unique download URL), a new download token is generated for the resized image(s). \n - If the orginal image does not contain a download token, resized image(s) will not be created with unique tokens. To make a resized image publically accessible, call the [`getDownloadURL`](https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getdownloadurl) method.\n\nBe aware of the following when using this extension:\n\n- Each original image must have a valid [image MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#Image_types) specified in its [`Content-Type` metadata](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) (for example, `image/png`).\n\n- If you configured the `Cache-Control header for resized images` parameter, your specified value will overwrite the value copied from the original image. Learn more about image metadata in the [Cloud Storage documentation](https://firebase.google.com/docs/storage/).\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n",
              "readmeContent": "# Resize Images\n\n**Author**: Firebase (**[https://firebase.google.com](https://firebase.google.com)**)\n\n**Description**: Resizes images uploaded to Cloud Storage to a specified size, and optionally keeps or deletes the original image.\n\n\n\n**Details**: Use this extension to create resized versions of an image uploaded to a Cloud Storage bucket.\n\nWhen you upload an image file to your specified Cloud Storage bucket, this extension:\n\n- Creates a resized image with your specified dimensions.\n- Names the resized image using the same name as the original uploaded image, but suffixed with your specified width and height.\n- Stores the resized image in the same Storage bucket as the original uploaded image.\n\nYou can even configure the extension to create resized images of different dimensions for each original image upload. For example, you might want images that are 200x200, 400x400, and 680x680 - this extension can create these three resized images then store them in your bucket.\n\nThe extension automatically copies the following metadata, if present, from the original image to the resized image(s): `Cache-Control`, `Content-Disposition`, `Content-Encoding`, `Content-Language`, `Content-Type`, and user-provided metadata (a new Firebase storage download token will be generated on the resized image(s) if the original metadata contains a token). Note that you can optionally configure the extension to overwrite the [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control) value for the resized image(s).\n\n#### Detailed configuration information\n\nTo configure this extension, you specify a maximum width and a maximum height (in pixels, px). This extension keeps the aspect ratio of uploaded images constant and shrinks the image until the resized image's dimensions are at or under your specified max width and height.\n\nFor example, say that you specify a max width of 200px and a max height of 100px. You upload an image that is 480px wide by 640px high, which means a 0.75 aspect ratio. The final resized image will be 75px wide by 100px high to maintain the aspect ratio while also being at or under both of your maximum specified dimensions.\n\n#### Additional setup\n\nBefore installing this extension, make sure that you've [set up a Cloud Storage bucket](https://firebase.google.com/docs/storage) in your Firebase project.\n\n#### Billing\n \nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n \n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n - Cloud Storage\n - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n\n\n\n\n**Configuration Parameters:**\n\n* Cloud Functions location: Where do you want to deploy the functions created for this extension? You usually want a location close to your Storage bucket. For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).\n\n* Cloud Storage bucket for images: To which Cloud Storage bucket will you upload images that you want to resize? Resized images will be stored in this bucket. Depending on your extension configuration, original images are either kept or deleted.\n\n\n* Sizes of resized images: What sizes of images would you like (in pixels)? Enter the sizes as a comma-separated list of WIDTHxHEIGHT values. Learn more about [how this parameter works](https://firebase.google.com/products/extensions/storage-resize-images).\n\n\n* Deletion of original file: Do you want to automatically delete the original file from the Cloud Storage bucket? Note that these deletions cannot be undone.\n\n* Cloud Storage path for resized images: A relative path in which to store resized images. For example, if you specify a path here of `thumbs` and you upload an image to `/images/original.jpg`, then the resized image is stored at `/images/thumbs/original_200x200.jpg`. If you prefer to store resized images at the root of your bucket, leave this field empty.\n\n\n* Cache-Control header for resized images: This extension automatically copies any `Cache-Control` metadata from the original image to the resized images. For the resized images, do you want to overwrite this copied `Cache-Control` metadata or add `Cache-Control` metadata? Learn more about [`Cache-Control` headers](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). If you prefer not to overwrite or add `Cache-Control` metadata, leave this field empty.\n\n\n* Convert image to preferred type: The image type you'd like your source image to convert to. The default for this option will  be to keep the original file type.\n\n\n\n\n**Cloud Functions:**\n\n* **generateResizedImage:** Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, then stores the resized images in the same bucket. Optionally keeps or deletes the original images.\n\n\n\n**APIs Used**:\n\n* storage-component.googleapis.com (Reason: Needed to use Cloud Storage)\n\n\n\n**Access Required**:\n\n\n\nThis extension will operate with the following project IAM roles:\n\n* storage.admin (Reason: Allows the extension to store resized images in Cloud Storage)\n",
              "displayName": "Resize Images"
            },
            "fetchTime": "2020-11-19T19:40:37.624195Z",
            "lastOperationName": "projects/firebasemods/operations/417dabea-399e-4c12-a8d1-0282fcafe31c",
            "state": "ACTIVE"
          },
          "params": {
            "IMAGE_TYPE": "false",
            "LOCATION": "australia-southeast1",
            "IMG_BUCKET": "akeela-development-291705.appspot.com",
            "IMG_SIZES": "200x200,500x500,1000x1000",
            "DELETE_ORIGINAL_FILE": "on_success",
            "RESIZED_IMAGES_PATH": "thumbs"
          },
          "populatedPostinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Storage dashboard](https://console.firebase.google.com/project/akeela-development-291705/storage) in the Firebase console.\n\n1.  Upload an image file to the bucket: `akeela-development-291705.appspot.com`\n\n1.  In a few seconds, the resized image(s) appear in the same bucket.\n\n    Note that you might need to refresh the page to see changes.\n\n### Using the extension\n\nYou can upload images using the [Cloud Storage for Firebase SDK](https://firebase.google.com/docs/storage/) for your platform (iOS, Android, or Web). Alternatively, you can upload images directly in the Firebase console's Storage dashboard.\n\nWhenever you upload an image file to `akeela-development-291705.appspot.com`, this extension does the following:\n\n- Creates resized image(s) with your specfied dimensions.\n- Names resized image(s) using the same name as the original uploaded image, but suffixed with the specified width and height.\n- Stores the resized image(s) in the bucket `akeela-development-291705.appspot.com` (and, if configured, under the path `thumbs`).\n\nThe extension also copies the following [metadata](https://cloud.google.com/storage/docs/metadata#mutable), if present, from the original image to the resized image(s):\n\n- `Cache-Control`\n- `Content-Disposition`\n- `Content-Encoding`\n- `Content-Language`\n- `Content-Type`\n- [user-provided metadata](https://cloud.google.com/storage/docs/metadata#custom-metadata)\n - If the original image contains a download token (publically accessible via a unique download URL), a new download token is generated for the resized image(s). \n - If the orginal image does not contain a download token, resized image(s) will not be created with unique tokens. To make a resized image publically accessible, call the [`getDownloadURL`](https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getdownloadurl) method.\n\nBe aware of the following when using this extension:\n\n- Each original image must have a valid [image MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#Image_types) specified in its [`Content-Type` metadata](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) (for example, `image/png`).\n\n- If you configured the `Cache-Control header for resized images` parameter, your specified value will overwrite the value copied from the original image. Learn more about image metadata in the [Cloud Storage documentation](https://firebase.google.com/docs/storage/).\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n"
        },
        "errorStatus": {
          "code": 13,
          "message": "; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images-mpny/resources/mods-api-enable-firebase: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/firebase.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images-mpny/resources/mods-api-enable-cloudfunctions: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudfunctions.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images-mpny/resources/mods-api-enable-iam: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/iam.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images-mpny/resources/mods-api-enable-cloudbuild: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudbuild.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images-mpny/resources/mods-api-enable-storage-component: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/storage-component.googleapis.com:enable\",\"httpMethod\":\"POST\"}}"
        },
        "lastOperationName": "projects/akeela-development-291705/operations/175c1114-d1ed-48b2-b9f9-9ed43d79d397",
        "serviceAccountEmail": "ext-storage-resize-images-mpny@akeela-development-291705.iam.gserviceaccount.com",
        "lastOperationType": "DELETE"
      },
      {
        "name": "projects/akeela-development-291705/instances/storage-resize-images",
        "createTime": "2020-11-21T20:17:22.347905Z",
        "updateTime": "2020-11-21T21:32:52.610549Z",
        "state": "ERRORED",
        "config": {
          "name": "projects/akeela-development-291705/instances/storage-resize-images/configurations/09e41da0-bc15-446f-bab2-fb4b657f1d8f",
          "createTime": "2020-11-21T20:21:46.025090Z",
          "source": {
            "name": "projects/firebasemods/sources/5aeb07f0-3f2e-42d6-b5f5-ae63b9e5fd99",
            "packageUri": "https://storage.googleapis.com/firebase-ext-eap-uploads/firebase-archive-901800eqysDNSgElb.zip?alt=media",
            "hash": "bd8a63899ae4900c93c1f7f937c6ce1980572cb1fab47e6b2fe2ebb9766d4806",
            "extensionRoot": "/",
            "spec": {
              "specVersion": "v1beta",
              "name": "storage-resize-images",
              "version": "0.1.14",
              "description": "Resizes images uploaded to Cloud Storage to a specified size, and optionally keeps or deletes the original image.",
              "apis": [
                {
                  "apiName": "storage-component.googleapis.com",
                  "reason": "Needed to use Cloud Storage"
                }
              ],
              "roles": [
                {
                  "role": "storage.admin",
                  "reason": "Allows the extension to store resized images in Cloud Storage"
                }
              ],
              "resources": [
                {
                  "name": "generateResizedImage",
                  "type": "firebaseextensions.v1beta.function",
                  "propertiesYaml": "availableMemoryMb: 1024\neventTrigger:\n  eventType: google.storage.object.finalize\n  resource: projects/_/buckets/${param:IMG_BUCKET}\nlocation: ${param:LOCATION}\nruntime: nodejs10\nsourceDirectory: .\n",
                  "description": "Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, then stores the resized images in the same bucket. Optionally keeps or deletes the original images.",
                  "deletionPolicy": "DELETE"
                }
              ],
              "billingRequired": true,
              "author": {
                "authorName": "Firebase",
                "url": "https://firebase.google.com"
              },
              "contributors": [
                {
                  "authorName": "Tina Liang",
                  "url": "https://github.com/tinaliang"
                },
                {
                  "authorName": "Chris Bianca",
                  "email": "chris@csfrequency.com",
                  "url": "https://github.com/chrisbianca"
                },
                {
                  "authorName": "Mike Diarmid",
                  "email": "mike@invertase.io",
                  "url": "https://github.com/salakar"
                }
              ],
              "license": "Apache-2.0",
              "releaseNotesUrl": "https://github.com/firebase/extensions/blob/master/storage-resize-images/CHANGELOG.md",
              "sourceUrl": "https://github.com/firebase/extensions/tree/master/storage-resize-images",
              "params": [
                {
                  "param": "LOCATION",
                  "label": "Cloud Functions location",
                  "type": "SELECT",
                  "description": "Where do you want to deploy the functions created for this extension? You usually want a location close to your Storage bucket. For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).",
                  "required": true,
                  "options": [
                    {
                      "value": "us-central1",
                      "label": "Iowa (us-central1)"
                    },
                    {
                      "value": "us-east1",
                      "label": "South Carolina (us-east1)"
                    },
                    {
                      "value": "us-east4",
                      "label": "Northern Virginia (us-east4)"
                    },
                    {
                      "value": "us-west2",
                      "label": "Los Angeles (us-west2)"
                    },
                    {
                      "value": "us-west3",
                      "label": "Salt Lake City (us-west3)"
                    },
                    {
                      "value": "us-west4",
                      "label": "Las Vegas (us-west4)"
                    },
                    {
                      "value": "europe-west1",
                      "label": "Belgium (europe-west1)"
                    },
                    {
                      "value": "europe-west2",
                      "label": "London (europe-west2)"
                    },
                    {
                      "value": "europe-west3",
                      "label": "Frankfurt (europe-west3)"
                    },
                    {
                      "value": "europe-west6",
                      "label": "Zurich (europe-west6)"
                    },
                    {
                      "value": "asia-east2",
                      "label": "Hong Kong (asia-east2)"
                    },
                    {
                      "value": "asia-northeast1",
                      "label": "Tokyo (asia-northeast1)"
                    },
                    {
                      "value": "asia-northeast2",
                      "label": "Osaka (asia-northeast2)"
                    },
                    {
                      "value": "asia-northeast3",
                      "label": "Seoul (asia-northeast3)"
                    },
                    {
                      "value": "asia-south1",
                      "label": "Mumbai (asia-south1)"
                    },
                    {
                      "value": "asia-southeast2",
                      "label": "Jakarta (asia-southeast2)"
                    },
                    {
                      "value": "northamerica-northeast1",
                      "label": "Montreal (northamerica-northeast1)"
                    },
                    {
                      "value": "southamerica-east1",
                      "label": "Sao Paulo (southamerica-east1)"
                    },
                    {
                      "value": "australia-southeast1",
                      "label": "Sydney (australia-southeast1)"
                    }
                  ],
                  "default": "us-central1",
                  "immutable": true
                },
                {
                  "param": "IMG_BUCKET",
                  "label": "Cloud Storage bucket for images",
                  "type": "STRING",
                  "description": "To which Cloud Storage bucket will you upload images that you want to resize? Resized images will be stored in this bucket. Depending on your extension configuration, original images are either kept or deleted.\n",
                  "required": true,
                  "default": "${STORAGE_BUCKET}",
                  "example": "my-project-12345.appspot.com",
                  "validationRegex": "^([0-9a-z_.-]*)$",
                  "validationErrorMessage": "Invalid storage bucket"
                },
                {
                  "param": "IMG_SIZES",
                  "label": "Sizes of resized images",
                  "type": "STRING",
                  "description": "What sizes of images would you like (in pixels)? Enter the sizes as a comma-separated list of WIDTHxHEIGHT values. Learn more about [how this parameter works](https://firebase.google.com/products/extensions/storage-resize-images).\n",
                  "required": true,
                  "default": "200x200",
                  "example": "200x200",
                  "validationRegex": "^\\d+x(\\d+,\\d+x)*\\d+$",
                  "validationErrorMessage": "Invalid sizes, must be a comma-separated list of WIDTHxHEIGHT values."
                },
                {
                  "param": "DELETE_ORIGINAL_FILE",
                  "label": "Deletion of original file",
                  "type": "SELECT",
                  "description": "Do you want to automatically delete the original file from the Cloud Storage bucket? Note that these deletions cannot be undone.",
                  "required": true,
                  "options": [
                    {
                      "value": "true",
                      "label": "Yes"
                    },
                    {
                      "value": "false",
                      "label": "No"
                    },
                    {
                      "value": "on_success",
                      "label": "Delete on successful resize"
                    }
                  ],
                  "default": "false"
                },
                {
                  "param": "RESIZED_IMAGES_PATH",
                  "label": "Cloud Storage path for resized images",
                  "type": "STRING",
                  "description": "A relative path in which to store resized images. For example, if you specify a path here of `thumbs` and you upload an image to `/images/original.jpg`, then the resized image is stored at `/images/thumbs/original_200x200.jpg`. If you prefer to store resized images at the root of your bucket, leave this field empty.\n",
                  "example": "thumbnails"
                },
                {
                  "param": "CACHE_CONTROL_HEADER",
                  "label": "Cache-Control header for resized images",
                  "type": "STRING",
                  "description": "This extension automatically copies any `Cache-Control` metadata from the original image to the resized images. For the resized images, do you want to overwrite this copied `Cache-Control` metadata or add `Cache-Control` metadata? Learn more about [`Cache-Control` headers](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). If you prefer not to overwrite or add `Cache-Control` metadata, leave this field empty.\n",
                  "example": "max-age=86400"
                },
                {
                  "param": "IMAGE_TYPE",
                  "label": "Convert image to preferred type",
                  "type": "SELECT",
                  "description": "The image type you'd like your source image to convert to. The default for this option will  be to keep the original file type.\n",
                  "options": [
                    {
                      "value": "jpg",
                      "label": "jpg"
                    },
                    {
                      "value": "png",
                      "label": "png"
                    },
                    {
                      "value": "webp",
                      "label": "webp"
                    },
                    {
                      "value": "tiff",
                      "label": "tiff"
                    },
                    {
                      "value": "false",
                      "label": "Do not convert"
                    }
                  ],
                  "default": "false"
                }
              ],
              "preinstallContent": "Use this extension to create resized versions of an image uploaded to a Cloud Storage bucket.\n\nWhen you upload an image file to your specified Cloud Storage bucket, this extension:\n\n- Creates a resized image with your specified dimensions.\n- Names the resized image using the same name as the original uploaded image, but suffixed with your specified width and height.\n- Stores the resized image in the same Storage bucket as the original uploaded image.\n\nYou can even configure the extension to create resized images of different dimensions for each original image upload. For example, you might want images that are 200x200, 400x400, and 680x680 - this extension can create these three resized images then store them in your bucket.\n\nThe extension automatically copies the following metadata, if present, from the original image to the resized image(s): `Cache-Control`, `Content-Disposition`, `Content-Encoding`, `Content-Language`, `Content-Type`, and user-provided metadata (a new Firebase storage download token will be generated on the resized image(s) if the original metadata contains a token). Note that you can optionally configure the extension to overwrite the [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control) value for the resized image(s).\n\n#### Detailed configuration information\n\nTo configure this extension, you specify a maximum width and a maximum height (in pixels, px). This extension keeps the aspect ratio of uploaded images constant and shrinks the image until the resized image's dimensions are at or under your specified max width and height.\n\nFor example, say that you specify a max width of 200px and a max height of 100px. You upload an image that is 480px wide by 640px high, which means a 0.75 aspect ratio. The final resized image will be 75px wide by 100px high to maintain the aspect ratio while also being at or under both of your maximum specified dimensions.\n\n#### Additional setup\n\nBefore installing this extension, make sure that you've [set up a Cloud Storage bucket](https://firebase.google.com/docs/storage) in your Firebase project.\n\n#### Billing\n \nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n \n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n - Cloud Storage\n - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n",
              "postinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Storage dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/storage) in the Firebase console.\n\n1.  Upload an image file to the bucket: `${param:IMG_BUCKET}`\n\n1.  In a few seconds, the resized image(s) appear in the same bucket.\n\n    Note that you might need to refresh the page to see changes.\n\n### Using the extension\n\nYou can upload images using the [Cloud Storage for Firebase SDK](https://firebase.google.com/docs/storage/) for your platform (iOS, Android, or Web). Alternatively, you can upload images directly in the Firebase console's Storage dashboard.\n\nWhenever you upload an image file to `${param:IMG_BUCKET}`, this extension does the following:\n\n- Creates resized image(s) with your specfied dimensions.\n- Names resized image(s) using the same name as the original uploaded image, but suffixed with the specified width and height.\n- Stores the resized image(s) in the bucket `${param:IMG_BUCKET}` (and, if configured, under the path `${param:RESIZED_IMAGES_PATH}`).\n\nThe extension also copies the following [metadata](https://cloud.google.com/storage/docs/metadata#mutable), if present, from the original image to the resized image(s):\n\n- `Cache-Control`\n- `Content-Disposition`\n- `Content-Encoding`\n- `Content-Language`\n- `Content-Type`\n- [user-provided metadata](https://cloud.google.com/storage/docs/metadata#custom-metadata)\n - If the original image contains a download token (publically accessible via a unique download URL), a new download token is generated for the resized image(s). \n - If the orginal image does not contain a download token, resized image(s) will not be created with unique tokens. To make a resized image publically accessible, call the [`getDownloadURL`](https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getdownloadurl) method.\n\nBe aware of the following when using this extension:\n\n- Each original image must have a valid [image MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#Image_types) specified in its [`Content-Type` metadata](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) (for example, `image/png`).\n\n- If you configured the `Cache-Control header for resized images` parameter, your specified value will overwrite the value copied from the original image. Learn more about image metadata in the [Cloud Storage documentation](https://firebase.google.com/docs/storage/).\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n",
              "readmeContent": "# Resize Images\n\n**Author**: Firebase (**[https://firebase.google.com](https://firebase.google.com)**)\n\n**Description**: Resizes images uploaded to Cloud Storage to a specified size, and optionally keeps or deletes the original image.\n\n\n\n**Details**: Use this extension to create resized versions of an image uploaded to a Cloud Storage bucket.\n\nWhen you upload an image file to your specified Cloud Storage bucket, this extension:\n\n- Creates a resized image with your specified dimensions.\n- Names the resized image using the same name as the original uploaded image, but suffixed with your specified width and height.\n- Stores the resized image in the same Storage bucket as the original uploaded image.\n\nYou can even configure the extension to create resized images of different dimensions for each original image upload. For example, you might want images that are 200x200, 400x400, and 680x680 - this extension can create these three resized images then store them in your bucket.\n\nThe extension automatically copies the following metadata, if present, from the original image to the resized image(s): `Cache-Control`, `Content-Disposition`, `Content-Encoding`, `Content-Language`, `Content-Type`, and user-provided metadata (a new Firebase storage download token will be generated on the resized image(s) if the original metadata contains a token). Note that you can optionally configure the extension to overwrite the [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control) value for the resized image(s).\n\n#### Detailed configuration information\n\nTo configure this extension, you specify a maximum width and a maximum height (in pixels, px). This extension keeps the aspect ratio of uploaded images constant and shrinks the image until the resized image's dimensions are at or under your specified max width and height.\n\nFor example, say that you specify a max width of 200px and a max height of 100px. You upload an image that is 480px wide by 640px high, which means a 0.75 aspect ratio. The final resized image will be 75px wide by 100px high to maintain the aspect ratio while also being at or under both of your maximum specified dimensions.\n\n#### Additional setup\n\nBefore installing this extension, make sure that you've [set up a Cloud Storage bucket](https://firebase.google.com/docs/storage) in your Firebase project.\n\n#### Billing\n \nTo install an extension, your project must be on the [Blaze (pay as you go) plan](https://firebase.google.com/pricing)\n \n- You will be charged a small amount (typically around $0.01/month) for the Firebase resources required by this extension (even if it is not used).\n- This extension uses other Firebase and Google Cloud Platform services, which have associated charges if you exceed the service’s free tier:\n - Cloud Storage\n - Cloud Functions (Node.js 10+ runtime. [See FAQs](https://firebase.google.com/support/faq#expandable-24))\n\n\n\n\n**Configuration Parameters:**\n\n* Cloud Functions location: Where do you want to deploy the functions created for this extension? You usually want a location close to your Storage bucket. For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).\n\n* Cloud Storage bucket for images: To which Cloud Storage bucket will you upload images that you want to resize? Resized images will be stored in this bucket. Depending on your extension configuration, original images are either kept or deleted.\n\n\n* Sizes of resized images: What sizes of images would you like (in pixels)? Enter the sizes as a comma-separated list of WIDTHxHEIGHT values. Learn more about [how this parameter works](https://firebase.google.com/products/extensions/storage-resize-images).\n\n\n* Deletion of original file: Do you want to automatically delete the original file from the Cloud Storage bucket? Note that these deletions cannot be undone.\n\n* Cloud Storage path for resized images: A relative path in which to store resized images. For example, if you specify a path here of `thumbs` and you upload an image to `/images/original.jpg`, then the resized image is stored at `/images/thumbs/original_200x200.jpg`. If you prefer to store resized images at the root of your bucket, leave this field empty.\n\n\n* Cache-Control header for resized images: This extension automatically copies any `Cache-Control` metadata from the original image to the resized images. For the resized images, do you want to overwrite this copied `Cache-Control` metadata or add `Cache-Control` metadata? Learn more about [`Cache-Control` headers](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). If you prefer not to overwrite or add `Cache-Control` metadata, leave this field empty.\n\n\n* Convert image to preferred type: The image type you'd like your source image to convert to. The default for this option will  be to keep the original file type.\n\n\n\n\n**Cloud Functions:**\n\n* **generateResizedImage:** Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, then stores the resized images in the same bucket. Optionally keeps or deletes the original images.\n\n\n\n**APIs Used**:\n\n* storage-component.googleapis.com (Reason: Needed to use Cloud Storage)\n\n\n\n**Access Required**:\n\n\n\nThis extension will operate with the following project IAM roles:\n\n* storage.admin (Reason: Allows the extension to store resized images in Cloud Storage)\n",
              "displayName": "Resize Images"
            },
            "fetchTime": "2020-11-19T19:40:37.624195Z",
            "lastOperationName": "projects/firebasemods/operations/417dabea-399e-4c12-a8d1-0282fcafe31c",
            "state": "ACTIVE"
          },
          "params": {
            "IMAGE_TYPE": "false",
            "LOCATION": "australia-southeast1",
            "IMG_BUCKET": "akeela-development-291705.appspot.com",
            "IMG_SIZES": "200x200,500x500,1000x1000",
            "DELETE_ORIGINAL_FILE": "on_success",
            "RESIZED_IMAGES_PATH": "thumbs"
          },
          "populatedPostinstallContent": "### See it in action\n\nYou can test out this extension right away!\n\n1.  Go to your [Storage dashboard](https://console.firebase.google.com/project/akeela-development-291705/storage) in the Firebase console.\n\n1.  Upload an image file to the bucket: `akeela-development-291705.appspot.com`\n\n1.  In a few seconds, the resized image(s) appear in the same bucket.\n\n    Note that you might need to refresh the page to see changes.\n\n### Using the extension\n\nYou can upload images using the [Cloud Storage for Firebase SDK](https://firebase.google.com/docs/storage/) for your platform (iOS, Android, or Web). Alternatively, you can upload images directly in the Firebase console's Storage dashboard.\n\nWhenever you upload an image file to `akeela-development-291705.appspot.com`, this extension does the following:\n\n- Creates resized image(s) with your specfied dimensions.\n- Names resized image(s) using the same name as the original uploaded image, but suffixed with the specified width and height.\n- Stores the resized image(s) in the bucket `akeela-development-291705.appspot.com` (and, if configured, under the path `thumbs`).\n\nThe extension also copies the following [metadata](https://cloud.google.com/storage/docs/metadata#mutable), if present, from the original image to the resized image(s):\n\n- `Cache-Control`\n- `Content-Disposition`\n- `Content-Encoding`\n- `Content-Language`\n- `Content-Type`\n- [user-provided metadata](https://cloud.google.com/storage/docs/metadata#custom-metadata)\n - If the original image contains a download token (publically accessible via a unique download URL), a new download token is generated for the resized image(s). \n - If the orginal image does not contain a download token, resized image(s) will not be created with unique tokens. To make a resized image publically accessible, call the [`getDownloadURL`](https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getdownloadurl) method.\n\nBe aware of the following when using this extension:\n\n- Each original image must have a valid [image MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#Image_types) specified in its [`Content-Type` metadata](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) (for example, `image/png`).\n\n- If you configured the `Cache-Control header for resized images` parameter, your specified value will overwrite the value copied from the original image. Learn more about image metadata in the [Cloud Storage documentation](https://firebase.google.com/docs/storage/).\n\n### Monitoring\n\nAs a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.\n"
        },
        "errorStatus": {
          "code": 13,
          "message": "; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images/resources/mods-api-enable-storage-component: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/storage-component.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images/resources/mods-api-enable-iam: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/iam.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images/resources/mods-api-enable-cloudbuild: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudbuild.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images/resources/mods-api-enable-firebase: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/firebase.googleapis.com:enable\",\"httpMethod\":\"POST\"}}; RESOURCE_ERROR at /deployments/firebase-ext-storage-resize-images/resources/mods-api-enable-cloudfunctions: {\"ResourceType\":\"deploymentmanager.v2.virtual.enableService\",\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\":\"The caller does not have permission\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\":\"Forbidden\",\"requestPath\":\"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudfunctions.googleapis.com:enable\",\"httpMethod\":\"POST\"}}"
        },
        "lastOperationName": "projects/akeela-development-291705/operations/db85c747-ef62-481c-9717-0687bcd7468d",
        "serviceAccountEmail": "ext-storage-resize-images@akeela-development-291705.iam.gserviceaccount.com",
        "lastOperationType": "DELETE"
      }
    ]
  }
}

This JSON suggests to me that I do not have the required permission to install/uninstall, however I am an owner of the project. I believe that through the docs this is the correct permission.

iam-permissions

Finally, this seems to be project specific. I can successfully install/uninstall extensions as the saem user in other projects.

Steps to reproduce:

  1. Navigate to firebase project akeela-development-291705
  2. Navigate to "Extensions"
  3. Install an Extension OR Uninstall an exisitng extension
Expected result
Actual result
russellwheatley commented 3 years ago

Hey @fergusfrl, thanks for raising this issue. This has me a little stumped. A couple of suggestions:

Generate a new service account key in the console and store the path to the key in this env variable: GOOGLE_APPLICATION_CREDENTIALS. This is used for the node admin SDK but I assume it's also used for the CLI.

Another one would be to logout and login (i.e firebase logout & firebase login on command line). It does sound like something may have failed silently during setup perhaps.

fergusfrl commented 3 years ago

Thanks for getting back to me Russell - join the "stumped" crew 😁

I created a Service Account with the "Owner" role then I the GOOGLE_APPLICATION_CREDENTIALS environment variable to point towards the associated JSON file for the Service Account. I then logged out and back in again.

Unfortunately when uninstalling an extension using the CLI I still get error:

RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-cloudfunctions: {"ResourceType":"deploymentmanager.v2.virtual.enableService","ResourceErrorCode":"403","ResourceErrorMessage":{"code":403,"message":"The caller does not have permission","status":"PERMISSION_DENIED","statusMessage":"Forbidden","requestPath":"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudfunctions.googleapis.com:enable","httpMethod":"POST"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-firebase: {"ResourceType":"deploymentmanager.v2.virtual.enableService","ResourceErrorCode":"403","ResourceErrorMessage":{"code":403,"message":"The caller does not have permission","status":"PERMISSION_DENIED","statusMessage":"Forbidden","requestPath":"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/firebase.googleapis.com:enable","httpMethod":"POST"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-iam: {"ResourceType":"deploymentmanager.v2.virtual.enableService","ResourceErrorCode":"403","ResourceErrorMessage":{"code":403,"message":"The caller does not have permission","status":"PERMISSION_DENIED","statusMessage":"Forbidden","requestPath":"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/iam.googleapis.com:enable","httpMethod":"POST"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-cloudbuild: {"ResourceType":"deploymentmanager.v2.virtual.enableService","ResourceErrorCode":"403","ResourceErrorMessage":{"code":403,"message":"The caller does not have permission","status":"PERMISSION_DENIED","statusMessage":"Forbidden","requestPath":"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/cloudbuild.googleapis.com:enable","httpMethod":"POST"}}; RESOURCE_ERROR at /deployments/firebase-ext-firestore-translate-text/resources/mods-api-enable-translate: {"ResourceType":"deploymentmanager.v2.virtual.enableService","ResourceErrorCode":"403","ResourceErrorMessage":{"code":403,"message":"The caller does not have permission","status":"PERMISSION_DENIED","statusMessage":"Forbidden","requestPath":"https://serviceusage.googleapis.com/v1/projects/akeela-development-291705/services/translate.googleapis.com:enable","httpMethod":"POST"}}

Is there anything further I can try?

russellwheatley commented 3 years ago

Yeah, it's really tough to debug this one. it could be something as trivial as not accepting an email after being invited to the project.

Here is the list of authentication methods used by the Firebase CLI tool in order of specificity. It might be worth having a play with that because it might be a sneaky credential being used that doesn't have permission.

Or you could save potentially a lot of fruitless debugging time, and create yourself as another owner? See if that works?

fergusfrl commented 3 years ago

Hey Russell, quick update. I unfortunately wasn't able to get this working. To resolve this problem I created a new project which is working as expected. I'm satisfied and do not require any more help on this ticket.

It's up to you whether you want to close this off or investigate further.

Thanks for all your help 💯

russellwheatley commented 3 years ago

Hey @fergusfrl, no problem, thanks for letting us know! I'm sorry it didn't work as expected but at least you have a working project now 😄

miguelgmarques commented 1 year ago

In my case Ideleted the service account

solved it by running gcloud projects add-iam-policy-binding aefct-dba1e --member serviceAccount:<PROJECT_NUMBER>@cloudservices.gserviceaccount.com --role roles/editor