Using rest API to assign values to tenants' template variables

We try to use multi-tenant feature to support deployments of our git topic branches. In order to do this tenants have to be dynamically created and deleted through the rest API. So far this works out very good. Now we need to assign values to the tenant’s template variables and this is where we have a problem. Here is what we did.

  • Right after the tenant has been created we used GET "/api/tenants/{tenant.Id}/variables" to get the list of variables that are expected to be assigned the values. Here is the sample response:
{
"TenantId":  "Tenants-104",
"TenantName":  "topic #ent-999",
"LibraryVariables":{
  "LibraryVariableSets-82":{
    "LibraryVariableSetId":  "LibraryVariableSets-82",
    "LibraryVariableSetName":  "Git Branching Support",
    "Templates":[{
      "Id":  "6a89d0f9-0ce4-4c6e-b693-314529df25ac",
      "Name":  "Tenant.IISWebAppPath",
      "Label":  "IIS Web Application Path",
      "HelpText":  "Alias to be assigned to this tenant's (git branch) web application within the module's web site",
      "DefaultValue":  null,
      "DisplaySettings":{
        "Octopus.ControlType":  "SingleLineText"
      }
    }],
    "Variables":{},
    "Links":{
      "LibraryVariableSet":  "/octopus/api/libraryvariablesets/LibraryVariableSets-82"
    }
  }
},
"Links":{
  "Self":  "/octopus/api/tenants/Tenants-104/variables",
  "Tenant":  "/octopus/api/tenants/Tenants-104"
  }
}
  • The property Variables is an empty object and this is where we need to put the value for the template variable. So, we added it and used POST "/api/tenants/{tenant.Id}/variables" with the following payload:
{
"TenantId":  "Tenants-104",
"TenantName":  "topic #ent-999",
"LibraryVariables":{
  "LibraryVariableSets-82":{
    "LibraryVariableSetId":  "LibraryVariableSets-82",
    "LibraryVariableSetName":  "Git Branching Support",
    "Templates":[{
      "Id":  "6a89d0f9-0ce4-4c6e-b693-314529df25ac",
      "Name":  "Tenant.IISWebAppPath",
      "Label":  "IIS Web Application Path",
      "HelpText":  "Alias to be assigned to this tenant's (git branch) web application within the module's web site",
      "DefaultValue":  null,
      "DisplaySettings":{
        "Octopus.ControlType":  "SingleLineText"
      }
    }],
    "Variables":{
      "6a89d0f9-0ce4-4c6e-b693-314529df25ac":  "ent-999"
    },
    "Links":{
      "LibraryVariableSet":  "/octopus/api/libraryvariablesets/LibraryVariableSets-82"
    }
  }
},
"Links":{
  "Self":  "/octopus/api/tenants/Tenants-104/variables",
  "Tenant":  "/octopus/api/tenants/Tenants-104"
  }
}

We got (500) Internal Server Error from the server. After that we examined what Octopus UI does and here is the payload that’s been sent:

{
"TenantId":"Tenants-104",
"TenantName":"topic #ent-999",
"LibraryVariables":{
  "LibraryVariableSets-82":{
    "LibraryVariableSetId":"LibraryVariableSets-82",
    "LibraryVariableSetName":"Git Branching Support",
    "Templates":[{
      "Id":"6a89d0f9-0ce4-4c6e-b693-314529df25ac",
      "Name":"Tenant.IISWebAppPath",
      "Label":"IIS Web Application Path",
      "HelpText":"Alias to be assigned to this tenant's (git branch) web application within the module's web site",
      "DefaultValue":null,
      "DisplaySettings":{
        "Octopus.ControlType":"SingleLineText"
        },
      "$$hashKey":"object:458"
    }],
    "Variables":{
      "6a89d0f9-0ce4-4c6e-b693-314529df25ac":"ent-999"
    },
    "Links":{
      "LibraryVariableSet":"/octopus/api/libraryvariablesets/LibraryVariableSets-82"
    }
  }
},
"LibraryVariableSetVariableTemplates":{
  "LibraryVariableSets-82":{
    "LibraryVariableSetId":"LibraryVariableSets-82",
    "LibraryVariableSetName":"Git Branching Support",
    "Templates":[{
      "Id":"6a89d0f9-0ce4-4c6e-b693-314529df25ac",
      "Name":"Tenant.IISWebAppPath",
      "Label":"IIS Web Application Path",
      "HelpText":"Alias to be assigned to this tenant's (git branch) web application within the module's web site",
      "DefaultValue":null,
      "DisplaySettings":{
        "Octopus.ControlType":"SingleLineText"
      },
      "$$hashKey":"object:458"
    }],
    "Variables":{
      "6a89d0f9-0ce4-4c6e-b693-314529df25ac":"ent-999"
    }
  }
},
"Links":{
  "Self":"/octopus/api/tenants/Tenants-104/variables",
  "Tenant":"/octopus/api/tenants/Tenants-104"
  }
}

What immediately stood out was the extra property LibraryVariableSetVariableTemplates which was the exact copy of LibraryVariables. The other difference was the extra property $$hashKey in the property Templates. The documentation is of no help here. Could you please guide me in how to assign a value to a tenant’s template variable?

Thanks,
Rosti Grigoriev

Hi Rosti,

Thanks for getting in touch! This is an advanced area of the Tenant features and Octopus and we were waiting to see how much interest there is in this kind of functionality before writing too much about it. We did design these resources to allow you to build your own integration for defining Tenant variable values, and I’d be interested in your feedback once you’ve got it working.

I’d like to investigate the 500 error you received and I’ll try and replicate locally after the weekend.

We are also just about to open source the Octopus Sampler which uses the C# SDK which makes working with these resources a lot easier. Here is a snippet from that sample which (hopefully) will give you some hints:

        private void FillOutTenantVariablesByConvention(
            TenantEditor tenantEditor,
            ProjectResource[] projects,
            EnvironmentResource[] environments,
            LibraryVariableSetResource[] libraryVariableSets)
        {
            var tenant = tenantEditor.Instance;
            var projectLookup = projects.ToDictionary(p => p.Id);
            var libraryVariableSetLookup = libraryVariableSets.ToDictionary(l => l.Id);
            var environmentLookup = environments.ToDictionary(e => e.Id);

            var tenantVariables = tenantEditor.Variables.Instance;

            // Library variables
            foreach (var libraryVariable in tenantVariables.LibraryVariables)
            {
                foreach (var template in libraryVariableSetLookup[libraryVariable.Value.LibraryVariableSetId].Templates)
                {
                    var value = TryFillLibraryVariableByConvention(template, tenant);
                    if (value != null)
                    {
                        libraryVariable.Value.Variables[template.Id] = value;
                    }
                }
            }

            // Project variables
            foreach (var projectVariable in tenantVariables.ProjectVariables)
            {
                foreach (var template in projectLookup[projectVariable.Value.ProjectId].Templates)
                {
                    foreach (var connectedEnvironmentId in tenant.ProjectEnvironments[projectVariable.Value.ProjectId])
                    {
                        var environment = environmentLookup[connectedEnvironmentId];
                        var value = TryFillProjectVariableByConvention(template, tenant, environment);
                        if (value != null)
                        {
                            projectVariable.Value.Variables[connectedEnvironmentId][template.Id] = value;
                        }
                    }
                }
            }
        }

        private PropertyValueResource TryFillLibraryVariableByConvention(ActionTemplateParameterResource template, TenantResource tenant)
        {
            if (template.Name == VariableKeys.StandardTenantDetails.TenantAlias) return new PropertyValueResource(tenant.Name.Replace(" ", "-").ToLowerInvariant());
            if (template.Name == VariableKeys.StandardTenantDetails.TenantRegion) return new PropertyValueResource(Region.All.GetRandom().Alias);
            if (template.Name == VariableKeys.StandardTenantDetails.TenantContactEmail) return new PropertyValueResource(tenant.Name.Replace(" ", ".").ToLowerInvariant() + "@test.com");

            return null;
        }

        private PropertyValueResource TryFillProjectVariableByConvention(ActionTemplateParameterResource template, TenantResource tenant, EnvironmentResource environment)
        {
            if (template.Name == VariableKeys.ProjectTenantVariables.TenantDatabasePassword) return new PropertyValueResource(RandomStringGenerator.Generate(16), isSensitive: true);

            return null;
        }

Hope that helps!
Mike

Hi Rosti,

Rob Wagner has gone to the effort to open-source our Sampler today: https://github.com/OctopusDeploy/Sampler

Likewise I started working on a sample for migrating from an Environment-per-tenant model to Tenants which also uses the TenantVariables API in a slightly different way: https://github.com/OctopusDeploy/TenantMigratorSample

Hope that helps!
Mike

After some trial and error, I was able to successfully updated update the tenant project variables with this JSON. For whatever reason, you have to make sure you have TenantId and ProjectId or the values won’t change.

{ "TenantId": "Tenants-46", "ProjectVariables": { "Projects-1": { "ProjectId": "Projects-1", "Variables": { "Environments-1": { "059aace9-983b-4677-a348-b0cf659da7d2": "VALUE1", "c8310370-006b-4013-b886-3677be4f6158": "VALUE2", "e3882f47-43dc-481d-b5bf-0c5108825234": "VALUE3" } } } } }

Maybe it works for ProjectVariables but I am trying to set LibraryVariables. I gave up at this moment.

Hi Rosti and Troy,

Thanks for getting back to me. Are you willing/able to use the C# SDK, or do you prefer to make direct HTTP requests?

Kind Regards!
Mike

In my case, I need to use direct HTTP requests.

We have to use rest API, since part of our automation infrastructure includes a linux machine hosting our git server and we use git hooks to create and deploy Octopus releases. But generally speaking I prefer to use the rest API anyway - much cleaner and much simpler :slight_smile: