Variables and variable mapping with SaaS Runtime

This document explains how variables, variable mapping, and dependencies work within SaaS Runtime.

SaaS Runtime lets you to deploy and manage complex SaaS applications by organizing them into modular units. These units, defined by Terraform configurations within blueprints, can be interconnected through dependencies, allowing for sophisticated orchestration and automated provisioning. A key aspect of managing these units and their interactions is through variables and variable mapping.

You can build complex, modular, and scalable deployments with automated provisioning, inter-unit communication, and flexible configuration options. Carefully consider your variable hierarchy, dependency relationships, and variable mappings to optimize your SaaS architecture and management workflows within SaaS Runtime.

Units and variables

At the heart of SaaS Runtime are units. Units utilize variables to customize their deployment and behavior. These variables are essentially Terraform variables that you can define in your blueprint's variables.tf file. They allow you to parameterize your Terraform configurations, making them reusable and adaptable across different environments and deployments.

Mandatory variables for unit provisioning

While you can define custom variables for your units, SaaS Runtime also relies on a set of mandatory variables. These variables are automatically recognized and handled by SaaS Runtime, even if they are not explicitly defined in your Terraform configuration.

The mandatory variables are:

  • project_id and project_number (or, tenant_project_id and tenant_project_id): This variable specifies the Google Cloud project ID where your unit's resources will be deployed. You can use either project_id and project_number, or tenant_id and tenant_project_id. This field is necessary for resource creation and management within the correct Google Cloud project.

    You can find your project ID in the the Google Cloud console on the Dashboard page. Look for the Project ID field in the Project info card within the Cloud overview section.

  • project_number or tenant_project_number: Similar to project_id, this variable represents the Google Cloud project number. You can use project_number or tenant_project_number interchangeably.

    You can find your project number alongside your project ID in the Project info card within the Cloud overview section of the the Google Cloud console Dashboard page.

  • actuation_sa: This variable represents the email address of the actuation service account. This service account is a user-managed service account that SaaS Runtime uses (via Infrastructure Manager) to execute your Terraform configurations when provisioning, updating, or deprovisioning units.

    We recommend that you use a dedicated actuation service account per tenant (or unit) to implement the principle of least privilege. This limits the potential impact of security breaches and provides better isolation between your deployments.

    For more information about configuring and granting permissions to actuation service accounts, see Product overview.

Unit variables hierarchy

Unit variables can be defined and sourced from multiple locations. When you use SaaS Runtime, it's important to understand the hierarchy of variable values used during unit operations.

The order is as follows (from lowest to highest precedence):

SaaS Runtime resolves variable values in this order:

  1. Existing input variables on the unit: If a variable is already defined on the unit resource itself (from a previous operation or configuration, for example), this value takes the lowest precedence. If a variable value is set directly on the unit, it will be overridden if a value is found from sources higher in the hierarchy.

  2. Release defaults: Release default values are applied if a variable isn't already set on the unit, but they will override existing unit variables.

  3. Unit operations: When you perform a unit operation such as provision or upgrade, you can explicitly provide input variables as part of the operation request. Variables provided in a unit operation will override release defaults and existing unit variables.

  4. Dependencies' input variable mappings: When a unit has dependencies on other units, variable mappings defined in the unit kind can source variable values from the output variables of dependency units. This is the highest precedence source. Variables obtained through dependency mappings will override values from unit operations, release defaults, and existing unit variables.

This hierarchical approach provides flexibility and control over your variable management. You can establish persistent configurations directly on the unit (lowest priority), define baseline defaults using releases, customize for specific operations, and dynamically source the most important and overriding values from unit dependencies (highest priority).

Unit dependencies

SaaS Runtime lets you to define dependencies between units. This is important for building complex SaaS applications where different components rely on each other. Dependencies ensure that related units are provisioned and managed in a coordinated manner.

You define dependencies within a unit kind. When you create a unit of a particular unit kind, SaaS Runtime automatically manages its dependencies.

Dependency definition in unit kind

In the UnitKind definition, you specify a list of dependencies, each referencing another UnitKind and assigning it an alias. This alias is used for referencing the dependency in variable mappings:

  message UnitKind {
    // ... other fields ...

    // List of other unit kinds that this release will depend on.
    repeated Dependency dependencies = 4
        [(.google.api.field_behavior) = OPTIONAL];
    // ...
  }

  message Dependency {
    // The unit kind of the dependency.
    string unit_kind = 1 [
      (.google.api.field_behavior) = REQUIRED,
      (.google.api.field_behavior) = IMMUTABLE,
      (.google.api.resource_reference) = {
        type: "saasservicemgmt.googleapis.com/UnitKind"
      }
    ];

    // An alias for the dependency. Used for input variable mapping.
    string alias = 2 [(.google.api.field_behavior) = REQUIRED];
  }

Automatic dependency provisioning

When you request to provision a unit that has dependencies defined in its UnitKind, SaaS Runtime automatically checks for the existence of those dependency units.

If a dependency unit is not found, SaaS Runtime will automatically provision it before provisioning the dependent unit.

SaaS Runtime ensures that dependency units are provisioned before their dependent units, maintaining the correct order of operations.

Variable mapping

Variable mapping is the mechanism to pass data (variable values) between dependent units and their dependencies. This is essential for configuring dependent units based on outputs from their dependencies.

Variable mapping is defined within the dependent unit kind and uses FromMapping and ToMapping:

  • FromMapping: FromMapping is used to retrieve output variables from a dependency unit and map them to input variables of the dependent unit. This is how a dependent unit can obtain information from its dependencies, such as connection endpoints or resource IDs.

    message VariableMapping {
      // ...
      oneof mapping_type {
        // Output variables which will get their values from dependencies
        FromMapping from = 2 [(.google.api.field_behavior) = OPTIONAL];
        // ...
      }
    }
    
    message FromMapping {
      // Alias of the dependency that the outputVariable will pass its value to
      string dependency = 1 [(.google.api.field_behavior) = REQUIRED];
    
      // Name of the outputVariable on the dependency
      string output_variable = 2 [(.google.api.field_behavior) = REQUIRED];
    }
    

    In the provided codelab example, the App UnitKind has a FromMapping to retrieve the cluster_endpoint output variable from the Cluster UnitKind dependency. This allows the application to connect to the newly provisioned Kubernetes cluster.

  • ToMapping: ToMapping is used to pass input variables from the dependent unit to the input variables of a dependency unit. This lets you configure dependency units based on parameters provided for the dependent unit.

    message VariableMapping {
      // ...
      oneof mapping_type {
        // ...
        // Input variables whose values will be passed on to dependencies.
        ToMapping to = 3 [(.google.api.field_behavior) = OPTIONAL];
      }
    }
    
    message ToMapping {
      // Alias of the dependency that the inputVariable will pass its value to
      string dependency = 1 [(.google.api.field_behavior) = REQUIRED];
    
      // Name of the inputVariable on the dependency
      string input_variable = 2 [(.google.api.field_behavior) = REQUIRED];
    
      // Tells EasySaaS if this mapping should be used during lookup or not
      bool ignore_for_lookup = 3 [(.google.api.field_behavior) = OPTIONAL];
    }
    

    In the codelab, the App UnitKind uses ToMapping to pass the tenant_project_id and tenant_project_number input variables to the Cluster UnitKind dependency. This ensures that the Kubernetes cluster is created within the correct project.

  • ignore_for_lookup in ToMapping: The ignore_for_lookup field in ToMapping controls dependency lookup behavior. It is a boolean value:

    • false (or not specified): When set to false, SaaS Runtime will use the input variables specified in the ToMapping as lookup keys to find an existing dependency unit. If a unit with matching input variables is found, it will be reused as the dependency. If no matching unit is found, a new dependency unit will be provisioned. This is useful for reusing shared resources like clusters.
    • true: When set to true, the input variable in the ToMapping won't be used for dependency lookup. This means that a new dependency unit will always be provisioned, even if units with the same input variables already exist. This is useful when you need dedicated, non-shared dependencies for each dependent unit.

Example: Shared Kubernetes cluster

If you want to reuse a Kubernetes cluster for multiple application units within the same project, you would use ToMapping with ignore_for_lookup set to false and map variables like tenant_project_id and region to the cluster unit kind. Units in the same project and region would then reuse the same cluster.

Example: Dedicated Kubernetes cluster per application

If you need a dedicated Kubernetes cluster for each application unit, you would use ToMapping with ignore_for_lookup set to true or avoid ToMapping lookup variables altogether. Each application unit would then trigger the provisioning of a new, isolated Kubernetes cluster.

Manage secrets

SaaS Runtime variables are not intended for storing sensitive information like passwords, API keys, or certificates. Storing secrets directly in variables poses security risks. Variable values can be logged and potentially exposed through system outputs, increasing the chance of unauthorized access.

For secure secret management in your SaaS Runtime applications, we strongly recommend using Secret Manager.

What's next