Octopus - Serialize Project to Terraform

Octopus.Script exported 2023-09-11 by mcasperson belongs to ‘Octopus’ category.

Serialize an Octopus project as a Terraform module and upload the resulting package to the Octopus built in feed.

This step uses naming conventions to exclude resources from the generated module:

  • Variables starting with Private. are excluded
  • Runbooks starting with __ are excluded
  • The environment called Sync is removed from any variable scopes

Because serializing Terraform modules is done via the API, the values of any secret variables are not available, and are not included in the module generated by this step.

However, by following a variable naming and scoping convention, it is possible to export and then apply a project in a Terraform module recreating secret variables, without ever including the secrets in the exported module.

The project to be exported must define all secret variables with a unique name and a single value. For example, the secret variable Test.Database.Password can be scoped to the Test environment and the secret variable Production.Database.Password can be scoped to the Production environment. You can not have a single secret variable called Database.Password with two values for the different environments though.

To collapse the unique secret variables into a single variable used by steps, it is possible to create a non-secret variable called Database.Password with two values #{Test.Database.Password} and #{Production.Database.Password} scoped to appropriate environments.

In this way steps can still reference a single variable called Database.Password, but all secret variables have unique names and only one value.

All secret variables are then scoped to an additional environment called Sync, which means all secret variables are exposed to runbooks run in the Step environment. The Sync environment is used to apply the Terraform module exported by this step, Apply a Terraform template step to perform variable replacements with secret variables.

The secret values in the Terraform module then have default values set to the Octostache template referencing the secret variable. For example, the Octopus variables in the Terraform module have default values like #{Test.Database.Password} and #{Production.Database.Password}. These templates are replaced at runtime by the Apply a Terraform template step, run in the Sync environment, effectively injecting the secret values back into the newly created project.

This allows secret variables to be recreated with their original values, without ever exporting the secret values.

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

Ignore All Changes

SerializeProject.Exported.Project.IgnoreAllChanges = False

Selecting this option creates a Terraform module with the “lifecycle.ignore_changes” option set to “all” for all project resources. This allows the resources to be created if they do not exist, but won’t update them if the module is reapplied. This value effectively enables the “Ignore Variable Changes” option.

Ignore Variable Changes

SerializeProject.Exported.Project.IgnoreVariableChanges = True

Selecting this option creates a Terraform module with the “lifecycle.ignore_changes” option set to “all” for Octopus variables (i.e. “octopusdeploy_variable” resources)”. This allows Octopus variables to be created if they do not exist, but won’t update Octopus variable values if the module is reapplied. This value effectively enabled if the “Ignore All Changes” option is enabled.

Ignore CaC Settings

SerializeProject.Exported.Project.IgnoreCacValues = False

Enable this option to exclude any Config-as-Code managed resources from the exported module, such as non-secret variables, deployment process, and CaC defined project settings. This option is useful when you are exporting CaC enabled projects and do not wish to include any settings in the exported module that are managed by Git. Disable this option, and enable the “Exclude CaC Settings” option to essentially convert CaC projects to regular projects.

Exclude CaC Settings

SerializeProject.Exported.Project.ExcludeCacProjectValues = False

Enable this option to exclude Config-as-Code settings from the exported module, such as Git credentials and the version controlled flag. Enable this option, and disable the “Ignore CaC Settings” option to essentially convert CaC projects to regular projects.

Terraform Backend

SerializeProject.ThisInstance.Terraform.Backend = s3

The backed to define in the Terraform module.

Octopus Server URL

SerializeProject.ThisInstance.Server.Url = #{Octopus.Web.ServerUri}

The URL of the Octopus Server hosting the project to be serialized.

Octopus API Key

SerializeProject.ThisInstance.Api.Key =

The Octopus API Key

Octopus Space ID

SerializeProject.Exported.Space.Id = #{Octopus.Space.Id}

The Space ID containing the project to be exported

Octopus Project Name

SerializeProject.Exported.Project.Name = #{Octopus.Project.Name}

The name of the project to serialize.

Octopus Upload Space ID

SerializeProject.Octopus.UploadSpace.Id =

The ID of the space to upload the Terraform package to. Leave this blank to upload to the space defined in the Octopus Space ID parameter.

Ignored Accounts

SerializeProject.Exported.Project.IgnoredAccounts =

A comma separated list of accounts that will not be included in the Terraform module. These accounts are often those used by Runbooks that are not included in the module, and so do not need to be referenced.

Ignored Library Variables Sets

Exported.Project.IgnoredLibraryVariableSet =

A comma separated list of library variables sets that will not be included in the Terraform module. These library variable sets are often those used by Runbooks that are not included in the module, and so do not need to be referenced.

Include Step Templates

SerializeProject.Exported.Project.IncludeStepTemplates = False

Enable this option to export step templates referenced by a project. Disable this option to have step templates detached in projects instead.

SerializeProject.Exported.Project.LookupProjectLinkTenants = False

Enable this option to have each project link any tenants and create project tenant variables.

Script body

Steps based on this template will execute the following Python script.

import argparse
import os
import stat
import re
import socket
import subprocess
import sys
from datetime import datetime
from urllib.parse import urlparse
import urllib.request
from itertools import chain
import platform
from urllib.request import urlretrieve
import zipfile
import json
import tarfile
import random, time

# If this script is not being run as part of an Octopus step, return variables from environment variables.
# Periods are replaced with underscores, and the variable name is converted to uppercase
if "get_octopusvariable" not in globals():
    def get_octopusvariable(variable):
        return os.environ[re.sub('\\.', '_', variable.upper())]

# If this script is not being run as part of an Octopus step, print directly to std out.
if "printverbose" not in globals():
    def printverbose(msg):
        print(msg)


def printverbose_noansi(output):
    """
    Strip ANSI color codes and print the output as verbose
    :param output: The output to print
    """
    if not output:
        return

    # https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python
    output_no_ansi = re.sub(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])', '', output)
    printverbose(output_no_ansi)


def get_octopusvariable_quiet(variable):
    """
    Gets an octopus variable, or an empty string if it does not exist.
    :param variable: The variable name
    :return: The variable value, or an empty string if the variable does not exist
    """
    try:
        return get_octopusvariable(variable)
    except:
        return ''


def retry_with_backoff(fn, retries=5, backoff_in_seconds=1):
    x = 0
    while True:
        try:
            return fn()
        except Exception as e:

            print(e)

            if x == retries:
                raise

            sleep = (backoff_in_seconds * 2 ** x +
                     random.uniform(0, 1))
            time.sleep(sleep)
            x += 1


def execute(args, cwd=None, env=None, print_args=None, print_output=printverbose_noansi):
    """
        The execute method provides the ability to execute external processes while capturing and returning the
        output to std err and std out and exit code.
    """
    process = subprocess.Popen(args,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               text=True,
                               cwd=cwd,
                               env=env)
    stdout, stderr = process.communicate()
    retcode = process.returncode

    if print_args is not None:
        print_output(' '.join(args))

    if print_output is not None:
        print_output(stdout)
        print_output(stderr)

    return stdout, stderr, retcode


def is_windows():
    return platform.system() == 'Windows'


def init_argparse():
    parser = argparse.ArgumentParser(
        usage='%(prog)s [OPTION] [FILE]...',
        description='Serialize an Octopus project to a Terraform module'
    )
    parser.add_argument('--ignore-all-changes',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.IgnoreAllChanges') or get_octopusvariable_quiet(
                            'Exported.Project.IgnoreAllChanges') or 'false',
                        help='Set to true to set the "lifecycle.ignore_changes" ' +
                             'setting on each exported resource to "all"')
    parser.add_argument('--ignore-variable-changes',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.IgnoreVariableChanges') or get_octopusvariable_quiet(
                            'Exported.Project.IgnoreVariableChanges') or 'false',
                        help='Set to true to set the "lifecycle.ignore_changes" ' +
                             'setting on each exported octopus variable to "all"')
    parser.add_argument('--terraform-backend',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.ThisInstance.Terraform.Backend') or get_octopusvariable_quiet(
                            'ThisInstance.Terraform.Backend') or 'pg',
                        help='Set this to the name of the Terraform backend to be included in the generated module.')
    parser.add_argument('--server-url',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.ThisInstance.Server.Url') or get_octopusvariable_quiet(
                            'ThisInstance.Server.Url'),
                        help='Sets the server URL that holds the project to be serialized.')
    parser.add_argument('--api-key',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.ThisInstance.Api.Key') or get_octopusvariable_quiet(
                            'ThisInstance.Api.Key'),
                        help='Sets the Octopus API key.')
    parser.add_argument('--space-id',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Space.Id') or get_octopusvariable_quiet(
                            'Exported.Space.Id') or get_octopusvariable_quiet('Octopus.Space.Id'),
                        help='Set this to the space ID containing the project to be serialized.')
    parser.add_argument('--project-name',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.Name') or get_octopusvariable_quiet(
                            'Exported.Project.Name') or get_octopusvariable_quiet(
                            'Octopus.Project.Name'),
                        help='Set this to the name of the project to be serialized.')
    parser.add_argument('--upload-space-id',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Octopus.UploadSpace.Id') or get_octopusvariable_quiet(
                            'Octopus.UploadSpace.Id') or get_octopusvariable_quiet('Octopus.Space.Id'),
                        help='Set this to the space ID of the Octopus space where ' +
                             'the resulting package will be uploaded to.')
    parser.add_argument('--ignore-cac-managed-values',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.IgnoreCacValues') or get_octopusvariable_quiet(
                            'Exported.Project.IgnoreCacValues') or 'false',
                        help='Set this to true to exclude cac managed values like non-secret variables, ' +
                             'deployment processes, and project versioning into the Terraform module. ' +
                             'Set to false to have these values embedded into the module.')
    parser.add_argument('--exclude-cac-project-settings',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.ExcludeCacProjectValues') or get_octopusvariable_quiet(
                            'Exported.Project.ExcludeCacProjectValues') or 'false',
                        help='Set this to true to exclude CaC settings like git connections from the exported module.')
    parser.add_argument('--ignored-library-variable-sets',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.IgnoredLibraryVariableSet') or get_octopusvariable_quiet(
                            'Exported.Project.IgnoredLibraryVariableSet'),
                        help='A comma separated list of library variable sets to ignore.')
    parser.add_argument('--ignored-accounts',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.IgnoredAccounts') or get_octopusvariable_quiet(
                            'Exported.Project.IgnoredAccounts'),
                        help='A comma separated list of accounts to ignore.')
    parser.add_argument('--include-step-templates',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.IncludeStepTemplates') or get_octopusvariable_quiet(
                            'Exported.Project.IncludeStepTemplates') or 'false',
                        help='Set this to true to include step templates in the exported module. ' +
                             'This disables the default behaviour of detaching step templates.')
    parser.add_argument('--lookup-project-link-tenants',
                        action='store',
                        default=get_octopusvariable_quiet(
                            'SerializeProject.Exported.Project.LookupProjectLinkTenants') or get_octopusvariable_quiet(
                            'Exported.Project.LookupProjectLinkTenants') or 'false',
                        help='Set this option to link tenants and create tenant project variables.')

    return parser.parse_known_args()


def get_latest_github_release(owner, repo, filename):
    url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
    releases = urllib.request.urlopen(url).read()
    contents = json.loads(releases)

    download = [asset for asset in contents.get('assets') if asset.get('name') == filename]

    if len(download) != 0:
        return download[0].get('browser_download_url')

    return None


def ensure_octo_cli_exists():
    if is_windows():
        print("Checking for the Octopus CLI")
        try:
            stdout, _, exit_code = execute(['octo.exe', 'help'])
            printverbose(stdout)
            if not exit_code == 0:
                raise "Octo CLI not found"
            return ""
        except:
            print("Downloading the Octopus CLI")
            urlretrieve('https://download.octopusdeploy.com/octopus-tools/9.0.0/OctopusTools.9.0.0.win-x64.zip',
                        'OctopusTools.zip')
            with zipfile.ZipFile('OctopusTools.zip', 'r') as zip_ref:
                zip_ref.extractall(os.getcwd())
            return os.getcwd()
    else:
        print("Checking for the Octopus CLI for Linux")
        try:
            stdout, _, exit_code = execute(['octo', 'help'])
            printverbose(stdout)
            if not exit_code == 0:
                raise "Octo CLI not found"
            return ""
        except:
            print("Downloading the Octopus CLI for Linux")
            urlretrieve('https://download.octopusdeploy.com/octopus-tools/9.0.0/OctopusTools.9.0.0.linux-x64.tar.gz',
                        'OctopusTools.tar.gz')
            with tarfile.open('OctopusTools.tar.gz') as file:
                file.extractall(os.getcwd())
                os.chmod(os.path.join(os.getcwd(), 'octo'), stat.S_IRWXO | stat.S_IRWXU | stat.S_IRWXG)
            return os.getcwd()


def ensure_octoterra_exists():
    if is_windows():
        print("Checking for the Octoterra tool for Windows")
        try:
            stdout, _, exit_code = execute(['octoterra.exe', '-version'])
            printverbose(stdout)
            if not exit_code == 0:
                raise "Octoterra not found"
            return ""
        except:
            print("Downloading Octoterra CLI for Windows")
            retry_with_backoff(lambda: urlretrieve(
                "https://github.com/OctopusSolutionsEngineering/OctopusTerraformExport/releases/latest/download/octoterra_windows_amd64.exe",
                'octoterra.exe'), 10, 30)
            return os.getcwd()
    else:
        print("Checking for the Octoterra tool for Linux")
        try:
            stdout, _, exit_code = execute(['octoterra', '-version'])
            printverbose(stdout)
            if not exit_code == 0:
                raise "Octoterra not found"
            return ""
        except:
            print("Downloading Octoterra CLI for Linux")
            retry_with_backoff(lambda: urlretrieve(
                "https://github.com/OctopusSolutionsEngineering/OctopusTerraformExport/releases/latest/download/octoterra_linux_amd64",
                'octoterra'), 10, 30)
            os.chmod(os.path.join(os.getcwd(), 'octoterra'), stat.S_IRWXO | stat.S_IRWXU | stat.S_IRWXG)
            return os.getcwd()


octocli_path = ensure_octo_cli_exists()
octoterra_path = ensure_octoterra_exists()
parser, _ = init_argparse()

# Variable precondition checks
if len(parser.server_url) == 0:
    print("--server-url, ThisInstance.Server.Url, or SerializeProject.ThisInstance.Server.Url must be defined")
    sys.exit(1)

if len(parser.api_key) == 0:
    print("--api-key, ThisInstance.Api.Key, or ThisInstance.Api.Key must be defined")
    sys.exit(1)

print("Octopus URL: " + parser.server_url)
print("Octopus Space ID: " + parser.space_id)

# Build the arguments to ignore library variable sets
ignores_library_variable_sets = parser.ignored_library_variable_sets.split(',')
ignores_library_variable_sets_args = [['-excludeLibraryVariableSet', x] for x in ignores_library_variable_sets]

# Build the arguments to ignore accounts
ignored_accounts = parser.ignored_accounts.split(',')
ignored_accounts = [['-excludeAccounts', x] for x in ignored_accounts]

os.mkdir(os.getcwd() + '/export')

export_args = [os.path.join(octoterra_path, 'octoterra'),
               # the url of the instance
               '-url', parser.server_url,
               # the api key used to access the instance
               '-apiKey', parser.api_key,
               # add a postgres backend to the generated modules
               '-terraformBackend', parser.terraform_backend,
               # dump the generated HCL to the console
               '-console',
               # dump the project from the current space
               '-space', parser.space_id,
               # the name of the project to serialize
               '-projectName', parser.project_name,
               # ignoreProjectChanges can be set to ignore all changes to the project, variables, runbooks etc
               '-ignoreProjectChanges=' + parser.ignore_all_changes,
               # use data sources to lookup external dependencies (like environments, accounts etc) rather
               # than serialize those external resources
               '-lookupProjectDependencies',
               # for any secret variables, add a default value set to the octostache value of the variable
               # e.g. a secret variable called "database" has a default value of "#{database}"
               '-defaultSecretVariableValues',
               # Any value that can't be replaced with an Octostache template, add a dummy value
               '-dummySecretVariableValues',
               # detach any step templates, allowing the exported project to be used in a new space
               '-detachProjectTemplates=' + str(not parser.include_step_templates),
               # allow the downstream project to move between project groups
               '-ignoreProjectGroupChanges',
               # allow the downstream project to change names
               '-ignoreProjectNameChanges',
               # CaC enabled projects will not export the deployment process, non-secret variables, and other
               # CaC managed project settings if ignoreCacManagedValues is true. It is usually desirable to
               # set this value to true, but it is false here because CaC projects created by Terraform today
               # save some variables in the database rather than writing them to the Git repo.
               '-ignoreCacManagedValues=' + parser.ignore_cac_managed_values,
               # Excluding CaC values means the resulting module does not include things like git credentials.
               # Setting excludeCaCProjectSettings to true and ignoreCacManagedValues to false essentially
               # converts a CaC project back to a database project.
               '-excludeCaCProjectSettings=' + parser.exclude_cac_project_settings,
               # This value is always true. Either this is an unmanaged project, in which case we are never
               # reapplying it; or it is a variable configured project, in which case we need to ignore
               # variable changes, or it is a shared CaC project, in which case we don't use Terraform to
               # manage variables.
               '-ignoreProjectVariableChanges=' + parser.ignore_variable_changes,
               # To have secret variables available when applying a downstream project, they must be scoped
               # to the Sync environment. But we do not need this scoping in the downstream project, so the
               # Sync environment is removed from any variable scopes when serializing it to Terraform.
               '-excludeVariableEnvironmentScopes', 'Sync',
               # Exclude any variables starting with "Private."
               '-excludeProjectVariableRegex', 'Private\\..*',
               # Capture the octopus endpoint, space ID, and space name as output vars. This is useful when
               # querying th Terraform state file to know which space and instance the resources were
               # created in. The scripts used to update downstream projects in bulk work by querying the
               # Terraform state, finding all the downstream projects, and using the space name to only process
               # resources that match the current tenant (because space names and tenant names are the same).
               # The output variables added by this option are octopus_server, octopus_space_id, and
               # octopus_space_name.
               '-includeOctopusOutputVars',
               # Where steps do not explicitly define a worker pool and reference the default one, this
               # option explicitly exports the default worker pool by name. This means if two spaces have
               # different default pools, the exported project still uses the pool that the original project
               # used.
               '-lookUpDefaultWorkerPools',
               # Link any tenants that were originally link to the project and create project tenant variables
               '-lookupProjectLinkTenants=' + parser.lookup_project_link_tenants,
               # Add support for experimental step templates
               '-experimentalEnableStepTemplates=' + parser.include_step_templates,
               # The directory where the exported files will be saved
               '-dest', os.getcwd() + '/export',
               # This is a management runbook that we do not wish to export
               '-excludeRunbookRegex', '__ .*'] + list(chain(*ignores_library_variable_sets_args)) + list(
    chain(*ignored_accounts))

print("Exporting Terraform module")
_, _, octoterra_exit = execute(export_args)

if not octoterra_exit == 0:
    print("Octoterra failed. Please check the logs for more information.")
    sys.exit(1)

date = datetime.now().strftime('%Y.%m.%d.%H%M%S')

print("Creating Terraform module package")
if is_windows():
    execute([os.path.join(octocli_path, 'octo.exe'),
             'pack',
             '--format', 'zip',
             '--id', re.sub('[^0-9a-zA-Z]', '_', parser.project_name),
             '--version', date,
             '--basePath', os.getcwd() + '\\export',
             '--outFolder', os.getcwd()])
else:
    _, _, _ = execute([os.path.join(octocli_path, 'octo'),
                       'pack',
                       '--format', 'zip',
                       '--id', re.sub('[^0-9a-zA-Z]', '_', parser.project_name),
                       '--version', date,
                       '--basePath', os.getcwd() + '/export',
                       '--outFolder', os.getcwd()])

print("Uploading Terraform module package")
if is_windows():
    _, _, _ = execute([os.path.join(octocli_path, 'octo.exe'),
                       'push',
                       '--apiKey', parser.api_key,
                       '--server', parser.server_url,
                       '--space', parser.upload_space_id,
                       '--package', os.getcwd() + "\\" +
                       re.sub('[^0-9a-zA-Z]', '_', parser.project_name) + '.' + date + '.zip',
                       '--replace-existing'])
else:
    _, _, _ = execute([os.path.join(octocli_path, 'octo'),
                       'push',
                       '--apiKey', parser.api_key,
                       '--server', parser.server_url,
                       '--space', parser.upload_space_id,
                       '--package', os.getcwd() + "/" +
                       re.sub('[^0-9a-zA-Z]', '_', parser.project_name) + '.' + date + '.zip',
                       '--replace-existing'])

print("##octopus[stdout-default]")

print("Done")

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "e9526501-09d5-490f-ac3f-5079735fe041",
  "Name": "Octopus - Serialize Project to Terraform",
  "Description": "Serialize an Octopus project as a Terraform module and upload the resulting package to the Octopus built in feed.\n\nThis step uses naming conventions to exclude resources from the generated module:\n\n* Variables starting with `Private.` are excluded\n* Runbooks starting with `__ ` are excluded\n* The environment called `Sync` is removed from any variable scopes\n\nBecause serializing Terraform modules is done via the API, the values of any secret variables are not available, and are not included in the module generated by this step.\n\nHowever, by following a variable naming and scoping convention, it is possible to export and then apply a project in a Terraform module recreating secret variables, without ever including the secrets in the exported module.\n\nThe project to be exported must define all secret variables with a unique name and a single value.  For example, the secret variable `Test.Database.Password` can be scoped to the `Test` environment and the secret variable `Production.Database.Password` can be scoped to the `Production` environment. You can not have a single secret variable called `Database.Password` with two values for the different environments though.\n\nTo collapse the unique secret variables into a single variable used by steps, it is possible to create a non-secret variable called `Database.Password`  with two values `#{Test.Database.Password}` and `#{Production.Database.Password}` scoped to appropriate environments.\n\nIn this way steps can still reference a single variable called `Database.Password`, but all secret variables have unique names and only one value.\n\nAll secret variables are then scoped to an additional environment called `Sync`, which means all secret variables are exposed to runbooks run in the `Step` environment. The `Sync` environment is used to apply the Terraform module exported by this step, `Apply a Terraform template` step to perform variable replacements with secret variables.\n\nThe secret values in the Terraform module then have default values set to the Octostache template referencing the secret variable. For example, the Octopus variables in the Terraform module have default values like `#{Test.Database.Password}` and `#{Production.Database.Password}`. These templates are replaced at runtime by the `Apply a Terraform template` step, run in the `Sync` environment, effectively injecting the secret values back into the newly created project.\n\nThis allows secret variables to be recreated with their original values, without ever exporting the secret values. ",
  "Version": 10,
  "ExportedAt": "2023-09-11T21:06:55.101Z",
  "ActionType": "Octopus.Script",
  "Author": "mcasperson",
  "Packages": [],
  "Parameters": [
    {
      "Id": "ca62a702-6eb3-4d01-b645-73fbb2a1ea86",
      "Name": "SerializeProject.Exported.Project.IgnoreAllChanges",
      "Label": "Ignore All Changes",
      "HelpText": "Selecting this option creates a Terraform module with the \"lifecycle.ignore_changes\" option set to \"all\" for all project resources. This allows the resources to be created if they do not exist, but won't update them if the module is reapplied. This value effectively enables the \"Ignore Variable Changes\" option.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "05fafe0b-b05f-4e7b-85c3-857b62dc4182",
      "Name": "SerializeProject.Exported.Project.IgnoreVariableChanges",
      "Label": "Ignore Variable Changes",
      "HelpText": "Selecting this option creates a Terraform module with the \"lifecycle.ignore_changes\" option set to \"all\" for Octopus variables (i.e. \"octopusdeploy_variable\" resources)\". This allows Octopus variables to be created if they do not exist, but won't update Octopus variable values if the module is reapplied. This value effectively enabled if the \"Ignore All Changes\" option is enabled.",
      "DefaultValue": "True",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "3b8f35b6-fc1a-442b-ae0e-3036f5436a7a",
      "Name": "SerializeProject.Exported.Project.IgnoreCacValues",
      "Label": "Ignore CaC Settings",
      "HelpText": "Enable this option to exclude any Config-as-Code managed resources from the exported module, such as non-secret variables, deployment process, and CaC defined project settings. This option is useful when you are exporting CaC enabled projects and do not wish to include any settings in the exported module that are managed by Git. Disable this option, and enable the \"Exclude CaC Settings\" option to essentially convert CaC projects to regular projects.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "5c315650-9ba8-48b8-a02c-269315277fea",
      "Name": "SerializeProject.Exported.Project.ExcludeCacProjectValues",
      "Label": "Exclude CaC Settings",
      "HelpText": "Enable this option to exclude Config-as-Code settings from the exported module, such as Git credentials and the version controlled flag. Enable this option, and disable the \"Ignore CaC Settings\" option to essentially convert CaC projects to regular projects.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "9c18e779-bddc-4f74-81c6-9d75babc9c9c",
      "Name": "SerializeProject.ThisInstance.Terraform.Backend",
      "Label": "Terraform Backend",
      "HelpText": "The [backed](https://developer.hashicorp.com/terraform/language/settings/backends/configuration) to define in the Terraform module.",
      "DefaultValue": "s3",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "f96cd929-1c18-4f7c-9121-82904e64834e",
      "Name": "SerializeProject.ThisInstance.Server.Url",
      "Label": "Octopus Server URL",
      "HelpText": "The URL of the Octopus Server hosting the project to be serialized.",
      "DefaultValue": "#{Octopus.Web.ServerUri}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "95e0f611-d1f2-4317-ad5e-9131de73bbbe",
      "Name": "SerializeProject.ThisInstance.Api.Key",
      "Label": "Octopus API Key",
      "HelpText": "The Octopus API Key",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "9473de1f-a633-41d0-ba2c-6b622ce65551",
      "Name": "SerializeProject.Exported.Space.Id",
      "Label": "Octopus Space ID",
      "HelpText": "The Space ID containing the project to be exported",
      "DefaultValue": "#{Octopus.Space.Id}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "9d8af8e2-307a-4149-a28e-f75cec9ee044",
      "Name": "SerializeProject.Exported.Project.Name",
      "Label": "Octopus Project Name",
      "HelpText": "The name of the project to serialize.",
      "DefaultValue": "#{Octopus.Project.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "1b8ce71f-0931-4966-805a-e6d0ec12e3a0",
      "Name": "SerializeProject.Octopus.UploadSpace.Id",
      "Label": "Octopus Upload Space ID",
      "HelpText": "The ID of the space to upload the Terraform package to. Leave this blank to upload to the space defined in the `Octopus Space ID` parameter.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "aec82033-cae1-4a18-a315-c70468f71539",
      "Name": "SerializeProject.Exported.Project.IgnoredAccounts",
      "Label": "Ignored Accounts",
      "HelpText": "A comma separated list of accounts that will not be included in the Terraform module. These accounts are often those used by Runbooks that are not included in the module, and so do not need to be referenced.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "e45abab5-cb8f-4af2-b3e9-3cde057907df",
      "Name": "Exported.Project.IgnoredLibraryVariableSet",
      "Label": "Ignored Library Variables Sets",
      "HelpText": "A comma separated list of library variables sets that will not be included in the Terraform module. These library variable sets are often those used by Runbooks that are not included in the module, and so do not need to be referenced.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "e456bc3f-a537-4982-8963-a091d3f31cf0",
      "Name": "SerializeProject.Exported.Project.IncludeStepTemplates",
      "Label": "Include Step Templates",
      "HelpText": "Enable this option to export step templates referenced by a project. Disable this option to have step templates detached in projects instead.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "d9dacd25-5b0f-4f3e-89e2-08eefc0ffb89",
      "Name": "SerializeProject.Exported.Project.LookupProjectLinkTenants",
      "Label": "Link Tenants and Create Tenant Variables",
      "HelpText": "Enable this option to have each project link any tenants and create project tenant variables.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.RunOnServer": "true",
    "Octopus.Action.Script.ScriptBody": "import argparse\nimport os\nimport stat\nimport re\nimport socket\nimport subprocess\nimport sys\nfrom datetime import datetime\nfrom urllib.parse import urlparse\nimport urllib.request\nfrom itertools import chain\nimport platform\nfrom urllib.request import urlretrieve\nimport zipfile\nimport json\nimport tarfile\nimport random, time\n\n# If this script is not being run as part of an Octopus step, return variables from environment variables.\n# Periods are replaced with underscores, and the variable name is converted to uppercase\nif \"get_octopusvariable\" not in globals():\n    def get_octopusvariable(variable):\n        return os.environ[re.sub('\\\\.', '_', variable.upper())]\n\n# If this script is not being run as part of an Octopus step, print directly to std out.\nif \"printverbose\" not in globals():\n    def printverbose(msg):\n        print(msg)\n\n\ndef printverbose_noansi(output):\n    \"\"\"\n    Strip ANSI color codes and print the output as verbose\n    :param output: The output to print\n    \"\"\"\n    if not output:\n        return\n\n    # https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python\n    output_no_ansi = re.sub(r'\\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])', '', output)\n    printverbose(output_no_ansi)\n\n\ndef get_octopusvariable_quiet(variable):\n    \"\"\"\n    Gets an octopus variable, or an empty string if it does not exist.\n    :param variable: The variable name\n    :return: The variable value, or an empty string if the variable does not exist\n    \"\"\"\n    try:\n        return get_octopusvariable(variable)\n    except:\n        return ''\n\n\ndef retry_with_backoff(fn, retries=5, backoff_in_seconds=1):\n    x = 0\n    while True:\n        try:\n            return fn()\n        except Exception as e:\n\n            print(e)\n\n            if x == retries:\n                raise\n\n            sleep = (backoff_in_seconds * 2 ** x +\n                     random.uniform(0, 1))\n            time.sleep(sleep)\n            x += 1\n\n\ndef execute(args, cwd=None, env=None, print_args=None, print_output=printverbose_noansi):\n    \"\"\"\n        The execute method provides the ability to execute external processes while capturing and returning the\n        output to std err and std out and exit code.\n    \"\"\"\n    process = subprocess.Popen(args,\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE,\n                               text=True,\n                               cwd=cwd,\n                               env=env)\n    stdout, stderr = process.communicate()\n    retcode = process.returncode\n\n    if print_args is not None:\n        print_output(' '.join(args))\n\n    if print_output is not None:\n        print_output(stdout)\n        print_output(stderr)\n\n    return stdout, stderr, retcode\n\n\ndef is_windows():\n    return platform.system() == 'Windows'\n\n\ndef init_argparse():\n    parser = argparse.ArgumentParser(\n        usage='%(prog)s [OPTION] [FILE]...',\n        description='Serialize an Octopus project to a Terraform module'\n    )\n    parser.add_argument('--ignore-all-changes',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.IgnoreAllChanges') or get_octopusvariable_quiet(\n                            'Exported.Project.IgnoreAllChanges') or 'false',\n                        help='Set to true to set the \"lifecycle.ignore_changes\" ' +\n                             'setting on each exported resource to \"all\"')\n    parser.add_argument('--ignore-variable-changes',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.IgnoreVariableChanges') or get_octopusvariable_quiet(\n                            'Exported.Project.IgnoreVariableChanges') or 'false',\n                        help='Set to true to set the \"lifecycle.ignore_changes\" ' +\n                             'setting on each exported octopus variable to \"all\"')\n    parser.add_argument('--terraform-backend',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.ThisInstance.Terraform.Backend') or get_octopusvariable_quiet(\n                            'ThisInstance.Terraform.Backend') or 'pg',\n                        help='Set this to the name of the Terraform backend to be included in the generated module.')\n    parser.add_argument('--server-url',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.ThisInstance.Server.Url') or get_octopusvariable_quiet(\n                            'ThisInstance.Server.Url'),\n                        help='Sets the server URL that holds the project to be serialized.')\n    parser.add_argument('--api-key',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.ThisInstance.Api.Key') or get_octopusvariable_quiet(\n                            'ThisInstance.Api.Key'),\n                        help='Sets the Octopus API key.')\n    parser.add_argument('--space-id',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Space.Id') or get_octopusvariable_quiet(\n                            'Exported.Space.Id') or get_octopusvariable_quiet('Octopus.Space.Id'),\n                        help='Set this to the space ID containing the project to be serialized.')\n    parser.add_argument('--project-name',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.Name') or get_octopusvariable_quiet(\n                            'Exported.Project.Name') or get_octopusvariable_quiet(\n                            'Octopus.Project.Name'),\n                        help='Set this to the name of the project to be serialized.')\n    parser.add_argument('--upload-space-id',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Octopus.UploadSpace.Id') or get_octopusvariable_quiet(\n                            'Octopus.UploadSpace.Id') or get_octopusvariable_quiet('Octopus.Space.Id'),\n                        help='Set this to the space ID of the Octopus space where ' +\n                             'the resulting package will be uploaded to.')\n    parser.add_argument('--ignore-cac-managed-values',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.IgnoreCacValues') or get_octopusvariable_quiet(\n                            'Exported.Project.IgnoreCacValues') or 'false',\n                        help='Set this to true to exclude cac managed values like non-secret variables, ' +\n                             'deployment processes, and project versioning into the Terraform module. ' +\n                             'Set to false to have these values embedded into the module.')\n    parser.add_argument('--exclude-cac-project-settings',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.ExcludeCacProjectValues') or get_octopusvariable_quiet(\n                            'Exported.Project.ExcludeCacProjectValues') or 'false',\n                        help='Set this to true to exclude CaC settings like git connections from the exported module.')\n    parser.add_argument('--ignored-library-variable-sets',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.IgnoredLibraryVariableSet') or get_octopusvariable_quiet(\n                            'Exported.Project.IgnoredLibraryVariableSet'),\n                        help='A comma separated list of library variable sets to ignore.')\n    parser.add_argument('--ignored-accounts',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.IgnoredAccounts') or get_octopusvariable_quiet(\n                            'Exported.Project.IgnoredAccounts'),\n                        help='A comma separated list of accounts to ignore.')\n    parser.add_argument('--include-step-templates',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.IncludeStepTemplates') or get_octopusvariable_quiet(\n                            'Exported.Project.IncludeStepTemplates') or 'false',\n                        help='Set this to true to include step templates in the exported module. ' +\n                             'This disables the default behaviour of detaching step templates.')\n    parser.add_argument('--lookup-project-link-tenants',\n                        action='store',\n                        default=get_octopusvariable_quiet(\n                            'SerializeProject.Exported.Project.LookupProjectLinkTenants') or get_octopusvariable_quiet(\n                            'Exported.Project.LookupProjectLinkTenants') or 'false',\n                        help='Set this option to link tenants and create tenant project variables.')\n\n    return parser.parse_known_args()\n\n\ndef get_latest_github_release(owner, repo, filename):\n    url = f\"https://api.github.com/repos/{owner}/{repo}/releases/latest\"\n    releases = urllib.request.urlopen(url).read()\n    contents = json.loads(releases)\n\n    download = [asset for asset in contents.get('assets') if asset.get('name') == filename]\n\n    if len(download) != 0:\n        return download[0].get('browser_download_url')\n\n    return None\n\n\ndef ensure_octo_cli_exists():\n    if is_windows():\n        print(\"Checking for the Octopus CLI\")\n        try:\n            stdout, _, exit_code = execute(['octo.exe', 'help'])\n            printverbose(stdout)\n            if not exit_code == 0:\n                raise \"Octo CLI not found\"\n            return \"\"\n        except:\n            print(\"Downloading the Octopus CLI\")\n            urlretrieve('https://download.octopusdeploy.com/octopus-tools/9.0.0/OctopusTools.9.0.0.win-x64.zip',\n                        'OctopusTools.zip')\n            with zipfile.ZipFile('OctopusTools.zip', 'r') as zip_ref:\n                zip_ref.extractall(os.getcwd())\n            return os.getcwd()\n    else:\n        print(\"Checking for the Octopus CLI for Linux\")\n        try:\n            stdout, _, exit_code = execute(['octo', 'help'])\n            printverbose(stdout)\n            if not exit_code == 0:\n                raise \"Octo CLI not found\"\n            return \"\"\n        except:\n            print(\"Downloading the Octopus CLI for Linux\")\n            urlretrieve('https://download.octopusdeploy.com/octopus-tools/9.0.0/OctopusTools.9.0.0.linux-x64.tar.gz',\n                        'OctopusTools.tar.gz')\n            with tarfile.open('OctopusTools.tar.gz') as file:\n                file.extractall(os.getcwd())\n                os.chmod(os.path.join(os.getcwd(), 'octo'), stat.S_IRWXO | stat.S_IRWXU | stat.S_IRWXG)\n            return os.getcwd()\n\n\ndef ensure_octoterra_exists():\n    if is_windows():\n        print(\"Checking for the Octoterra tool for Windows\")\n        try:\n            stdout, _, exit_code = execute(['octoterra.exe', '-version'])\n            printverbose(stdout)\n            if not exit_code == 0:\n                raise \"Octoterra not found\"\n            return \"\"\n        except:\n            print(\"Downloading Octoterra CLI for Windows\")\n            retry_with_backoff(lambda: urlretrieve(\n                \"https://github.com/OctopusSolutionsEngineering/OctopusTerraformExport/releases/latest/download/octoterra_windows_amd64.exe\",\n                'octoterra.exe'), 10, 30)\n            return os.getcwd()\n    else:\n        print(\"Checking for the Octoterra tool for Linux\")\n        try:\n            stdout, _, exit_code = execute(['octoterra', '-version'])\n            printverbose(stdout)\n            if not exit_code == 0:\n                raise \"Octoterra not found\"\n            return \"\"\n        except:\n            print(\"Downloading Octoterra CLI for Linux\")\n            retry_with_backoff(lambda: urlretrieve(\n                \"https://github.com/OctopusSolutionsEngineering/OctopusTerraformExport/releases/latest/download/octoterra_linux_amd64\",\n                'octoterra'), 10, 30)\n            os.chmod(os.path.join(os.getcwd(), 'octoterra'), stat.S_IRWXO | stat.S_IRWXU | stat.S_IRWXG)\n            return os.getcwd()\n\n\noctocli_path = ensure_octo_cli_exists()\noctoterra_path = ensure_octoterra_exists()\nparser, _ = init_argparse()\n\n# Variable precondition checks\nif len(parser.server_url) == 0:\n    print(\"--server-url, ThisInstance.Server.Url, or SerializeProject.ThisInstance.Server.Url must be defined\")\n    sys.exit(1)\n\nif len(parser.api_key) == 0:\n    print(\"--api-key, ThisInstance.Api.Key, or ThisInstance.Api.Key must be defined\")\n    sys.exit(1)\n\nprint(\"Octopus URL: \" + parser.server_url)\nprint(\"Octopus Space ID: \" + parser.space_id)\n\n# Build the arguments to ignore library variable sets\nignores_library_variable_sets = parser.ignored_library_variable_sets.split(',')\nignores_library_variable_sets_args = [['-excludeLibraryVariableSet', x] for x in ignores_library_variable_sets]\n\n# Build the arguments to ignore accounts\nignored_accounts = parser.ignored_accounts.split(',')\nignored_accounts = [['-excludeAccounts', x] for x in ignored_accounts]\n\nos.mkdir(os.getcwd() + '/export')\n\nexport_args = [os.path.join(octoterra_path, 'octoterra'),\n               # the url of the instance\n               '-url', parser.server_url,\n               # the api key used to access the instance\n               '-apiKey', parser.api_key,\n               # add a postgres backend to the generated modules\n               '-terraformBackend', parser.terraform_backend,\n               # dump the generated HCL to the console\n               '-console',\n               # dump the project from the current space\n               '-space', parser.space_id,\n               # the name of the project to serialize\n               '-projectName', parser.project_name,\n               # ignoreProjectChanges can be set to ignore all changes to the project, variables, runbooks etc\n               '-ignoreProjectChanges=' + parser.ignore_all_changes,\n               # use data sources to lookup external dependencies (like environments, accounts etc) rather\n               # than serialize those external resources\n               '-lookupProjectDependencies',\n               # for any secret variables, add a default value set to the octostache value of the variable\n               # e.g. a secret variable called \"database\" has a default value of \"#{database}\"\n               '-defaultSecretVariableValues',\n               # Any value that can't be replaced with an Octostache template, add a dummy value\n               '-dummySecretVariableValues',\n               # detach any step templates, allowing the exported project to be used in a new space\n               '-detachProjectTemplates=' + str(not parser.include_step_templates),\n               # allow the downstream project to move between project groups\n               '-ignoreProjectGroupChanges',\n               # allow the downstream project to change names\n               '-ignoreProjectNameChanges',\n               # CaC enabled projects will not export the deployment process, non-secret variables, and other\n               # CaC managed project settings if ignoreCacManagedValues is true. It is usually desirable to\n               # set this value to true, but it is false here because CaC projects created by Terraform today\n               # save some variables in the database rather than writing them to the Git repo.\n               '-ignoreCacManagedValues=' + parser.ignore_cac_managed_values,\n               # Excluding CaC values means the resulting module does not include things like git credentials.\n               # Setting excludeCaCProjectSettings to true and ignoreCacManagedValues to false essentially\n               # converts a CaC project back to a database project.\n               '-excludeCaCProjectSettings=' + parser.exclude_cac_project_settings,\n               # This value is always true. Either this is an unmanaged project, in which case we are never\n               # reapplying it; or it is a variable configured project, in which case we need to ignore\n               # variable changes, or it is a shared CaC project, in which case we don't use Terraform to\n               # manage variables.\n               '-ignoreProjectVariableChanges=' + parser.ignore_variable_changes,\n               # To have secret variables available when applying a downstream project, they must be scoped\n               # to the Sync environment. But we do not need this scoping in the downstream project, so the\n               # Sync environment is removed from any variable scopes when serializing it to Terraform.\n               '-excludeVariableEnvironmentScopes', 'Sync',\n               # Exclude any variables starting with \"Private.\"\n               '-excludeProjectVariableRegex', 'Private\\\\..*',\n               # Capture the octopus endpoint, space ID, and space name as output vars. This is useful when\n               # querying th Terraform state file to know which space and instance the resources were\n               # created in. The scripts used to update downstream projects in bulk work by querying the\n               # Terraform state, finding all the downstream projects, and using the space name to only process\n               # resources that match the current tenant (because space names and tenant names are the same).\n               # The output variables added by this option are octopus_server, octopus_space_id, and\n               # octopus_space_name.\n               '-includeOctopusOutputVars',\n               # Where steps do not explicitly define a worker pool and reference the default one, this\n               # option explicitly exports the default worker pool by name. This means if two spaces have\n               # different default pools, the exported project still uses the pool that the original project\n               # used.\n               '-lookUpDefaultWorkerPools',\n               # Link any tenants that were originally link to the project and create project tenant variables\n               '-lookupProjectLinkTenants=' + parser.lookup_project_link_tenants,\n               # Add support for experimental step templates\n               '-experimentalEnableStepTemplates=' + parser.include_step_templates,\n               # The directory where the exported files will be saved\n               '-dest', os.getcwd() + '/export',\n               # This is a management runbook that we do not wish to export\n               '-excludeRunbookRegex', '__ .*'] + list(chain(*ignores_library_variable_sets_args)) + list(\n    chain(*ignored_accounts))\n\nprint(\"Exporting Terraform module\")\n_, _, octoterra_exit = execute(export_args)\n\nif not octoterra_exit == 0:\n    print(\"Octoterra failed. Please check the logs for more information.\")\n    sys.exit(1)\n\ndate = datetime.now().strftime('%Y.%m.%d.%H%M%S')\n\nprint(\"Creating Terraform module package\")\nif is_windows():\n    execute([os.path.join(octocli_path, 'octo.exe'),\n             'pack',\n             '--format', 'zip',\n             '--id', re.sub('[^0-9a-zA-Z]', '_', parser.project_name),\n             '--version', date,\n             '--basePath', os.getcwd() + '\\\\export',\n             '--outFolder', os.getcwd()])\nelse:\n    _, _, _ = execute([os.path.join(octocli_path, 'octo'),\n                       'pack',\n                       '--format', 'zip',\n                       '--id', re.sub('[^0-9a-zA-Z]', '_', parser.project_name),\n                       '--version', date,\n                       '--basePath', os.getcwd() + '/export',\n                       '--outFolder', os.getcwd()])\n\nprint(\"Uploading Terraform module package\")\nif is_windows():\n    _, _, _ = execute([os.path.join(octocli_path, 'octo.exe'),\n                       'push',\n                       '--apiKey', parser.api_key,\n                       '--server', parser.server_url,\n                       '--space', parser.upload_space_id,\n                       '--package', os.getcwd() + \"\\\\\" +\n                       re.sub('[^0-9a-zA-Z]', '_', parser.project_name) + '.' + date + '.zip',\n                       '--replace-existing'])\nelse:\n    _, _, _ = execute([os.path.join(octocli_path, 'octo'),\n                       'push',\n                       '--apiKey', parser.api_key,\n                       '--server', parser.server_url,\n                       '--space', parser.upload_space_id,\n                       '--package', os.getcwd() + \"/\" +\n                       re.sub('[^0-9a-zA-Z]', '_', parser.project_name) + '.' + date + '.zip',\n                       '--replace-existing'])\n\nprint(\"##octopus[stdout-default]\")\n\nprint(\"Done\")\n",
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "Python"
  },
  "Category": "Octopus",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/octopus-serialize-project-to-terraform.json",
  "Website": "/step-templates/e9526501-09d5-490f-ac3f-5079735fe041",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1QTFRFT6Tl////L5Pg8vj9Y67omsvwPJrisdfzfbzs5fL7y+T32Ov5isLucLXqvt31CJPHWwAABMJJREFUeNrs3deW4jAMAFDF3U75/89dlp0ZhiU4blJEjvQ8hYubLJsA00UCBCIQgQhEIAIRiEAEIhCBCEQgAhGIQAQiEIEIhD8kJm+t+QprfdKfB9HbYpx6CWfspj8HMi+gMgHL/AmQA8W3JTKH+ALFvzCeL0RbpyoCPE9IJeNOSQwh5Z3qd6yRGWQ2qi2cZQWxqj1WzQYSjeoJmJlAklOd4VlArOqPhQEkqBERToeMcfRJBkC0Uep8CfBpjz4JsHJ0zF3dkEWNje0kiB/sUC6eApndaIiCMyAa1PiwJ0AWhRGJHJJQHG2dC7h1rNbO1QOxSA7lNCkkKrQIpJCAB1GREILYIC1NAiwbpKFJgGWDNExcwGstfExcZBCHC6nOglshHtmhViLIig1RNBCN7qjtW8C0Z1UvJcC1Z9XmwMBzzvobmgAyEzgq91dtEEsBsQSQQAFZCSBAATEEEApHZbrVBIkkEIUPSVeB+KtALA0kXQUSrwKZBCIQBnk8Y4i5CsReBeKvkqLM+BCSDWJlrZFvGk9SRTHshkgjZCGAaArIxm3H3grhVzFlW2msfl1ca79UJ1bofYvsDHHlNdTZnlh5MghuPd5NdBDUNZHyCkfktIh03XzALGRPlBDPac7qgWjHZzWcmF5zmmkhidMQ6boKiDXcDTUEaylZqCGJ0Vjvu/fLJtHqhSANEvqb2OYqkOUqEHuVMbJcZdZCGiPhKhC4yjqiIjEE7XThMp8fAWII3mY3kUIQD+AMKQTzPiBhgQ63HlT/KSvgtoi0dq5mCPah1UIE0eh3sT0NhOByvKeAkFzi8PgQomumFhsyOxpIzZN4gLOj5plVwNpR0b2AuePWKBEHQu24pSsJA+LVCeHHQxZ1SiyDIdqok8IOhSSnTottHEQTdyt4ettAj4KkzA4dMikk2Dht2S5ptm1vswnPDxn0YyDZ5oDM3iToo2T5voWaYe+Q+vdjH80QyAzZhCgcDtLMI1Tmtz9w++XHgziHQHJJu/OZ3bs9Xn8gQ72NcP3dKqEfkp10F51xhoIi2I91R+LurXV/5q7pH+wx061CzO16oSQleMyr8fXvwMA0Pro8432DPD/ySx8XrHfSuDAM8n6UhnjQabaiXf5Bq/lREHvEeNtn1rJ08+C/uXkQZHeguxAPC3UvtcJYUogLzZX5hhZZvS6onG5lxXtzWGaygwb79vT/IXhdlNibwlKYOR6T8xjI7W8n+xV7T+GH4tMzWwR+lZhRkJYSsC0thpmCYqyngOz3rN2FLBZ2wZflBCggUHF0Vnp88JKienzIXLSEZCZqU7IKr/gQW9yx3pzV7Y9kvWZWTRRIqDmTtRUnU7b2lLcTYmoqHqnmiO1poER0SPkAeZMAZxaJx0Y3TCdAclsIqDz03ALcyxfTCZBsthoGXWmigGyVhWPLFJJfuuKQWycoEFdXbH4dJJoJxNR1eD/kshz6yn48cF8yW8sFoitflB1w6Q8n+/15Za7oA17/pYNmYgP5fmWm8L1NOHPWgK8kuFew1/JXtOA0yJCv7ah7X8ObUuT5kObU30+fDZm8+zqP+HTIpK0xQ796b5Kv2hSIQAQiEIEIRCACEYhABCIQgQhEIAIRiEAEIpBf8UeAAQAEjtYmlDTcCgAAAABJRU5ErkJggg==",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Monday, September 11, 2023