private static AppointmentApiAzureResourceBag CreateAppointmentApiAzureResources(AzureResourceBag azureResources, Config config, Input <string> registryImageName)
        {
            var tenantId = config.Require("azure-tenantid");

            var appointmentApiDb = new Database("appointments-api-db", new DatabaseArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                Name       = "appointment-api",
                ServerName = azureResources.SqlServer.Name,
                RequestedServiceObjectiveName = "S0",
                Tags = azureResources.Tags
            });

            var image = new Image("appointments-api-docker-image", new ImageArgs
            {
                Build    = $".{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}",
                Registry = new ImageRegistry
                {
                    Server   = azureResources.Registry.LoginServer,
                    Username = azureResources.Registry.AdminUsername,
                    Password = azureResources.Registry.AdminPassword
                },
                ImageName = registryImageName
            }, new ComponentResourceOptions
            {
                DependsOn = new InputList <Resource> {
                    azureResources.Registry
                }
            });

            var appointmentApiIdentity = new UserAssignedIdentity("appointments-api", new UserAssignedIdentityArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                Name = "appointments-api",
                Tags = azureResources.Tags
            });

            // AKS service principal needs to have Managed Identity Operator rights over the user assigned identity else AAD pod identity won't work
            var aksSpAppointmentApiAccessPolicy = new Assignment("aks-sp-appontment-api-access", new AssignmentArgs
            {
                PrincipalId        = azureResources.AksServicePrincipal.ObjectId,
                RoleDefinitionName = "Managed Identity Operator",
                Scope = appointmentApiIdentity.Id
            });

            var sqlAdmin = new ActiveDirectoryAdministrator("appointments-api-sql-access", new ActiveDirectoryAdministratorArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                TenantId          = tenantId,
                ObjectId          = appointmentApiIdentity.PrincipalId,
                Login             = "******",
                ServerName        = azureResources.SqlServer.Name
            });

            var clientConfig             = Output.Create(GetClientConfig.InvokeAsync());
            var currentPrincipalTenantId = clientConfig.Apply(c => c.TenantId);
            var currentPrincipal         = clientConfig.Apply(c => c.ObjectId);

            var appointmentApiKeyVault = new KeyVault("appointment-api-keyvault", new KeyVaultArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                Name = "appointments-api",
                EnabledForDiskEncryption = true,
                TenantId       = tenantId,
                SkuName        = "standard",
                AccessPolicies = new InputList <KeyVaultAccessPolicyArgs>
                {
                    new KeyVaultAccessPolicyArgs
                    {
                        TenantId          = tenantId,
                        ObjectId          = azureResources.AksServicePrincipal.ObjectId,
                        SecretPermissions = new[] { "get", "list" }
                    },
                    new KeyVaultAccessPolicyArgs
                    {
                        TenantId          = tenantId,
                        ObjectId          = appointmentApiIdentity.PrincipalId,
                        SecretPermissions = new[] { "get", "list" }
                    },
                    new KeyVaultAccessPolicyArgs
                    {
                        TenantId          = currentPrincipalTenantId,
                        ObjectId          = currentPrincipal,
                        SecretPermissions = { "delete", "get", "list", "set" },
                    }
                },
                NetworkAcls = new KeyVaultNetworkAclsArgs
                {
                    DefaultAction           = "Deny",
                    Bypass                  = "******",
                    VirtualNetworkSubnetIds = new InputList <string>
                    {
                        azureResources.Subnet.Id
                    },
                    // Need to whitelist the local public IP address otherwise setting secrets will fail
                    IpRules = new InputList <string>
                    {
                        GetMyPublicIpAddress()
                    }
                },
                Tags = azureResources.Tags
            });

            var secret = new Pulumi.Azure.KeyVault.Secret("appointments-api-db-connection-string",
                                                          new Pulumi.Azure.KeyVault.SecretArgs
            {
                KeyVaultId = appointmentApiKeyVault.Id,
                Name       = "ConnectionStrings--PetDoctorContext",
                Value      = Output.Tuple(azureResources.SqlServer.Name, azureResources.SqlServer.Name,
                                          azureResources.SqlServer.AdministratorLogin, azureResources.SqlServer.AdministratorLoginPassword).Apply(
                    t =>
                {
                    var(server, database, administratorLogin, administratorLoginPassword) = t;
                    return
                    ($"Server=tcp:{server}.database.windows.net;Database={database};User ID={administratorLogin};Password={administratorLoginPassword}");
                })
            }, new CustomResourceOptions
            {
                DependsOn = new InputList <Resource>
                {
                    azureResources.SqlServer,
                    appointmentApiKeyVault
                }
            });

            return(new AppointmentApiAzureResourceBag
            {
                Identity = appointmentApiIdentity,
                KeyVault = appointmentApiKeyVault
            });
        }
        private static void SetupAppointmentApiInKubernetes(AzureResourceBag azureResources, AppointmentApiAzureResourceBag appointmentApiAzureResources, PetDoctorClusterOptions clusterOptions)
        {
            var customOpts = new CustomResourceOptions
            {
                DependsOn = azureResources.Cluster,
                Provider  = azureResources.ClusterProvider
            };

            var secret = new Secret(clusterOptions.AppointmentApi.SecretName, new SecretArgs
            {
                Metadata = new ObjectMetaArgs
                {
                    Namespace = clusterOptions.Namespace,
                    Name      = clusterOptions.AppointmentApi.SecretName
                },
                Kind       = "Secret",
                ApiVersion = "v1",
                Type       = "Opaque",
                Data       = new InputMap <string>
                {
                    { "keyvault-url", appointmentApiAzureResources.KeyVault.VaultUri.Apply(kvUrl => Convert.ToBase64String(Encoding.UTF8.GetBytes(kvUrl))) },
                    { "appinsights-instrumentationkey", azureResources.AppInsights.InstrumentationKey.Apply(key => Convert.ToBase64String(Encoding.UTF8.GetBytes(key))) }
                }
            }, customOpts);

            var appointmentApiPodIdentity = new CustomResource(clusterOptions.AppointmentApi.AadPodIdentityName,
                                                               new AzureIdentityResourceArgs
            {
                Metadata = new ObjectMetaArgs
                {
                    Name = clusterOptions.AppointmentApi.AadPodIdentityName
                },
                Spec = new AzureIdentitySpecArgs
                {
                    Type       = 0,
                    ResourceId = appointmentApiAzureResources.Identity.Id,
                    ClientId   = appointmentApiAzureResources.Identity.ClientId
                }
            }, customOpts);

            var appointmentApiPodIdentityBinding = new CustomResource(clusterOptions.AppointmentApi.AadPodIdentityBindingName,
                                                                      new AzureIdentityBindingResourceArgs
            {
                Metadata = new ObjectMetaArgs
                {
                    Name = clusterOptions.AppointmentApi.AadPodIdentityBindingName
                },
                Spec = new AzureIdentityBindingSpecArgs
                {
                    AzureIdentity = clusterOptions.AppointmentApi.AadPodIdentityName,
                    Selector      = clusterOptions.AppointmentApi.AadPodIdentitySelector
                }
            }, new CustomResourceOptions
            {
                DependsOn = new InputList <Resource> {
                    azureResources.Cluster, appointmentApiAzureResources.Identity
                },
                Provider = azureResources.ClusterProvider
            });

            var appointmentApiDeployment = new Deployment("appointment-api-deployment", new DeploymentArgs
            {
                ApiVersion = "apps/v1beta1",
                Kind       = "Deployment",
                Metadata   = new ObjectMetaArgs
                {
                    Name      = clusterOptions.AppointmentApi.DeploymentName,
                    Namespace = clusterOptions.Namespace,
                    Labels    = new InputMap <string>
                    {
                        { "app", clusterOptions.AppointmentApi.DeploymentName },
                        { "aadpodidbinding", clusterOptions.AppointmentApi.AadPodIdentitySelector }
                    }
                },
                Spec = new DeploymentSpecArgs
                {
                    Replicas = clusterOptions.AppointmentApi.ReplicaCount,
                    Selector = new LabelSelectorArgs
                    {
                        MatchLabels = new InputMap <string>
                        {
                            { "app", clusterOptions.AppointmentApi.DeploymentName }
                        }
                    },
                    Template = new PodTemplateSpecArgs
                    {
                        Metadata = new ObjectMetaArgs
                        {
                            Labels = new InputMap <string>
                            {
                                { "app", clusterOptions.AppointmentApi.DeploymentName },
                                { "aadpodidbinding", clusterOptions.AppointmentApi.AadPodIdentitySelector }
                            }
                        },
                        Spec = new PodSpecArgs
                        {
                            Containers = new InputList <ContainerArgs>
                            {
                                new ContainerArgs
                                {
                                    Name  = clusterOptions.AppointmentApi.DeploymentName,
                                    Image = clusterOptions.AppointmentApi.Image,
                                    Ports = new InputList <ContainerPortArgs>
                                    {
                                        new ContainerPortArgs
                                        {
                                            ContainerPortValue = clusterOptions.AppointmentApi.Port,
                                            Protocol           = "TCP"
                                        }
                                    },
                                    ReadinessProbe = new ProbeArgs
                                    {
                                        HttpGet = new HTTPGetActionArgs
                                        {
                                            Port   = clusterOptions.AppointmentApi.Port,
                                            Path   = "/ready",
                                            Scheme = "HTTP"
                                        },
                                        InitialDelaySeconds = 15,
                                        TimeoutSeconds      = 2,
                                        PeriodSeconds       = 10,
                                        FailureThreshold    = 3
                                    },
                                    LivenessProbe = new ProbeArgs
                                    {
                                        HttpGet = new HTTPGetActionArgs
                                        {
                                            Port   = clusterOptions.AppointmentApi.Port,
                                            Path   = "/live",
                                            Scheme = "HTTP"
                                        },
                                        InitialDelaySeconds = 30,
                                        TimeoutSeconds      = 2,
                                        PeriodSeconds       = 5,
                                        FailureThreshold    = 3
                                    },
                                    Env = new InputList <EnvVarArgs>
                                    {
                                        new EnvVarArgs
                                        {
                                            Name  = "ASPNETCORE_ENVIRONMENT",
                                            Value = "Production"
                                        },
                                        new EnvVarArgs
                                        {
                                            Name      = "KEYVAULT__URL",
                                            ValueFrom = new EnvVarSourceArgs
                                            {
                                                SecretKeyRef = new SecretKeySelectorArgs
                                                {
                                                    Name = clusterOptions.AppointmentApi.SecretName,
                                                    Key  = "keyvault-url"
                                                }
                                            }
                                        },
                                        new EnvVarArgs
                                        {
                                            Name      = "APPLICATIONINSIGHTS__INSTRUMENTATIONKEY",
                                            ValueFrom = new EnvVarSourceArgs
                                            {
                                                SecretKeyRef = new SecretKeySelectorArgs
                                                {
                                                    Name = clusterOptions.AppointmentApi.SecretName,
                                                    Key  = "appinsights-instrumentationkey"
                                                }
                                            }
                                        },
                                        new EnvVarArgs
                                        {
                                            Name  = "HOSTENV",
                                            Value = "K8S"
                                        },
                                        new EnvVarArgs
                                        {
                                            Name  = "PATH_BASE",
                                            Value = "api"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }, new CustomResourceOptions
            {
                DependsOn = new InputList <Resource> {
                    azureResources.Cluster, appointmentApiAzureResources.Identity, appointmentApiPodIdentityBinding
                },
                Provider = azureResources.ClusterProvider
            });

            var appointmentApiService = new Service(clusterOptions.AppointmentApi.ServiceName, new ServiceArgs
            {
                ApiVersion = "v1",
                Kind       = "Service",
                Metadata   = new ObjectMetaArgs
                {
                    Name      = clusterOptions.AppointmentApi.ServiceName,
                    Namespace = clusterOptions.Namespace
                },
                Spec = new ServiceSpecArgs
                {
                    Selector = new InputMap <string>
                    {
                        { "app", clusterOptions.AppointmentApi.DeploymentName }
                    },
                    Type      = "ClusterIP",
                    ClusterIP = "None",
                    Ports     = new InputList <ServicePortArgs>
                    {
                        new ServicePortArgs
                        {
                            Name       = "http",
                            Protocol   = "TCP",
                            Port       = 80,
                            TargetPort = clusterOptions.AppointmentApi.Port
                        }
                    }
                }
            }, customOpts);

            var appointmentApiIngress = new Ingress(clusterOptions.AppointmentApi.IngressName, new IngressArgs
            {
                ApiVersion = "extensions/v1beta1",
                Kind       = "Ingress",
                Metadata   = new ObjectMetaArgs
                {
                    Name        = clusterOptions.AppointmentApi.IngressName,
                    Namespace   = clusterOptions.Namespace,
                    Annotations = new InputMap <string>
                    {
                        { "kubernetes.io/ingress.class", "nginx" },
                        { "certmanager.k8s.io/cluster-issuer", "letsencrypt-prod" }
                    }
                },
                Spec = new IngressSpecArgs
                {
                    Tls = new InputList <IngressTLSArgs>
                    {
                        new IngressTLSArgs
                        {
                            Hosts = new InputList <string>
                            {
                                clusterOptions.Domain
                            },
                            SecretName = "tls-secret"
                        }
                    },
                    Rules = new InputList <IngressRuleArgs>
                    {
                        new IngressRuleArgs
                        {
                            Host = clusterOptions.Domain,
                            Http = new HTTPIngressRuleValueArgs
                            {
                                Paths = new InputList <HTTPIngressPathArgs>
                                {
                                    new HTTPIngressPathArgs
                                    {
                                        Path    = "/api",
                                        Backend = new IngressBackendArgs
                                        {
                                            ServiceName = clusterOptions.AppointmentApi.ServiceName,
                                            ServicePort = clusterOptions.AppointmentApi.Port
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }, customOpts);
        }
        private static void ConfigureKubernetesCluster(AzureResourceBag azureResources, PetDoctorClusterOptions clusterOptions)
        {
            var componentOpts = new ComponentResourceOptions
            {
                DependsOn = azureResources.Cluster,
                Provider  = azureResources.ClusterProvider
            };

            var customOpts = new CustomResourceOptions
            {
                DependsOn = azureResources.Cluster,
                Provider  = azureResources.ClusterProvider
            };

            var aadPodIdentityDeployment = new ConfigFile("k8s-aad-pod-identity", new ConfigFileArgs
            {
                File = "https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml"
            }, componentOpts);

            var certManagerDeployment = new ConfigFile("k8s-cert-manager", new ConfigFileArgs
            {
                File = "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.8/deploy/manifests/00-crds.yaml"
            }, componentOpts);

            var nginxDeployment = new ConfigFile("k8s-nginx-ingress", new ConfigFileArgs
            {
                File =
                    "https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/cloud/deploy.yaml"
            }, componentOpts);

            var clusterNamespace = new Namespace("k8s-namespace", new NamespaceArgs
            {
                Metadata = new ObjectMetaArgs
                {
                    Name = clusterOptions.Namespace
                }
            }, customOpts);

            var clusterIssuer = new CustomResource("k8s-cert-manager-cluster-issuer", new CertManagerClusterIssuerResourceArgs
            {
                Metadata = new ObjectMetaArgs
                {
                    Name = "letsencrypt-prod"
                },
                Spec = new CertManagerClusterIssuerSpecArgs
                {
                    Acme = new CertManagerClusterIssuerAcmeArgs
                    {
                        Email  = clusterOptions.CertificateIssuerAcmeEmail,
                        Server = "https://acme-v02.api.letsencrypt.org/directory",
                        PrivateKeySecretRef = new CertManagerClusterIssuerAcmeSecretArgs
                        {
                            Name = "letsencrypt-prod"
                        }
                    }
                }
            }, customOpts);

            var certs = new CustomResource("k8s-cert-manager-domain-cert", new CertManagerCertificateResourceArgs
            {
                Metadata = new ObjectMetaArgs
                {
                    Name = "tls-secret"
                },
                Spec = new CertManagerCertificateSpecArgs
                {
                    SecretName = "tls-secret",
                    DnsNames   = clusterOptions.Domain,
                    Acme       = new CertManagerCertificateAcmeArgs
                    {
                        Config = new CertManagerCertificateAcmeConfigArgs
                        {
                            Http = new CertManagerCertificateAcmeConfigHttpArgs
                            {
                                IngressClass = "nginx"
                            },
                            Domains = clusterOptions.Domain
                        }
                    },
                    IssuerRef = new CertManagerCertificateIssuerRefArgs
                    {
                        Name = "letsencrypt-prod",
                        Kind = "ClusterIssuer"
                    }
                }
            }, customOpts);
        }