Esempio n. 1
0
        private async Task BuildClientProject(ProjectGroupResource projectGroup, LifecycleResource normalLifecycle)
        {
            var clientProjectEditor = await Repository.Projects.CreateOrModify("Truck Tracker Client", projectGroup, normalLifecycle);

            clientProjectEditor.SetLogo(SampleImageCache.DownloadImage("http://b2bimg.bridgat.com/files/GPS_Camera_TrackerGPS_Camera_Tracking.jpg", "GPS_Camera_TrackerGPS_Camera_Tracking.jpg"));

            var variables = await clientProjectEditor.Variables;

            variables.AddOrUpdateVariableValue("TrackerUrl", "https://trucktracker.com/trucks/#{Octopus.Machine.Name}");

            var channel = await clientProjectEditor.Channels.CreateOrModify("1.x Normal", "The channel for stable releases that will be deployed to our production trucks.");

            channel.SetAsDefaultChannel();

            await clientProjectEditor.Channels.Delete("Default");

            var deploymentProcess = await clientProjectEditor.DeploymentProcess;

            deploymentProcess.AddOrUpdateStep("Deploy Application")
            .TargetingRoles("truck")
            .AddOrUpdateScriptAction("Deploy Application", new InlineScriptActionFromFileInAssembly("TrucksSample.Client.Deploy.fsx"), ScriptTarget.Target);

            await clientProjectEditor.Triggers.CreateOrModify("Auto-Deploy to trucks when available",
                                                              ProjectTriggerType.DeploymentTarget,
                                                              ProjectTriggerConditionEvent.ExistingDeploymentTargetChangesState,
                                                              ProjectTriggerConditionEvent.NewDeploymentTargetBecomesAvailable);

            await clientProjectEditor.Save();
        }
        private async Task <ProjectResource> BuildClientProject(ProjectGroupResource projectGroup, LifecycleResource normalLifecycle, LibraryVariableSetResource[] libraryVariableSets, Func <string, TagResource> getTag)
        {
            Log.Information("Setting up client project...");
            var clientProjectEditor = await Repository.Projects.CreateOrModify("Truck Tracker Client", projectGroup, normalLifecycle);

            await clientProjectEditor.SetLogo(SampleImageCache.DownloadImage("http://b2bimg.bridgat.com/files/GPS_Camera_TrackerGPS_Camera_Tracking.jpg", "GPS_Camera_TrackerGPS_Camera_Tracking.jpg"));

            clientProjectEditor.IncludingLibraryVariableSets(libraryVariableSets)
            .Customize(p => p.TenantedDeploymentMode = TenantedDeploymentMode.Tenanted);

            var channel = await clientProjectEditor.Channels.CreateOrModify("1.x Normal", "The channel for stable releases that will be deployed to our production trucks.");

            channel.SetAsDefaultChannel()
            .AddOrUpdateTenantTags(getTag("Canary"), getTag("Stable"));

            await clientProjectEditor.Channels.Delete("Default");

            var deploymentProcess = await clientProjectEditor.DeploymentProcess;

            deploymentProcess.AddOrUpdateStep("Deploy Application")
            .TargetingRoles("truck")
            .AddOrUpdateScriptAction("Deploy Application", new InlineScriptActionFromFileInAssembly("TrucksSample.Client.Deploy.fsx"), ScriptTarget.Target);

            var machineFilter = new MachineFilterResource();

            machineFilter.EventGroups.Add("MachineAvailableForDeployment");
            await clientProjectEditor.Triggers.CreateOrModify("Auto-Deploy to trucks when available",
                                                              machineFilter,
                                                              new AutoDeployActionResource());

            await clientProjectEditor.Save();

            return(clientProjectEditor.Instance);
        }
Esempio n. 3
0
        private async Task BuildServerProject(ProjectGroupResource projectGroup, LifecycleResource normalLifecycle)
        {
            var serverProjectEditor = await Repository.Projects.CreateOrModify("Truck Tracker Server", projectGroup, normalLifecycle);

            serverProjectEditor.SetLogo(SampleImageCache.DownloadImage("http://blog.budgettrucks.com.au/wp-content/uploads/2015/08/tweed-heads-moving-truck-rental-map.jpg"));

            (await serverProjectEditor.Variables).AddOrUpdateVariableValue("DatabaseConnectionString", $"Server=trackerdb.com;Database=trackerdb;");
            (await serverProjectEditor.DeploymentProcess).AddOrUpdateStep("Deploy Application")
            .AddOrUpdateScriptAction("Deploy Application", new InlineScriptActionFromFileInAssembly("TrucksSample.Server.Deploy.fsx"), ScriptTarget.Server);

            await serverProjectEditor.Save();
        }
        private async Task BuildProject(
            ProjectGroupResource projectGroup,
            LifecycleResource normalLifecycle,
            AccountResource account)
        {
            var projectEditor = await Repository.Projects.CreateOrModify("Azure Cloud Service Sample", projectGroup, normalLifecycle);

            await projectEditor.SetLogo(SampleImageCache.DownloadImage("https://azurecomcdn.azureedge.net/cvt-9c42e10c78bceeb8622e49af8d0fe1a20cd9ca9f4983c398d0b356cf822d8844/images/shared/social/azure-icon-250x250.png"));

            var variableEditor = await projectEditor.Variables;

            if (variableEditor.Instance.Variables.All(v => v.Name != "UniqueName"))
            {
                variableEditor.AddOrUpdateVariableValue("UniqueName", Guid.NewGuid().ToString("N"));
            }
            variableEditor
            .AddOrUpdateVariableValue("CloudService", "AzureCloudService#{UniqueName}")
            .AddOrUpdateVariableValue("StorageAccount", storageAccountName)
            .AddOrUpdateVariableValue("Location", location);

            var processEditor = await projectEditor.DeploymentProcess;
            var process       = processEditor.Instance;

            process.Steps.Add(new DeploymentStepResource
            {
                Name      = "Create Azure Cloud Service",
                Condition = DeploymentStepCondition.Success,
                RequiresPackagesToBeAcquired = false,
                Actions =
                {
                    new DeploymentActionResource
                    {
                        ActionType = "Octopus.AzurePowerShell",
                        Name       = "Create Azure Cloud Service",
                        Properties =
                        {
                            { "Octopus.Action.Script.ScriptBody", "New-AzureService -ServiceName #{CloudService} -Location \"#{Location}\""   },
                            { "Octopus.Action.Azure.AccountId",   account.Id                                                                  }
                        }
                    }
                }
            });

            process.Steps.Add(new DeploymentStepResource
            {
                Name      = "Deploy Azure Cloud Service",
                Condition = DeploymentStepCondition.Success,
                RequiresPackagesToBeAcquired = true,
                Actions =
                {
                    new DeploymentActionResource
                    {
                        ActionType = "Octopus.AzureCloudService",
                        Name       = "Deploy Azure Cloud Service",
                        Properties =
                        {
                            { "Octopus.Action.Azure.AccountId",               account.Id                                            },
                            { "Octopus.Action.Azure.CloudServiceName",        "#{CloudService}"                                     },
                            { "Octopus.Action.Azure.StorageAccountName",      "#{StorageAccount}"                                   },
                            { "Octopus.Action.Azure.Slot",                    "Staging"                                             },
                            { "Octopus.Action.Azure.SwapIfPossible",          "False"                                               },
                            { "Octopus.Action.Azure.UseCurrentInstanceCount", "False"                                               },
                            { "Octopus.Action.Package.PackageId",             "Octopus.Sample.AzureCloudService"                    },
                            { "Octopus.Action.Package.FeedId",                "feeds-builtin"                                       }
                        }
                    }
                }
            });

            process.Steps.Add(new DeploymentStepResource
            {
                Name      = "Remove Azure Cloud Service",
                Condition = DeploymentStepCondition.Always,
                RequiresPackagesToBeAcquired = false,
                Actions =
                {
                    new DeploymentActionResource
                    {
                        ActionType = "Octopus.AzurePowerShell",
                        Name       = "Remove Azure Cloud Service",
                        Properties =
                        {
                            { "Octopus.Action.Script.ScriptBody", "Remove-AzureService -ServiceName #{CloudService} -Force"   },
                            { "Octopus.Action.Azure.AccountId",   account.Id                                                  }
                        }
                    }
                }
            });


            await projectEditor.Save();
        }
        protected override async Task Execute()
        {
            Log.Information("Building trucks sample with {TrucksCount} trucks using multi-tenant deployments...", NumberOfTrucks);

            await EnsureMultitenancyFeature();

            Log.Information("Setting up environment...");
            var environments = new[]
            {
                (await Repository.Environments.CreateOrModify("Trucks Production", LipsumRobinsonoKruso.GenerateLipsum(1))).Instance
            };
            var normalLifecycle = await Repository.Lifecycles.CreateOrModify("Trucks Normal Lifecycle", "The normal lifecycle for the trucks sample");

            await normalLifecycle.AsSimplePromotionLifecycle(environments.ToArray()).Save();

            var projectGroup = await Repository.ProjectGroups.CreateOrModify("Trucks sample");

            Log.Information("Setting up tags...");
            var tagSetTruckType = await Repository.TagSets.CreateOrModify("Truck type", "Allows you to categorize tenants");

            await tagSetTruckType.AddOrUpdateTag("General Waste", "These are the trucks that deal with general waste", TagResource.StandardColor.DarkRed)
            .AddOrUpdateTag("Green Waste", "These are the trucks that deal with green waste", TagResource.StandardColor.DarkGreen)
            .AddOrUpdateTag("Recycling", "These are the trucks that deal with recycling", TagResource.StandardColor.LightYellow)
            .Save();

            var tagSetRing = await Repository.TagSets.CreateOrModify("Upgrade ring", "The order in which to upgrade sets of trucks");

            await tagSetRing.AddOrUpdateTag("Canary", "Upgrade these trucks first", TagResource.StandardColor.LightYellow)
            .AddOrUpdateTag("Stable", "Upgrade these trucks last", TagResource.StandardColor.LightGreen)
            .Save();

            var allTags = new TagResource[0]
                          .Concat(tagSetTruckType.Instance.Tags)
                          .Concat(tagSetRing.Instance.Tags)
                          .ToArray();

            var getTag = new Func <string, TagResource>(name => allTags.FirstOrDefault(t => t.Name == name));

            Log.Information("Setting up variables...");
            var standardTruckVarEditor = await Repository.LibraryVariableSets.CreateOrModify("Standard truck details", "The standard details we require for all trucks");

            standardTruckVarEditor.VariableTemplates
            .AddOrUpdateSingleLineTextTemplate(VariableKeys.StandardTenantDetails.TruckAlias,
                                               "Alias", defaultValue: null, helpText: "This alias will be used to build convention-based settings for the truck");
            await standardTruckVarEditor.Save();

            var libraryVariableSets = new[] { standardTruckVarEditor.Instance };

            await BuildServerProject(projectGroup.Instance, normalLifecycle.Instance);

            var clientProject = await BuildClientProject(projectGroup.Instance, normalLifecycle.Instance, libraryVariableSets, getTag);

            var logos = new Dictionary <string, string>
            {
                { "General Waste", "http://images.moc-pages.com/user_images/16939/1240756821m_SPLASH.jpg" },
                { "Green Waste", "http://cache.lego.com/e/dynamic/is/image/LEGO/4432?$main$" },
                { "Recycling", "http://lego.lk/wp-content/uploads/2016/03/Garbage-Truck.png" },
            };

            var proj = new[] { clientProject };
            var env  = environments.Where(e => e.Name.EndsWith("Production")).ToArray();

            var trucks = await Task.WhenAll(
                Enumerable.Range(0, NumberOfTrucks)
                .Select(i => new
            {
                Name = $"Truck-{i:0000}",
                TruckType = tagSetTruckType.Instance.Tags.ElementAt(i % 3),
                UpgradeRing = i % 5 == 0 ? getTag("Canary") : getTag("Stable")
            })
                .Select(async(x, i) =>
            {
                Log.Information("Setting up tenant for truck {TruckName}...", x.Name);
                var tenantEditor = await Repository.Tenants.CreateOrModify(x.Name);
                await tenantEditor.SetLogo(SampleImageCache.DownloadImage(logos[x.TruckType.Name]));
                tenantEditor.ClearTags().WithTag(x.TruckType).WithTag(x.UpgradeRing);

                tenantEditor.ClearProjects();
                foreach (var project in proj)
                {
                    tenantEditor.ConnectToProjectAndEnvironments(project, env);
                }
                await tenantEditor.Save();

                // Ensure the tenant is saved before we attempt to fill out variables - otherwise we don't know what projects they are connected to
                await FillOutTenantVariablesByConvention(tenantEditor, proj, env, libraryVariableSets);

                await tenantEditor.Save();
                return(tenantEditor.Instance);
            })
                .ToArray()
                );

            foreach (var truck in trucks)
            {
                Log.Information("Setting up hosting for {TruckName}...", truck.Name);
                var dedicatedHost = Repository.Machines.CreateOrModify(truck.Name, new CloudRegionEndpointResource(), env, new[] { "truck" }, new[] { truck }, new TagResource[0], TenantedDeploymentMode.Tenanted);
            }

            Log.Information("Created {TruckCount} trucks.", trucks.Length);

            await StartTrucksMoving(trucks);
        }
        protected override async Task Execute()
        {
            if (NumberOfProjects >= ProjectNames.Length)
            {
                throw new CommandException($"Please create up to {ProjectNames.Length} projects, we only have so many random names!");
            }

            Log.Information("Building multi-tenant sample with {ProjectCount} projects, {CustomerCount} customers and {TesterCount} testers...",
                            NumberOfProjects, NumberOfCustomers, NumberOfTesters);

            await EnsureMultitenancyFeature();

            Log.Information("Setting up environments...");
            var allEnvironmentsTasks = new[] { "MT Dev", "MT Test", "MT Beta", "MT Staging", "MT Production" }
            .Select(name => Repository.Environments.CreateOrModify(name, LipsumRobinsonoKruso.GenerateLipsum(1)))
            .ToArray();
            var allEnvironments = (await Task.WhenAll(allEnvironmentsTasks)).Select(e => e.Instance).ToArray();
            var normalLifecycle = await Repository.Lifecycles.CreateOrModify("MT Normal Lifecycle", "The normal lifecycle for the multi-tenant deployments sample");

            await normalLifecycle.AsSimplePromotionLifecycle(allEnvironments.Where(e => e.Name != "MT Beta").ToArray()).Save();

            var betaLifecycle = await Repository.Lifecycles.CreateOrModify("MT Beta Lifecycle", "The beta lifecycle for the multi-tenant deployments sample");

            await betaLifecycle.AsSimplePromotionLifecycle(allEnvironments.Take(3).ToArray()).Save();

            var projectGroup = await Repository.ProjectGroups.CreateOrModify("Multi-tenancy sample");

            Log.Information("Setting up tags...");
            var tagSetTenantType = await Repository.TagSets.CreateOrModify("Tenant type", "Allows you to categorize tenants");

            await tagSetTenantType.AddOrUpdateTag("Internal", "These are internal tenants, like our test team")
            .AddOrUpdateTag("External", "These are external tenants, our real customers", TagResource.StandardColor.LightBlue)
            .Save();

            var tagSetImportance = await Repository.TagSets.CreateOrModify("Tenant importance", "Allows you to have different customers that we should pay more or less attention to");

            await tagSetImportance.AddOrUpdateTag("VIP", "Very important tenant - pay attention!", TagResource.StandardColor.DarkRed)
            .AddOrUpdateTag("Standard", "These are our standard customers")
            .AddOrUpdateTag("Trial", "These are trial customers", TagResource.StandardColor.DarkPurple)
            .Save();

            var tagSetRing = await Repository.TagSets.CreateOrModify("Upgrade ring", "What kind of upgrade stability to these customers want");

            await tagSetRing.AddOrUpdateTag("Tester", "These are our internal test team members", TagResource.StandardColor.LightCyan)
            .AddOrUpdateTag("Early adopter", "Upgrade these customers first", TagResource.StandardColor.LightYellow)
            .AddOrUpdateTag("Stable", "Upgrade these customers last", TagResource.StandardColor.LightGreen)
            .AddOrUpdateTag("Pinned", "Don't upgrade these customers until they come back to the stable ring", TagResource.StandardColor.DarkRed)
            .Save();

            var tagSetEarlyAccessProgram = await Repository.TagSets.CreateOrModify("Early access program", "Provides tenants with access to certain early access programs.");

            await tagSetEarlyAccessProgram.AddOrUpdateTag("2.x Beta", "These customers are part of our 2.x beta program", TagResource.StandardColor.LightPurple)
            .Save();

            var tagSetHosting = await Repository.TagSets.CreateOrModify("Hosting", "Allows you to define where the tenant software should be hosted");

            await tagSetHosting.AddOrUpdateTag("Internal-Shared-Farm", "The internal test server farm")
            .AddOrUpdateTag("Shared-Farm-1", "Shared server farm 1", TagResource.StandardColor.LightGreen)
            .AddOrUpdateTag("Shared-Farm-2", "Shared server farm 2", TagResource.StandardColor.LightGreen)
            .AddOrUpdateTag("Shared-Farm-3", "Shared server farm 3", TagResource.StandardColor.LightGreen)
            .AddOrUpdateTag("Shared-Farm-4", "Shared server farm 4", TagResource.StandardColor.LightGreen)
            .AddOrUpdateTag("Dedicated", "This customer will have their own dedicated hardware", TagResource.StandardColor.DarkRed)
            .Save();

            var allTags = new TagResource[0]
                          .Concat(tagSetTenantType.Instance.Tags)
                          .Concat(tagSetHosting.Instance.Tags)
                          .Concat(tagSetImportance.Instance.Tags)
                          .Concat(tagSetRing.Instance.Tags)
                          .Concat(tagSetEarlyAccessProgram.Instance.Tags)
                          .ToArray();

            var getTag = new Func <string, TagResource>(name => allTags.FirstOrDefault(t => t.Name == name));

            Log.Information("Setting up the untenanted host for the development...");
            var untenantedHosts = Enumerable.Range(0, 1).Select(i => Repository.Machines.CreateOrModify(
                                                                    $"Untenanted Node {i}",
                                                                    new CloudRegionEndpointResource(),
                                                                    allEnvironments.Where(e => e.Name == "MT Dev").ToArray(),
                                                                    new[] { "web-server" }))
                                  .ToArray();

            Log.Information("Setting up the shared hosts for the test environment...");
            foreach (var internalSharedHostTag in tagSetHosting.Instance.Tags.Where(t => t.Name.StartsWith("Internal-Shared")))
            {
                var sharedHosts = Enumerable.Range(0, 4).Select(i => Repository.Machines.CreateOrModify(
                                                                    $"{internalSharedHostTag.Name} Node {i}",
                                                                    new CloudRegionEndpointResource(),
                                                                    allEnvironments.Where(e => e.Name == "MT Test").ToArray(),
                                                                    new[] { "web-server" },
                                                                    new TenantResource[0],
                                                                    new[] { internalSharedHostTag }))
                                  .ToArray();
            }

            Log.Information("Setting up the shared hosts for the production environment...");
            foreach (var sharedHostTag in tagSetHosting.Instance.Tags.Where(t => t.Name.StartsWith("Shared-Farm")))
            {
                await Task.WhenAll(
                    Enumerable.Range(0, 4)
                    .Select(i => Repository.Machines.CreateOrModify(
                                $"{sharedHostTag.Name} Node {i}",
                                new CloudRegionEndpointResource(),
                                allEnvironments.Where(e => e.Name == "MT Production").ToArray(),
                                new[] { "web-server" },
                                new TenantResource[0],
                                new[] { sharedHostTag }))
                    .ToArray()
                    );
            }

            Log.Information("Setting up variables...");
            var envVarEditor = await Repository.LibraryVariableSets.CreateOrModify("Environment variables", "The environment details we require for all projects");

            foreach (var e in allEnvironments)
            {
                (await envVarEditor.Variables).AddOrUpdateVariableValue("Environment.Alias", e.Name.Replace("MT ", "").ToLowerInvariant(), new ScopeSpecification {
                    { ScopeField.Environment, e.Id }
                });
            }
            await envVarEditor.Save();

            var stdTenantVarEditor = await Repository.LibraryVariableSets.CreateOrModify("Standard tenant details", "The standard details we require for all tenants");

            stdTenantVarEditor.VariableTemplates
            .AddOrUpdateSingleLineTextTemplate(VariableKeys.StandardTenantDetails.TenantAlias, "Alias", defaultValue: null, helpText: "This alias will be used to build convention-based settings for the tenant")
            .AddOrUpdateSelectTemplate(VariableKeys.StandardTenantDetails.TenantRegion, "Region", Region.All.ToDictionary(x => x.Alias, x => x.DisplayName), defaultValue: null, helpText: "The geographic region where this tenant will be hosted")
            .AddOrUpdateSingleLineTextTemplate(VariableKeys.StandardTenantDetails.TenantContactEmail, "Contact email", defaultValue: null, helpText: "A comma-separated list of email addresses to send deployment notifications");
            await stdTenantVarEditor.Save();

            var libraryVariableSets = new[] { envVarEditor.Instance, stdTenantVarEditor.Instance };

            Log.Information("Building {Count} sample projects...", NumberOfProjects);
            var projects = await Task.WhenAll(
                Enumerable.Range(0, NumberOfProjects)
                .Select(i => new { Name = ProjectNames[i], Alias = ProjectNames[i].ToLowerInvariant() })
                .Select(async(x, i) =>
            {
                Log.Information("Setting up project {ProjectName}...", x.Name);
                var projectEditor = await Repository.Projects.CreateOrModify(x.Name, projectGroup.Instance, normalLifecycle.Instance, LipsumTheRaven.GenerateLipsum(2));
                projectEditor.IncludingLibraryVariableSets(libraryVariableSets);

                var image = SampleImageCache.GetRobotImage(x.Name);
                if (!string.IsNullOrWhiteSpace(image))
                {
                    projectEditor.SetLogo(image);
                }

                projectEditor.VariableTemplates
                .Clear()
                .AddOrUpdateSingleLineTextTemplate("Tenant.Database.Name", "Database name", $"{x.Alias}-#{{Environment.Alias}}-#{{Tenant.Alias}}", $"The environment-specific name of the {x.Name} database for this tenant.")
                .AddOrUpdateSingleLineTextTemplate("Tenant.Database.UserID", "Database username", $"{x.Alias}-#{{Environment.Alias}}-#{{Tenant.Alias}}", "The User ID used to connect to the tenant database.")
                .AddOrUpdateSensitiveTemplate(VariableKeys.ProjectTenantVariables.TenantDatabasePassword, "Database password", defaultValue: null, helpText: "The password used to connect to the tenant database.")
                .AddOrUpdateSingleLineTextTemplate("Tenant.Domain.Name", "Domain name", $"#{{Tenant.Alias}}.{x.Alias}.com", $"The environment-specific domain name for the {x.Name} web application for this tenant.");

                (await projectEditor.Variables)
                .AddOrUpdateVariableValue("DatabaseConnectionString", $"Server=db.{x.Alias}.com;Database=#{{Tenant.Database.Name}};User ID=#{{Tenant.Database.UserID}};Password=#{{Tenant.Database.Password}};")
                .AddOrUpdateVariableValue("HostURL", "https://#{Tenant.Domain.Name}");

                // Create the channels for the sample project
                var channel = await projectEditor.Channels.CreateOrModify("1.x Normal", "The channel for stable releases that will be deployed to our production customers.");
                await channel.SetAsDefaultChannel()
                .AddOrUpdateTenantTags(getTag("Tester"), getTag("Early adopter"), getTag("Stable"))
                .Save();
                var betaChannelEditor = await projectEditor.Channels.CreateOrModify("2.x Beta", "The channel for beta releases that will be deployed to our beta customers.");
                betaChannelEditor.UsingLifecycle(betaLifecycle.Instance)
                .AddOrUpdateTenantTags(getTag("2.x Beta"));

                // Delete the "default channel" if it exists
                await projectEditor.Channels.Delete("Default");

                // Rebuild the process from scratch
                var deploymentProcess = await projectEditor.DeploymentProcess;
                deploymentProcess.ClearSteps();

                deploymentProcess.AddOrUpdateStep("Deploy Application")
                .TargetingRoles("web-server")
                .AddOrUpdateScriptAction("Deploy Application", new InlineScriptActionFromFileInAssembly("MultiTenantSample.Deploy.ps1"), ScriptTarget.Target);

                deploymentProcess.AddOrUpdateStep("Deploy 2.x Beta Component")
                .TargetingRoles("web-server")
                .AddOrUpdateScriptAction("Deploy 2.x Beta Component", new InlineScriptActionFromFileInAssembly("MultiTenantSample.DeployBetaComponent.ps1"), ScriptTarget.Target)
                .ForChannels(betaChannelEditor.Instance);

                deploymentProcess.AddOrUpdateStep("Notify VIP Contact")
                .AddOrUpdateScriptAction("Notify VIP Contact", new InlineScriptActionFromFileInAssembly("MultiTenantSample.NotifyContact.ps1"), ScriptTarget.Server)
                .ForTenantTags(getTag("VIP"));

                projectEditor.Instance.TenantedDeploymentMode = ProjectTenantedDeploymentMode.TenantedOrUntenanted;

                await projectEditor.Save();

                return(projectEditor.Instance);
            })
                .ToArray()
                );

            Log.Information("Building {Count} sample Testers...", NumberOfTesters);
            var testers = await Task.WhenAll(
                GetSampleTenantData("Tester", NumberOfTesters)
                .Select(async x =>
            {
                Log.Information("Setting up tester {TenantName}...", x.Name);
                var tenantEditor = await Repository.Tenants.CreateOrModify(x.Name);
                tenantEditor.SetLogo(SampleImageCache.DownloadImage(x.LogoUrl))
                .WithTag(getTag("Internal"))
                .WithTag(getTag("Tester"))
                .WithTag(getTag("Internal-Shared-Farm"))
                .WithTag(getTag("2.x Beta"));

                // Connect to projects/environments
                tenantEditor.ClearProjects();
                var testEnvironments = allEnvironments.Where(e => e.Name == "MT Test").ToArray();
                foreach (var project in projects)
                {
                    tenantEditor.ConnectToProjectAndEnvironments(project, testEnvironments);
                }
                await tenantEditor.Save();

                // Ensure project mapping is saved before we attempt to fill out variables - otherwise they won't exist
                await FillOutTenantVariablesByConvention(tenantEditor, projects, allEnvironments, libraryVariableSets);

                await tenantEditor.Save();
                return(tenantEditor.Instance);
            })
                .ToArray()
                );

            Log.Information("Building {Count} sample Customers...", NumberOfCustomers);
            var customers = await Task.WhenAll(
                GetSampleTenantData("Customer", NumberOfCustomers)
                .Select(async x =>
            {
                Log.Information("Setting up customer {TenantName}...", x.Name);
                var tenantEditor = await Repository.Tenants.CreateOrModify(x.Name);
                tenantEditor.SetLogo(SampleImageCache.DownloadImage(x.LogoUrl));
                TagCustomerByConvention(tenantEditor.Instance, allTags);

                // Connect to projects/environments
                var customerEnvironments = GetEnvironmentsForCustomer(allEnvironments, tenantEditor.Instance);
                foreach (var project in projects)
                {
                    tenantEditor.ConnectToProjectAndEnvironments(project, customerEnvironments);
                }
                await tenantEditor.Save();

                // Ensure project mapping is saved before we attempt to fill out variables - otherwise they won't exist
                await FillOutTenantVariablesByConvention(tenantEditor, projects, allEnvironments, libraryVariableSets);
                await tenantEditor.Save();
                return(tenantEditor.Instance);
            })
                .ToArray()
                );

            foreach (var customer in customers.Where(c => c.IsVIP()))
            {
                Log.Information("Setting up dedicated hosting for {VIP}...", customer.Name);
                await Task.WhenAll(
                    Enumerable.Range(0, 2)
                    .Select(i => Repository.Machines.CreateOrModify(
                                $"{customer.Name} Host {i}",
                                new CloudRegionEndpointResource(),
                                GetEnvironmentsForCustomer(allEnvironments, customer),
                                new[] { "web-server" },
                                new[] { customer },
                                new TagResource[0]))
                    .ToArray()
                    );
            }

            Log.Information("Created {CustomerCount} customers and {TesterCount} testers and {ProjectCount} projects.",
                            customers.Length, testers.Length, projects.Length);
            Log.Information("Customer tagging conventions: Names with 'v' will become 'VIP' (with dedicated hosting), names with 'u' will become 'Trial', names with 'e' will become 'Early adopter', everyone else will be 'Standard' and assigned to a random shared server pool.");
        }