Can't apply splat operator to null sequence

This seems to have stoped working

special_steps = var.prefix == "dev" ? ["go"] : null

│  118:         for_each = local.special_steps[*]
│     ├────────────────
│     │ local.special_steps is null
│ 
│ Splat expressions (with the * symbol) cannot be applied to null sequences.

Any thoughts on that?

Hi @vongohren,

The [*] behavior differs depending on whether you apply it to a sequence (list, set, or tuple) or to some other kind of value.

It seems like your local.special_steps is a null list or null tuple, and so you can’t apply [*] to it like this. The typical way to represent no items in a list is as an empty list rather than a null list, and so I’d suggest changing the definition of local.special_steps to always be a non-null list which might sometimes have zero elements.

Thanks for feedback, and yeah I did this basically and it helped me.
But might there be a cleaner and better way of doing this?

I have an incomming flag, and I want to set a variable that shall affect many dynamic blocks so want to wrap them all under one flag. But having to make an array with “go” inside just seems not tooo clean :stuck_out_tongue:

Without some more context about what you’re doing (including, ideally, some full code examples) I’m not really sure what to suggest. The purpose of a dynamic block is to declare a block for each element of a collection, and the [*] operator can be helpful to turn a nullable non-collection value into a list but if you are already working with lists anyway then you likely won’t need it, because for_each can work with the list values directly.

Below you find variables and the code itself. Im building a dynamic CI pipeline module for all our frontend builds in a micro frontend architecture.

I like it all, except for the if else statemenst with the arrays of [“go”]. I wish I could decalare things a bit more cleaner. But I guess I just dont understand the splat command completly, so im just doing what works and moving on.

But if you have any immediate feedback on how to make use of splat even cleaner, that would be awesome

variable "prefix" {
  description = "The prefix for the environment"
  type = string
}

variable "appname" {
  description = "The app name for naming things"
  type = string
}

variable "build_env" {
  description = "Any special build envs for the build step of the pipeline"
  type = list(string)
  default = null
}

variable "path_for_app" {
  description = "The path for the app and where to find its code"
  type = string
}

variable "release_tag" {
  description = "Release tag if we are doing production pipeline"
  type = string
  default = null
}

variable "push_types" {
  description = "This is a boolean to declare if push types is set or not"
  type = bool
  default = false
}

variable "have_assets" {
  description = "This is a boolean to declare if push types is set or not"
  type = bool
  default = true
}

variable "folder_release" {
  description = "This is a boolean to declare this is app is ready to be release via a folder or per file basis. It is backward compatible meaning it will set default false"
  type = bool
  default = false
}
module "import_map_service" {
  source = "github.com/sourcecode"
  prefix = var.prefix
}

locals {
  bucket_name = "${var.prefix}-courier"
  special_steps = var.prefix == "dev" ? ["go"] : []
  type_publish = var.push_types == true ? ["go"] : []
  prep_command = var.push_types == true ? "pre:publish" : "bump:version:commit"
  push_assets = var.have_assets == true ? ["go"] : []
  gsutil_js_file_args = var.folder_release == true ? ["cp", "-r", "dist/*${var.appname}*", "gs://${local.bucket_name}/${var.appname}/build-$COMMIT_SHA-$BUILD_ID/"] : ["cp", "dist/${var.appname}.js", "gs://${local.bucket_name}/${var.appname}/${var.appname}-$COMMIT_SHA-$BUILD_ID.js"]
  js_file_location = var.folder_release == true ? "build-$COMMIT_SHA-$BUILD_ID/${var.appname}.js" : "${var.appname}-$COMMIT_SHA-$BUILD_ID.js"
  assets_file_location = var.folder_release == true ? "build-$COMMIT_SHA-$BUILD_ID/src/" : "src/"
}

data "google_secret_manager_secret_version" "github_access_token_enc" {
  secret = "github_access_token_enc"
}
data "google_secret_manager_secret_version" "npm_token" {
  secret = "npm_ci_token"
}

resource "google_cloudbuild_trigger" "deployer" {
    provider = google-beta
    name = "${var.prefix}-web-${var.appname}-deployer"

    substitutions = {
      _PATH = var.path_for_app
    }

    github {
        name = "repo"
        owner = "owner"
        push {
          branch = var.prefix == "dev" ? "main" : null
          tag = var.prefix == "dev" ? null : var.release_tag
        }
    }
    build {
      dynamic "step" {
        for_each = local.special_steps[*]
        content {
          name = "gcr.io/cloud-builders/git"
          entrypoint = "bash"
          dir = "$${_PATH}"
          args = [
                  "-c",  
                  <<-EOF
                  git config --global user.name username
                  git config --global user.email email
                  git remote set-url origin url
                  git clone url
                  git fetch
                  git checkout $BRANCH_NAME
                  EOF
          ]
          secret_env = [ "GITHUB_TOKEN" ]
        }
      }
      step {
        name = "gcr.io/cloud-builders/yarn"
        id = "Install packages"
        entrypoint = "yarn"
        dir = "$${_PATH}"
        args = ["install"]
      }
      step {
        name = "gcr.io/cloud-builders/yarn"
        entrypoint = "yarn"
        dir = "$${_PATH}"
        args = ["lint:prod"]
      }
      dynamic "step" {
        for_each = local.special_steps[*]
        content {
          name = "gcr.io/cloud-builders/yarn"
          entrypoint = "yarn"
          dir = "$${_PATH}"
          args = [local.prep_command]
        }
      }
      step {
        name = "gcr.io/cloud-builders/yarn"
        id = "Build the app"
        entrypoint = "yarn"
        dir = "$${_PATH}"
        env = var.build_env
        args = ["build"]
      }
      step {
        name = "gcr.io/cloud-builders/gsutil"
        id = "Send js files to bucket"
        dir = "$${_PATH}"
        args = local.gsutil_js_file_args
      }
      dynamic "step" {
        for_each = local.push_assets[*]
        content {
          name = "gcr.io/cloud-builders/gsutil"
          id = "Send assets to bucket"
          dir = "$${_PATH}"
          args = ["cp", "-r", "src/assets", "gs://${local.bucket_name}/${var.appname}/${local.assets_file_location}"]
        }
      }
      # https://httpie.io/ for easier deployment of the rootmap deployer
      step {
        name = "alpine/httpie"
        id = "Call import map deployer with new url"
        args = [
          "--check-status",
          "--ignore-stdin",
          "-a",
          "${module.import_map_service.username}:${module.import_map_service.password}",
          "PATCH",
          "${module.import_map_service.url}/services?env=${var.prefix}",
          "service=\\@company/${var.appname}",
          "url=https://storage.googleapis.com/${local.bucket_name}/${var.appname}/${local.js_file_location}"
        ]
      }
      dynamic "step" {
        for_each = local.type_publish[*]
        content {
          name = "bash"
          dir = "$${_PATH}"
          args = ["mv",".npmrcpublish", ".npmrc"]
        }
      }
      dynamic "step" {
        for_each = local.type_publish[*]
        content {
          name = "gcr.io/cloud-builders/npm"
          entrypoint = "npm"
          dir = "$${_PATH}"
          args = ["publish","--access", "public"]
          env = ["NPM_TOKEN=${data.google_secret_manager_secret_version.npm_token.secret_data}"]
        }
      }
      dynamic "step" {
        for_each = local.special_steps[*]
        content {
          name = "gcr.io/cloud-builders/git"
          entrypoint = "bash"
          dir = "$${_PATH}"
          args = [
                    "-c",  
                  <<-EOF
                    git pull origin $BRANCH_NAME
                    git push origin $BRANCH_NAME
                  EOF
          ]
        }
      }
      dynamic "secret" {
        for_each = local.special_steps[*]
        content {
          kms_key_name = "kmsname"
          secret_env = {
            GITHUB_TOKEN = "${data.google_secret_manager_secret_version.github_access_token_enc.secret_data}"
          }
        }
      }
    }

    included_files = ["${var.path_for_app}/**"]
    ignored_files = [
      "${var.path_for_app}/terraform/**",
      "${var.path_for_app}/package.json"
    ]
}