public DevOpsInfra() { _config = new Config("ado"); #pragma warning disable Pulumi.InputMap <string> tags = JsonConvert.DeserializeObject <Dictionary <string, string> >(_config.RequireObject <JsonElement>("tags").ToString()); #region AzureNative.Resources.ResourceGroup (-rg) var resourceGroup = new Pulumi.AzureNative.Resources.ResourceGroup($"{_config.Require("resourceGroup")}-{_config.Require("env")}-rg-", new Pulumi.AzureNative.Resources.ResourceGroupArgs { Location = _config.Require("location"), Tags = tags, }); this.ResourceGroupName = resourceGroup.Name; #endregion #region NetworkSecurityGroup (-nsg) // NSGs can be connected to Subnets or Network cards (NICs). // When applied to the subnet, the NSG applies to all VMs on that subnet. If you apply only to the NIC card, just that one VM is affected. string myIp = new System.Net.WebClient().DownloadString("https://api.ipify.org"); var networkSecurityGroup = new Pulumi.AzureNative.Network.NetworkSecurityGroup($"{_config.Require("vnet.name")}-{_config.Require("env")}-nsg-", new Pulumi.AzureNative.Network.NetworkSecurityGroupArgs { ResourceGroupName = resourceGroup.Name, SecurityRules = { new Pulumi.AzureNative.Network.Inputs.SecurityRuleArgs { Name = "allow-rdp", Protocol = SecurityRuleProtocol.Tcp, SourcePortRange = "*", DestinationPortRange = "3389", SourceAddressPrefix = myIp, DestinationAddressPrefix = "*", Access = "Allow", Priority = 300, Direction = "Inbound", }, new Pulumi.AzureNative.Network.Inputs.SecurityRuleArgs { Name = "allow-http", // _config.Require("nsg.name"), Protocol = SecurityRuleProtocol.Tcp, SourcePortRange = "*", DestinationPortRange = "443", SourceAddressPrefix = "*", DestinationAddressPrefix = "*", Access = "Allow", Priority = 301, Direction = "Inbound", }, }, }); #endregion #region VirtualNetwork (-vnet) var network = new VirtualNetwork($"{_config.Require("vnet.name")}-{_config.Require("env")}-vnet-", new VirtualNetworkArgs { AddressSpace = new Pulumi.AzureNative.Network.Inputs.AddressSpaceArgs { AddressPrefixes = { _config.Require("vnet.cidr"), }, }, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, Subnets = { new Pulumi.AzureNative.Network.Inputs.SubnetArgs { AddressPrefix = _config.Require("bastion.cidr"), Name = "AzureBastionSubnet", }, new Pulumi.AzureNative.Network.Inputs.SubnetArgs { AddressPrefix = _config.Require("snet.cidr"), Name = _config.Require("snet.name"), NetworkSecurityGroup = new Pulumi.AzureNative.Network.Inputs.NetworkSecurityGroupArgs { Id = networkSecurityGroup.Id, }, }, }, VirtualNetworkName = "AzureDevOps", // VirtualNetworkPeerings EnableDdosProtection = false, } ); #endregion #region PublicIp (-ip) var publicIp = new Pulumi.AzureNative.Network.PublicIPAddress($"bastion-{_config.Require("env")}-ip-", new Pulumi.AzureNative.Network.PublicIPAddressArgs { PublicIPAddressVersion = IPVersion.IPv4, PublicIPAllocationMethod = IPAllocationMethod.Static, IdleTimeoutInMinutes = 4, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, Sku = new Pulumi.AzureNative.Network.Inputs.PublicIPAddressSkuArgs { Name = PublicIPAddressSkuName.Standard, Tier = PublicIPAddressSkuTier.Regional, }, }); var vmPublicIp = new Pulumi.AzureNative.Network.PublicIPAddress($"{_config.Require("agent.name")}-{_config.Require("env")}-ip-", new Pulumi.AzureNative.Network.PublicIPAddressArgs { PublicIPAddressVersion = IPVersion.IPv4, PublicIPAllocationMethod = IPAllocationMethod.Static, IdleTimeoutInMinutes = 4, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, Sku = new Pulumi.AzureNative.Network.Inputs.PublicIPAddressSkuArgs { Name = PublicIPAddressSkuName.Standard, Tier = PublicIPAddressSkuTier.Regional, }, }); #endregion #region NetworkInterface (-nic) var networkInterface = new Pulumi.AzureNative.Network.NetworkInterface($"{_config.Require("agent.name")}-{_config.Require("env")}-nic-", new Pulumi.AzureNative.Network.NetworkInterfaceArgs { // AdoAgent has size Standard_B2s, which is not compatible with accelerated networking on network interface. // https://docs.microsoft.com/en-us/azure/virtual-network/create-vm-accelerated-networking-powershell // EnableAcceleratedNetworking = true, IpConfigurations = { new Pulumi.AzureNative.Network.Inputs.NetworkInterfaceIPConfigurationArgs { Name = "ipconfig1", Primary = true, PrivateIPAllocationMethod = IPAllocationMethod.Dynamic, PrivateIPAddressVersion = "IPv4", PublicIPAddress = new Pulumi.AzureNative.Network.Inputs.PublicIPAddressArgs { Id = vmPublicIp.Id, }, Subnet = new Pulumi.AzureNative.Network.Inputs.SubnetArgs { #pragma warning disable Id = network.Subnets.Apply(s => s[1].Id), }, }, }, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, }); #endregion #region VirtualMachine (-vm) var vm = new Pulumi.AzureNative.Compute.VirtualMachine($"{_config.Require("agent.name")}-{_config.Require("env")}-vmi-", new Pulumi.AzureNative.Compute.VirtualMachineArgs { ResourceGroupName = resourceGroup.Name, HardwareProfile = new Pulumi.AzureNative.Compute.Inputs.HardwareProfileArgs { VmSize = _config.Require("vm.vmSize") }, VmName = "AdoAgent", DiagnosticsProfile = new Pulumi.AzureNative.Compute.Inputs.DiagnosticsProfileArgs { BootDiagnostics = new Pulumi.AzureNative.Compute.Inputs.BootDiagnosticsArgs { Enabled = true } }, NetworkProfile = new Pulumi.AzureNative.Compute.Inputs.NetworkProfileArgs { NetworkInterfaces = { new Pulumi.AzureNative.Compute.Inputs.NetworkInterfaceReferenceArgs { Id = networkInterface.Id, Primary = true, }, } }, StorageProfile = new Pulumi.AzureNative.Compute.Inputs.StorageProfileArgs { ImageReference = new Pulumi.AzureNative.Compute.Inputs.ImageReferenceArgs { Publisher = "MicrosoftWindowsServer", Offer = "WindowsServer", Sku = "2019-Datacenter-with-Containers-smalldisk", Version = "latest" }, OsDisk = new Pulumi.AzureNative.Compute.Inputs.OSDiskArgs { OsType = Pulumi.AzureNative.Compute.OperatingSystemTypes.Windows, Name = "system", CreateOption = Pulumi.AzureNative.Compute.DiskCreateOptionTypes.FromImage, Caching = Pulumi.AzureNative.Compute.CachingTypes.ReadWrite, ManagedDisk = new Pulumi.AzureNative.Compute.Inputs.ManagedDiskParametersArgs { StorageAccountType = Pulumi.AzureNative.Compute.StorageAccountTypes.Standard_LRS, }, DiskSizeGB = 100, }, DataDisks = { new Pulumi.AzureNative.Compute.Inputs.DataDiskArgs { Name = "Data", CreateOption = Pulumi.AzureNative.Compute.DiskCreateOptionTypes.Empty, DiskSizeGB = _config.RequireInt32("vm.diskSizeGB"), Lun = 0, Caching = Pulumi.AzureNative.Compute.CachingTypes.None, }, }, }, OsProfile = new Pulumi.AzureNative.Compute.Inputs.OSProfileArgs { ComputerName = _config.Require("agent.name"), AdminUsername = _config.Require("admin.user"), AdminPassword = _config.RequireSecret("admin.pw"), WindowsConfiguration = new Pulumi.AzureNative.Compute.Inputs.WindowsConfigurationArgs { ProvisionVMAgent = true, EnableAutomaticUpdates = true, PatchSettings = new Pulumi.AzureNative.Compute.Inputs.PatchSettingsArgs { PatchMode = Pulumi.AzureNative.Compute.WindowsVMGuestPatchMode.AutomaticByOS, EnableHotpatching = false, }, }, }, }); // new CustomResourceOptions { DeleteBeforeReplace = true }); #endregion #region BastionHost (-bas) // Azure Bastion is a fully managed PaaS service that provides secure and seamless RDP and SSH access to virtual machines through the Azure Portal. // A BastionHost is provisioned within a Virtual Network (VNet) to provid SSL access to all VMs in the VNet without exposure through public IP addresses. var bastionHost = new Pulumi.AzureNative.Network.BastionHost($"{_config.Require("vnet.name")}-{_config.Require("env")}-bas-", new Pulumi.AzureNative.Network.BastionHostArgs { Location = resourceGroup.Location, ResourceGroupName = resourceGroup.Name, IpConfigurations = { new Pulumi.AzureNative.Network.Inputs.BastionHostIPConfigurationArgs { Name = "bastionHostIpConfiguration", PublicIPAddress = new Pulumi.AzureNative.Network.Inputs.SubResourceArgs { Id = publicIp.Id, }, Subnet = new Pulumi.AzureNative.Network.Inputs.SubResourceArgs { Id = network.Subnets.Apply(s => s[0].Id), }, }, }, }); #endregion }
static Task <int> Main() { return(Deployment.RunAsync(async() => { // Get the configuration and required variables var config = new Pulumi.Config(); var location = config.Require("location"); var companyCode = config.Require("company_code"); var environment = config.Require("environment"); var scope = config.Require("default_scope"); var tenantId = Output.Create <string>(config.Require("tenant_id")); var rbacGroups = config.RequireObject <JsonElement>("rbac_groups"); List <string> rbacGroupsList = new List <string>(); foreach (JsonElement group in rbacGroups.EnumerateArray()) { rbacGroupsList.Add(group.ToString()); } // Create the factory ResourceFactory factory = new ResourceFactory(companyCode, location, environment, scope); // Create the tags to be applied to these resources: scope Dictionary <string, string> tags = new Dictionary <string, string>(); tags.Add("scope", scope); // Create the resource group, analytics workspace and automation account var resourceGroup = factory.GetResourceGroup(tags: tags); var workspace = factory.GetAnalyticsWorkspace(resourceGroupName: resourceGroup.Name, tags: tags); var automationAccount = factory.GetAutomationAccount(resourceGroupName: resourceGroup.Name, tags: tags); #region Runbooks // Runbook for UpdatePowershellModules DateTime now = DateTime.Now; int start = (int)now.DayOfWeek; int target = (int)DayOfWeek.Sunday; target = (target <= start ? target + 7 : target); DateTime nextSunday = now.AddDays(target - start); string publishContentLink = "https://raw.githubusercontent.com/Daniel-Jennings/PulumiTemplates/master/sub-tcms-pul-infra/Runbooks/UpdatePowershellModules.ps1"; string runbookDescription = "A runbook to update all of the Powershell modules used in the automation account. Should be run weekly to ensure latest module code is available"; string startTime = nextSunday.ToString("yyyy'-'MM'-'dd") + "T20:00:00-04:00"; string scheduleDescription = "Run a task weekly on Sunday at 8PM"; Dictionary <string, string> parameters = new Dictionary <string, string> { { "resourcegroupname", factory.ResourceNames["rg"][0] }, { "automationaccountname", factory.ResourceNames["aacc"][0] } }; var updatePowershellModulesRunbook = factory.GetAutomationRunbook(name: "UpdatePowershellModules", resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, publishContentLink: publishContentLink, description: runbookDescription, runbookType: "PowerShell", tags: tags); var updatePowershellModulesSchedule = factory.GetAutomationSchedule(name: "WeeklySunday8PM", resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, description: scheduleDescription, frequency: "Week", startTime: startTime, timezone: "America/Toronto", interval: 1, weekDays: new List <string> { "Sunday" }); var updatePowershellModulesJob = factory.GetAutomationJobSchedule(resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, runbookName: updatePowershellModulesRunbook.Name, scheduleName: updatePowershellModulesSchedule.Name, parameters: parameters); // Runbook for ShutdownSchedule DateTime tomorrow = now.AddDays(1); publishContentLink = "https://raw.githubusercontent.com/Daniel-Jennings/PulumiTemplates/master/sub-tcms-pul-infra/Runbooks/ShutdownSchedule.ps1"; runbookDescription = "A runbook to control shutdown / startup schedules for VMs and Scale Sets"; var shutdownScheduleRunbook = factory.GetAutomationRunbook(name: "ShutdownSchedule", resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, publishContentLink: publishContentLink, description: runbookDescription, runbookType: "PowerShellWorkflow", tags: tags); // Schedule for Startup startTime = tomorrow.ToString("yyyy'-'MM'-'dd") + "T07:00:00-04:00"; scheduleDescription = "Run a task daily at 7AM"; parameters = new Dictionary <string, string> { { "shutdown", "false" }, { "verboselogging", "false" } }; var shutdownScheduleScheduleStartup = factory.GetAutomationSchedule(name: "Daily7AM", resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, description: scheduleDescription, frequency: "Day", startTime: startTime, timezone: "America/Toronto", interval: 1); var shutdownScheduleJobStartup = factory.GetAutomationJobSchedule(resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, runbookName: shutdownScheduleRunbook.Name, scheduleName: shutdownScheduleScheduleStartup.Name, parameters: parameters); // Schedule for Shutdown startTime = tomorrow.ToString("yyyy'-'MM'-'dd") + "T19:00:00-04:00"; scheduleDescription = "Run a task daily at 7PM"; parameters = new Dictionary <string, string> { { "shutdown", "true" }, { "verboselogging", "false" } }; var shutdownScheduleScheduleShutdown = factory.GetAutomationSchedule(name: "Daily7PM", resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, description: scheduleDescription, frequency: "Day", startTime: startTime, timezone: "America/Toronto", interval: 1); var shutdownScheduleJobShutdown = factory.GetAutomationJobSchedule(resourceGroupName: resourceGroup.Name, automationAccountName: automationAccount.Name, runbookName: shutdownScheduleRunbook.Name, scheduleName: shutdownScheduleScheduleShutdown.Name, parameters: parameters); #endregion // Return any outputs that may be required for subsequent steps return new Dictionary <string, object?> { { "resourceGroupId", resourceGroup.Id }, { "resourceGroupName", resourceGroup.Name }, { "workspaceId", workspace.WorkspaceId }, }; })); }