How Do I Set Variables On A Tenant?

I am using PowerShell to create a Tenant for a multi-tenanted deployment. I can create the tenant successfully and apply tag sets to it. For each tenant there are two variables to fill in, DBName and ServerName, but I cannot see how to assign values to them.

I have tried various things based on entries I’ve found on this support website and the wider internet, but to no avail.

I have tried the following based on a previous support post:

Add-Type -Path ‘C:\Program Files\Octopus Deploy\Octopus\Octopus.Client.dll’

$apikey = ‘API-’ # Get this from your profile
$octopusURI = ‘http://OctopusDeploy’ # Your Octopus Server address

$endpoint = New-Object Octopus.Client.OctopusServerEndpoint $octopusURI, $apiKey
$repository = New-Object Octopus.Client.OctopusRepository $endpoint

$project = $repository.Projects.FindByName(“Spotlight Database MT”)
$environment = $repository.Environments.FindByName(“Framework Dev”)

$tenantEditor = $repository.Tenants.CreateOrModify(“CM Test”)
$tenantEditor.WithTag($tagSet.Tags[1]) #Tags[1] is FDScheduled
$tenantEditor.ConnectToProjectAndEnvironments($project, $environment)

$Vars = $TenantEditor.Variables.Instance.LibraryVariables.Values[0]
$VarSet = $Vars.‘LibraryVariableSets-23’
$VarTemplate = $VarSet.Templates | Where-Object -Property Name -eq “DBName”
$VarSet.Variables[$varTemplate.Id] = “database_name”

$tenantEditor.Save()

The tenant is created successfully, but I cannot set the variables whatever I do.

I have also tried going through the projects collection (replacing the variable set block above), but that doesn’t work either.

$Vars = $TenantEditor.Variables.Instance.ProjectVariables
$VarSet = $Vars.‘Projects-361’
$VarTemplate = $VarSet.Templates | Where-Object -Property Name -eq “DBName”
$VarSet.Variables[$varTemplate.Id] = “database_name”

Any help would be greatly appreciated, the object model is unfathomable to me.

Thanks,
Chris.

First you need to create Project Template variables in your deployment project. In the web interface it’s on the project itself, under Variables > Project Template Variables. Then, when you connect the project to the tenant, you’ll be able to set those specific variables for the tenant.

Thanks Jason, I have done all of that, exactly as you say. I have tenants set up and working in the front end.

It’s just the code to do this without having to manually set up every tenant by hand is baffling, there’s nothing obvious about the object model at all and I can’t find a single simple example anywhere. If you have an example of the PowerShell code to achieve this I’d be very grateful.

1 Like

Hi @ChrisMichael and @jhealy,

Thanks for getting in touch! I wrote some C# code quite a while ago to set up a sample project using tenants. We built the API for our tooling in an API-first manner, and it works… What we didn’t do was build our API to be friendly for scripting, and I think this is something we should fix.

I started writing a targeted PowerShell sample to show how you can do this. It made me sad.

What if you could code something like this for setting a value for a Tenant+Project+Environment combination:
$repo.Tenants.SetVariableValue($project, $environment, "Database.Name", "xyz-database")

What if you could do this to set a “constant” value for a tenant?
$repo.Tenants.SetVariableValue("Tenant.Alias", "xyz")

If Octopus can make sense of the data you send it, it could do the heavy lifting to set the value on your behalf. If you’ve done something wrong in the set up of your tenant/project/variable/request, Octopus could try to tell you what’s gone wrong.

What do you think?

Hope that helps!
Mike

Hi Mike,

Yes, I’m fairly sad after being at this a day or two myself!

Either of your suggestions are great, it’s just finding what should go into the “SetVariableValue” script that is proving impossible at the moment. I get to the point where I can read variable values for tenants, but I can’t fathom a way of setting them!

I can’t find any comprehensive documentation for the API either, what’s out there appears to be community sourced and therefore understandably bitty.

1 Like

Hi Mike,

As a follow-up if you have some C# code that will do the job then could I have a look? While I was looking at PowerShell if we could do this through C# then I’m sure we could implement that if PowerShell isn’t quite up to the job yet.

Thanks,
Chris.

Hi Mike,

A bit of background, we potentially have a few thousand tenants to deploy database changes to if we can get this working on our live environment. Can you tell me if you have any Octopus Deploy sites working at this level of multi-tenanted applications? Also, could you let me know if there is another way rather than a multi-tenanted solution to perform these deployments?

Thanks,
Chris.

Hi @ChrisMichael,

Here is a C# sample, which may be a bit hard to read, but it has the relevant parts: https://github.com/OctopusDeploy/Sampler/blob/master/source/Sampler/Commands/MultiTenantSampleCommand.cs#L388

I’m working with my fellow engineers to provide a better API for this specific task, but it might be too late for your needs.

Tenants in Octopus were designed from the ground up to work with “thousands”. We have many customers using hundreds and thousands of tenants for different purposes.

If you haven’t already, I would suggest reading the tenanted deployment guide. If your scenario matches what we describe in the guidance, using tenants will generally make your life easier. There are some cases where tenants do not provide as much value, and if you start to run into really rough edges trying to model your scenario in Octopus, perhaps it’s worth rethinking.

Hope that helps!
Mike

Great, thanks Mike. I’ll have a look at the C# code, I’m sure we can make use of it.

Good to know that we’re on the right track with our setup for the number of tenants we are looking to deploy to.

Cheers,
Chris.

Hi Chris,

Great! Feel free to reach out if I can help in the meantime. Once you are done, I’d love to see the end result.

Mike

Hi Mike,

I managed to get what I need done in a C# console app. The end result is below, thanks for your help.

Chris.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Octopus.Client;
using Octopus.Client.Editors.Async;
using Octopus.Client.Model.DeploymentProcess;
using Octopus.Client.Model;
using Octopus.Client.Model.Endpoints;

namespace CreateOctoTenant
{
class Program
{
//Date: 12th April 2019
//Author: Chris Michael
//Purpose: Creates a new tenant, assigns an optional tag and then sets the database name and
// server name variables in the project variable set

    //Requires the following arguments:
    //arg[0]: Tenant Name
    //arg[1]: Database Name
    //arg[2]: Server Name
    //arg[3]: Tag Set Name (optional)
    //arg[3]: Tag Name (optional)
    //arg[5]: Project Name
    //arg[6]: Environment Name
    //arg[7]: API Key
    //arg[8]: Octopus URI
    static int Main(string[] args)
    {
        // Test if input arguments were supplied:
        if (args.Length != 9)
        {
            //if exactly 9 arguments are not supplied exit with code 99
            return 99;
        }

        //Connection properties for Octopus Deploy
        string apikey = args[7];
        string octopusURI = args[8];

        //Connect to Octopus Deploy repository
        OctopusServerEndpoint EndPoint = new OctopusServerEndpoint(octopusURI, apikey);
        OctopusRepository Repo = new OctopusRepository(EndPoint);

        //Get project and environment resources
        ProjectResource MyProj = Repo.Projects.FindByName(args[5]);
        EnvironmentResource MyEnv = Repo.Environments.FindByName(args[6]);

        //Get or Create tenant resources
        Octopus.Client.Editors.TenantEditor NewTenant = Repo.Tenants.CreateOrModify(args[0]);
        NewTenant.ConnectToProjectAndEnvironments(MyProj, MyEnv);
        NewTenant.Save();

        //Only ttry and add tenant tags if the tag set and tag are supplied
        if (args[3] != "" && args[4] != "")
        {

            //You can find the tag set by name...
            TagSetResource NewTagSet = Repo.TagSets.FindByName(args[3]);

            string TagId = "";
            //...but you have to loop to find the actual tag within the set!!
            foreach (TagResource Tag in NewTagSet.Tags)
            {
                if (Tag.Name == args[4])
                {
                    TagId = Tag.CanonicalTagName;
                    break;
                }
            }
            //Add the tag and save it
            NewTenant.Instance.TenantTags.Add(TagId);
            NewTenant.Save();
        }

        //Adding variables appears to require a TenantResource object rather than a TenantEditor object
        //so that's why we find our previously created tenant here
        TenantResource MyTenant = Repo.Tenants.FindByName(args[0]);
        TenantVariableResource MyTenantVars = Repo.Tenants.GetVariables(MyTenant);

        //Set new values
        PropertyValueResource DbName = new PropertyValueResource(args[1]);
        PropertyValueResource ServerName = new PropertyValueResource(args[2]);

        string DBNameid = "";
        string Serverid = "";

        //Get the IDs of the variables being changed
        //I have had to do this weird loop as there doesn't appear to be any way of accessing the elements of the
        //list with a name or label or anything except the element number
        //i.e. you can't do MyTenantVars.ProjectVariables[MyProj.Id].Templates["Tenant.Database.Name"].Id
        foreach (ActionTemplateParameterResource MyTemplate in MyTenantVars.ProjectVariables[MyProj.Id].Templates)
        {
            if(MyTemplate.Name == "Tenant.Database.Name")
            {
                DBNameid = MyTemplate.Id;
            }
            else if (MyTemplate.Name == "Tenant.Server.Name")
            {
                Serverid = MyTemplate.Id;
            }
        }

        //Get the project variables for the tenant
        var projectVariables = MyTenantVars.ProjectVariables[MyProj.Id].Variables[MyEnv.Id];

        //Set the database name
        if (projectVariables.ContainsKey(DBNameid))
        {
            projectVariables.Remove(DBNameid);
        }
        projectVariables.Add(DBNameid, DbName);

        //set the server name
        if (projectVariables.ContainsKey(Serverid))
        {
            projectVariables.Remove(Serverid);
        }
        projectVariables.Add(Serverid, ServerName);

        //modify the variables
        Repo.Tenants.ModifyVariables(MyTenant, MyTenantVars);

        return 0;
    }
}

}

Thanks @ChrisMichael. We’ve got plans for a bigger project to unify the variable concept across all of Octopus, but that’s a big pill to swallow. I’ve got a discussion hooked up this afternoon to see whether we can do something in the meantime without painting ourselves into a corner.

I’ll report back when I have more details. :slight_smile:

Glad to see you’ve got a working solution for now. :+1:

Mike

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.