Configurar o ambiente


Muitas vezes, você precisará de configuração adicional para suas funções, como chaves de API de terceiros ou configurações ajustáveis. O SDK do Firebase para Cloud Functions oferece uma configuração de ambiente integrada que facilita o armazenamento e a recuperação desse tipo de dados para o projeto.

Você pode escolher entre estas opções:

  • Configuração parametrizada (recomendada na maioria dos casos). Essa opção fornece uma configuração de ambiente fortemente identificada, com parâmetros que são validados no momento da implantação, o que evita erros e simplifica a depuração.
  • Configuração das variáveis de ambiente com base em arquivos. Com essa abordagem, você cria manualmente um arquivo dotenv para carregar variáveis de ambiente.

A configuração parametrizada é recomendada na maioria dos casos de uso. Essa abordagem disponibiliza valores de configuração no ambiente de execução e no momento da implantação. A menos que todos os parâmetros tenham um valor válido, a implantação é bloqueada. Por outro lado, a configuração com variáveis de ambiente não está disponível no momento da implantação.

Configuração parametrizada

O Cloud Functions for Firebase fornece uma interface para definir parâmetros de configuração de maneira declarativa na base de código. O valor desses parâmetros fica disponível quando as opções de implantação e ambiente de execução são configuradas, durante a implantação da função e durante a execução. Isso significa que a CLI vai bloquear a implantação até que todos os parâmetros tenham um valor válido.

Node.js

const { onRequest } = require('firebase-functions/v2/https');
const { defineInt, defineString } = require('firebase-functions/params');

// Define some parameters
const minInstancesConfig = defineInt('HELLO_WORLD_MININSTANCES');
const welcomeMessage = defineString('WELCOME_MESSAGE');

// To use configured parameters inside the config for a function, provide them
// directly. To use them at runtime, call .value() on them.
export const helloWorld = onRequest(
  { minInstances: minInstancesConfig },
(req, res) => {
    res.send(`${welcomeMessage.value()}! I am a function.`);
  }
);

Python

from firebase_functions import https_fn
from firebase_functions.params import IntParam, StringParam

MIN_INSTANCES = IntParam("HELLO_WORLD_MIN_INSTANCES")
WELCOME_MESSAGE = StringParam("WELCOME_MESSAGE")

# To use configured parameters inside the config for a function, provide them
# directly. To use them at runtime, call .value() on them.
@https_fn.on_request(min_instances=MIN_INSTANCES)
def hello_world(req):
    return https_fn.Response(f'{WELCOME_MESSAGE.value()}! I am a function!')

Ao implantar uma função com variáveis de configuração parametrizada, a CLI do Firebase primeiro tenta carregar os valores a partir de arquivos .env locais. Se eles não estiverem presentes nesses arquivos e nenhum default estiver definido, a CLI vai solicitar os valores durante a implantação e os salvar automaticamente em um arquivo .env com o nome .env.<project_ID> no diretório functions/:

$ firebase deploy
i  functions: preparing codebase default for deployment
? Enter a string value for ENVIRONMENT: prod
i  functions: Writing new parameter values to disk: .env.projectId
…
$ firebase deploy
i  functions: Loaded environment variables from .env.projectId

Dependendo do fluxo de trabalho de desenvolvimento, pode ser útil adicionar o arquivo .env.<project_ID> gerado ao controle de versões.

Como usar parâmetros no escopo global

Durante a implantação, o código das funções é carregado e inspecionado antes que os parâmetros tenham valores reais. Isso significa que a busca por valores de parâmetro durante o escopo global resulta em falha na implantação. Quando você quiser usar um parâmetro para inicializar um valor global, use o callback de inicialização onInit(). Esse callback é executado antes da execução de qualquer função na produção, mas não é chamado durante o tempo de implantação. Portanto, é um local seguro para acessar o valor de um parâmetro.

Node.js

const { GoogleGenerativeAI } = require('@google/generative-ai');
const { defineSecret } = require('firebase-functions/params');
const { onInit } = require('firebase-functions/v2/core');

const apiKey = defineSecret('GOOGLE_API_KEY');

let genAI;
onInit(() => {
  genAI = new GoogleGenerativeAI(apiKey.value());
})

Python

from firebase_functions.core import init
from firebase_functions.params import StringParam, PROJECT_ID
import firebase_admin
import vertexai

location = StringParam("LOCATION")

x = "hello"

@init
def initialize():
  # Note: to write back to a global, you'll need to use the "global" keyword
  # to avoid creating a new local with the same name.
  global x
  x = "world"
  firebase_admin.initialize_app()
  vertexai.init(PROJECT_ID.value, location.value)

Ao usar parâmetros do tipo Secret, eles estarão disponíveis apenas no processo de funções que vincularam o secret. Se um secret estiver vinculado a apenas algumas funções, verifique se secret.value() é falso antes de usá-lo.

Configurar o comportamento da CLI

Os parâmetros podem ser configurados com um objeto Options, que controla como a CLI solicita valores. O exemplo a seguir define opções para validar o formato de um número de telefone, oferecer uma opção de seleção simples e preencher automaticamente uma opção de seleção a partir de um projeto do Firebase:

Node.js

const { defineString } = require('firebase-functions/params');

const welcomeMessage = defineString('WELCOME_MESSAGE', {default: 'Hello World',
description: 'The greeting that is returned to the caller of this function'});

const onlyPhoneNumbers = defineString('PHONE_NUMBER', {
  input: {
    text: {
      validationRegex: /\d{3}-\d{3}-\d{4}/,
      validationErrorMessage: "Please enter
a phone number in the format XXX-YYY-ZZZZ"
    },
  },
});

const selectedOption = defineString('PARITY', {input: params.select(["odd", "even"])});

const memory = defineInt("MEMORY", {
  description: "How much memory do you need?",
  input: params.select({ "micro": 256, "chonky": 2048 }),
});

const extensions = defineList("EXTENSIONS", {
  description: "Which file types should be processed?",
  input: params.multiSelect(["jpg", "tiff", "png", "webp"]),
});

const storageBucket = defineString('BUCKET', {
  description: "This will automatically
populate the selector field with the deploying Cloud Project’s
storage buckets",
  input: params.PICK_STORAGE_BUCKET,
});

Python

from firebase_functions.params import (
    StringParam,
    ListParam,
    TextInput,
    SelectInput,
    SelectOptions,
    ResourceInput,
    ResourceType,
)

MIN_INSTANCES = IntParam("HELLO_WORLD_MIN_INSTANCES")

WELCOME_MESSAGE = StringParam(
    "WELCOME_MESSAGE",
    default="Hello World",
    description="The greeting that is returned to the caller of this function",
)

ONLY_PHONE_NUMBERS = StringParam(
    "PHONE_NUMBER",
    input=TextInput(
        validation_regex="\d{3}-\d{3}-\d{4}",
        validation_error_message="Please enter a phone number in the format XXX-YYY-XXX",
    ),
)

SELECT_OPTION = StringParam(
    "PARITY",
    input=SelectInput([SelectOptions(value="odd"), SelectOptions(value="even")]),
)

STORAGE_BUCKET = StringParam(
    "BUCKET",
    input=ResourceInput(type=ResourceType.STORAGE_BUCKET),
    description="This will automatically populate the selector field with the deploying Cloud Project's storage buckets",
)

Tipos de parâmetros

A configuração parametrizada oferece uma sólida identificação de tipo para valores de parâmetro, além de oferecer suporte para secrets do Secret Manager do Google Cloud. Estes são os tipos compatíveis:

  • Secret
  • String
  • Booleano
  • Número inteiro
  • Ponto flutuante
  • Lista (Node.js)

Valores de parâmetros e expressões

O Firebase avalia os parâmetros no momento da implantação e enquanto a função está em execução. Devido a esses ambientes duplos, é necessário ter mais cuidado na comparação dos valores de parâmetro e no uso deles para definir as opções de ambiente de execução para suas funções.

Faça a transmissão direta de um parâmetro para sua função se quiser que ele seja transmitido como uma opção de ambiente de execução:

Node.js

const { onRequest } = require('firebase-functions/v2/https');
const { defineInt } = require('firebase-functions/params');
const minInstancesConfig = defineInt('HELLO\_WORLD\_MININSTANCES');

export const helloWorld = onRequest(
  { minInstances: minInstancesConfig },
  (req, res) => {
    //…

Python

from firebase_functions import https_fn
from firebase_functions.params import IntParam

MIN_INSTANCES = IntParam("HELLO_WORLD_MIN_INSTANCES")

@https_fn.on_request(min_instances=MIN_INSTANCES)
def hello_world(req):
    ...

Além disso, se você tiver que fazer uma comparação com outro parâmetro para saber qual opção escolher, vai ter que usar comparadores integrados, em vez de verificar o valor:

Node.js

const { onRequest } = require('firebase-functions/v2/https');
const environment = params.defineString(ENVIRONMENT, {default: 'dev'});

// use built-in comparators
const minInstancesConfig = environment.equals('PRODUCTION').thenElse(10, 1);
export const helloWorld = onRequest(
  { minInstances: minInstancesConfig },
  (req, res) => {
    //…

Python

from firebase_functions import https_fn
from firebase_functions.params import IntParam, StringParam

ENVIRONMENT = StringParam("ENVIRONMENT", default="dev")
MIN_INSTANCES = ENVIRONMENT.equals("PRODUCTION").then(10, 0)

@https_fn.on_request(min_instances=MIN_INSTANCES)
def hello_world(req):
    ...

Os parâmetros e as expressões de parâmetro usados apenas no ambiente de execução podem ser acessados com a função value deles:

Node.js

const { onRequest } = require('firebase-functions/v2/https');
const { defineString } = require('firebase-functions/params');
const welcomeMessage = defineString('WELCOME_MESSAGE');

// To use configured parameters inside the config for a function, provide them
// directly. To use them at runtime, call .value() on them.
export const helloWorld = onRequest(
(req, res) => {
    res.send(`${welcomeMessage.value()}! I am a function.`);
  }
);

Python

from firebase_functions import https_fn
from firebase_functions.params import StringParam

WELCOME_MESSAGE = StringParam("WELCOME_MESSAGE")

@https_fn.on_request()
def hello_world(req):
    return https_fn.Response(f'{WELCOME_MESSAGE.value()}! I am a function!')

Parâmetros integrados

O SDK do Cloud Functions oferece três parâmetros predefinidos, disponíveis no subpacote firebase-functions/params:

Node.js

  • projectID: o projeto do Cloud em que a função está sendo executada.
  • databaseURL: o URL da instância do Realtime Database associada à função, se ativado no projeto do Firebase.
  • storageBucket: o bucket do Cloud Storage associado à função, se ativado no projeto do Firebase.

Python

  • PROJECT_ID: o projeto do Cloud em que a função está sendo executada.
  • DATABASE_URL: o URL da instância do Realtime Database associada à função, se ativado no projeto do Firebase.
  • STORAGE_BUCKET: o bucket do Cloud Storage associado à função, se ativado no projeto do Firebase.

Eles funcionam como parâmetros de string definidos pelo usuário em todos os aspectos, exceto por um: como os valores são sempre conhecidos pela CLI do Firebase, eles nunca são solicitados na implantação nem salvos em arquivos .env.

Parâmetros do secret

Os parâmetros do tipo Secret, que são definidos usando defineSecret(), representam parâmetros de string que têm um valor armazenado no Secret Manager do Google Cloud. Em vez de verificar se há um arquivo .env local e gravar um novo valor nele caso esteja vazio, os parâmetros do secret conferem a existência no Secret Manager do Google Cloud e solicitam o valor de um novo secret durante a implantação.

Os parâmetros do secret definidos assim precisam ser vinculados a funções individuais com acesso a eles:

Node.js

const { onRequest } = require('firebase-functions/v2/https');
const { defineSecret } = require('firebase-functions/params');
const discordApiKey = defineSecret('DISCORD_API_KEY');

export const postToDiscord = onRequest(
  { secrets: [discordApiKey] },
  (req, res) => {
  const apiKey = discordApiKey.value();
    //…

Python

from firebase_functions import https_fn
from firebase_functions.params import SecretParam

DISCORD_API_KEY = SecretParam('DISCORD_API_KEY')

@https_fn.on_request(secrets=[DISCORD_API_KEY])
def post_to_discord(req):
    api_key = DISCORD_API_KEY.value

Como os valores dos secrets ficam ocultos até que a função seja executada, eles não podem ser usados durante a configuração da função.

Variáveis de ambiente

O Cloud Functions for Firebase oferece suporte ao formato de arquivo dotenv para carregamento de variáveis de ambiente especificadas em um arquivo .env no ambiente de execução do app. Depois de implantadas, as variáveis de ambiente podem ser lidas usando a interface process.env (em projetos baseados em Node.js) ou os.environ (em projetos baseados em Python).

Para configurar o ambiente dessa maneira, crie um arquivo .env no projeto, adicione as variáveis desejadas e implante:

  1. Crie um arquivo .env no diretório functions/:

    # Directory layout:
    #   my-project/
    #     firebase.json
    #     functions/
    #       .env
    #       package.json
    #       index.js
    
  2. Abra o arquivo .env para editar e adicione as chaves desejadas. Por exemplo:

    PLANET=Earth
    AUDIENCE=Humans
    
  3. Implante funções e verifique se as variáveis de ambiente foram carregadas:

    firebase deploy --only functions
    # ...
    # i functions: Loaded environment variables from .env.
    # ...
    

Depois que as variáveis de ambiente personalizadas forem implantadas, o código da função poderá acessá-las:

Node.js

// Responds with "Hello Earth and Humans"
exports.hello = onRequest((request, response) => {
  response.send(`Hello ${process.env.PLANET} and ${process.env.AUDIENCE}`);
});

Python

import os

@https_fn.on_request()
def hello(req):
    return https_fn.Response(
        f"Hello {os.environ.get('PLANET')} and {os.environ.get('AUDIENCE')}"
    )

Como implantar vários conjuntos de variáveis de ambiente

Se você precisar de um conjunto alternativo de variáveis de ambiente para seus projetos do Firebase (como preparo x produção), crie um arquivo .env.<project or alias> e grave as variáveis específicas do projeto nele. As variáveis de ambiente de .env e arquivos específicos do projeto .env (se existirem) serão incluídos em todas as funções implantadas.

Por exemplo, um projeto pode incluir esses três arquivos com valores um pouco diferentes para desenvolvimento e produção:

.env .env.dev .env.prod
PLANET=Earth

AUDIENCE=Humans

AUDIENCE=Dev Humans AUDIENCE=Prod Humans

Considerando os valores nesses arquivos separados, o conjunto de variáveis de ambiente implantadas com suas funções varia de acordo com o projeto de destino:

$ firebase use dev
$ firebase deploy --only functions
i functions: Loaded environment variables from .env, .env.dev.
# Deploys functions with following user-defined environment variables:
#   PLANET=Earth
#   AUDIENCE=Dev Humans

$ firebase use prod
$ firebase deploy --only functions
i functions: Loaded environment variables from .env, .env.prod.
# Deploys functions with following user-defined environment variables:
#   PLANET=Earth
#   AUDIENCE=Prod Humans

Variáveis de ambiente reservadas

Algumas chaves de variáveis de ambiente são reservadas para uso interno. Não use nenhuma dessas chaves nos arquivos .env:

  • Todas as chaves que começam com X_GOOGLE_
  • Todas as chaves que começam com EXT_
  • Todas as chaves que começam com FIREBASE_
  • Qualquer chave da lista a seguir:
  • CLOUD_RUNTIME_CONFIG
  • ENTRY_POINT
  • GCP_PROJECT
  • GCLOUD_PROJECT
  • GOOGLE_CLOUD_PROJECT
  • FUNCTION_TRIGGER_TYPE
  • FUNCTION_NAME
  • FUNCTION_MEMORY_MB
  • FUNCTION_TIMEOUT_SEC
  • FUNCTION_IDENTITY
  • FUNCTION_REGION
  • FUNCTION_TARGET
  • FUNCTION_SIGNATURE_TYPE
  • K_SERVICE
  • K_REVISION
  • PORT
  • K_CONFIGURATION

Armazene e acesse informações de configuração confidenciais

Variáveis de ambiente armazenadas em arquivos .env podem ser usadas para configurar funções. No entanto, elas não devem ser consideradas uma maneira segura de armazenar informações confidenciais, como credenciais de banco de dados ou chaves de API. Isso é especialmente importante se você verificar seus arquivos .env no controle da origem.

Para ajudar você a armazenar informações de configuração sensíveis, o Cloud Functions for Firebase tem integração com o Secret Manager do Google Cloud. Esse serviço criptografado armazena valores de configuração com segurança e ainda facilita o acesso das suas funções quando necessário.

Criar e usar um secret

Para criar um secret, use a CLI do Firebase.

.

Para criar e usar um secret, siga as instruções a seguir:

  1. Na raiz do diretório do projeto local, execute o seguinte comando:

    firebase functions:secrets:set SECRET_NAME

  2. Insira um valor para SECRET_NAME.

    A CLI gera uma mensagem de êxito e avisa que você precisa implantar funções para que a alteração entre em vigor.

  3. Antes da implantação, verifique se o código das funções permite o acesso ao secret usando o parâmetro runWith:

    Node.js

    const { onRequest } = require('firebase-functions/v2/https');
    
    exports.processPayment = onRequest(
      { secrets: ["SECRET_NAME"] },
      (req, res) => {
        const myBillingService = initializeBillingService(
          // reference the secret value
          process.env.SECRET_NAME
        );
        // Process the payment
      }
    );

    Python

    import os
    from firebase_functions import https_fn
    
    @https_fn.on_request(secrets=["SECRET_NAME"])
    def process_payment(req):
        myBillingService = initialize_billing(key=os.environ.get('SECRET_NAME'))
        # Process the payment
        ...
    
  4. Implante o Cloud Functions:

    firebase deploy --only functions

    Agora você poderá acessá-lo como qualquer outra variável de ambiente. Por outro lado, se outra função que não especifica o secret em runWith tenta acessá-lo, ele recebe um valor indefinido:

    Node.js

    exports.anotherEndpoint = onRequest((request, response) => {
      response.send(`The secret API key is ${process.env.SECRET_NAME}`);
      // responds with "The secret API key is undefined" because the `runWith` parameter is missing
    });
    

    Python

    @https_fn.on_request()
    def another_endpoint(req):
        return https_fn.Response(f"The secret API key is {os.environ.get("SECRET_NAME")}")
        # Responds with "The secret API key is None" because the `secrets` parameter is missing.
    

A função terá acesso ao valor do secret depois que for implantada. Somente funções que incluem especificamente um secret no parâmetro runWith vão ter acesso a ele como uma variável de ambiente. Isso ajuda a garantir que os valores do secret fiquem disponíveis apenas quando necessários, reduzindo o risco de vazamentos acidentais.

Como gerenciar secrets

Use a CLI do Firebase para gerenciar secrets. Ao gerenciar secrets assim, lembre-se de que algumas alterações da CLI exigem a modificação e/ou reimplantação de funções associadas. Especificamente:

  • Sempre que você definir um novo valor de secret, implante novamente todas as funções que fazem referência a esse secret para coletar o valor mais recente.
  • Se você excluir um secret, verifique se nenhuma das funções implantadas faz referência a ele. As funções que usam um valor do secret excluído vão falhar de maneira silenciosa.

Confira um resumo dos comandos da CLI do Firebase para gerenciamento de secrets:

# Change the value of an existing secret
firebase functions:secrets:set SECRET_NAME

# View the value of a secret
functions:secrets:access SECRET_NAME

# Destroy a secret
functions:secrets:destroy SECRET_NAME

# View all secret versions and their state
functions:secrets:get SECRET_NAME

# Automatically clean up all secrets that aren't referenced by any of your functions
functions:secrets:prune

Para os comandos access e destroy, é possível fornecer o parâmetro de versão opcional para gerenciar uma versão específica. Por exemplo:

functions:secrets:access SECRET_NAME[@VERSION]

Para mais informações sobre essas operações, transmita -h com o comando para ver a ajuda da CLI.

Como é feito o faturamento dos secrets

O Secret Manager permite seis versões ativas do secret sem custos financeiros. Isso significa que é possível ter seis secrets por mês em um projeto do Firebase sem custos financeiros.

Por padrão, a CLI do Firebase tenta destruir automaticamente as versões não utilizadas de um secret quando apropriado, como durante a implantação de funções com uma nova versão dele. Além disso, é possível limpar ativamente os secrets não utilizados usando functions:secrets:destroy e functions:secrets:prune.

O Secret Manager permite 10 mil operações mensais de acesso não faturadas em um secret. As instâncias de função leem apenas os secrets especificados no parâmetro runWith sempre que passam pela inicialização a frio. Se você tiver muitas instâncias de função que leem muitos secrets, seu projeto poderá exceder essa permissão. A partir de então, será cobrado US $0,03 por 10.000 operações de acesso.

Para saber mais, consulte Preços do Secret Manager.

Suporte ao emulador

A configuração do ambiente com dotenv foi projetada para interoperar com um emulador local do Cloud Functions.

Ao usar um emulador local do Cloud Functions, é possível modificar as variáveis de ambiente para o projeto configurando um arquivo .env.local. O conteúdo de .env.local tem precedência sobre .env e sobre o arquivo .env específico do projeto.

Por exemplo, um projeto pode incluir esses três arquivos com valores um pouco diferentes para desenvolvimento e teste local:

.env .env.dev .env.local
PLANET=Earth

AUDIENCE=Humans

AUDIENCE=Dev Humans AUDIENCE=Local Humans

Quando iniciado no contexto local, o emulador carrega as variáveis de ambiente da seguinte maneira:

  $ firebase emulators:start
  i  emulators: Starting emulators: functions
  # Starts emulator with following environment variables:
  #  PLANET=Earth
  #  AUDIENCE=Local Humans

Secrets e credenciais no emulador de Cloud Functions

O emulador de Cloud Functions é compatível com o uso de secrets para armazenar e acessar informações sensíveis de configuração. Por padrão, o emulador tentará acessar seus secrets de produção usando credenciais padrão do aplicativo. Em determinadas situações, como ambientes de CI, o emulador pode não acessar valores do secret por causa de restrições de permissão.

Da mesma forma que o suporte do emulador Cloud Functions para variáveis de ambiente, é possível substituir valores de segredos configurando um arquivo .secret.local. Isso facilita o teste local das funções, especialmente se você não tiver acesso ao valor do secret.

Como migrar da configuração do ambiente

Se você estiver usando a configuração do ambiente com functions.config, é possível migrar a configuração atual como variáveis de ambiente (no formato dotenv). A CLI do Firebase fornece um comando de exportação que gera a configuração de cada alias ou projeto listado no arquivo .firebaserc do diretório (no exemplo abaixo, local, dev e prod) como arquivos .env.

Para migrar, exporte as configurações do ambiente atuais usando o comando firebase functions:config:export:

firebase functions:config:export
i  Importing configs from projects: [project-0, project-1]
⚠  The following configs keys could not be exported as environment variables:

⚠  project-0 (dev):
    1foo.a => 1FOO\_A (Key 1FOO\_A must start with an uppercase ASCII letter or underscore, and then consist of uppercase ASCII letters, digits, and underscores.)

Enter a PREFIX to rename invalid environment variable keys: CONFIG\_
✔  Wrote functions/.env.prod
✔  Wrote functions/.env.dev
✔  Wrote functions/.env.local
✔  Wrote functions/.env

Observe que, em alguns casos, você vai precisar inserir um prefixo para renomear as chaves de variáveis de ambiente exportadas. Isso ocorre porque nem todas as configurações podem ser transformadas automaticamente, já que podem ser inválidas ou serem chaves de variável de ambiente reservadas.

Recomendamos que você analise cuidadosamente o conteúdo dos arquivos .env gerados antes de implantar suas funções ou verifique os arquivos .env no controle de origem. Remova os valores confidenciais que não podem ser divulgados dos arquivos .env e os armazene com segurança no Secret Manager.

Também é necessário atualizar seu código de funções. Agora, todas as funções que usam functions.config vão precisar usar process.env, conforme mostrado nas Variáveis de ambiente.