public override async Task RunAsync() { (var resolvedImageName, var shouldBuild) = ResolveImageName(); TriggersPayload triggers = null; if (DryRun) { if (shouldBuild) { // don't build on a --dry-run. // read files from the local dir triggers = await GetTriggersLocalFiles(); } else { triggers = await DockerHelpers.GetTriggersFromDockerImage(resolvedImageName); } } else { if (shouldBuild) { await DockerHelpers.DockerBuild(resolvedImageName, Environment.CurrentDirectory); } triggers = await DockerHelpers.GetTriggersFromDockerImage(resolvedImageName); } (var resources, var funcKeys) = await KubernetesHelper.GetFunctionsDeploymentResources( Name, resolvedImageName, Namespace, triggers, _secretsManager.GetSecrets(), PullSecret, SecretsCollectionName, ConfigMapName, UseConfigMap, PollingInterval, CooldownPeriod, ServiceType, MinReplicaCount, MaxReplicaCount, KeysSecretCollectionName, MountFuncKeysAsContainerVolume); if (DryRun) { ColoredConsole.WriteLine(KubernetesHelper.SerializeResources(resources, OutputSerializationOptions.Yaml)); } else { if (!await KubernetesHelper.NamespaceExists(Namespace)) { await KubernetesHelper.CreateNamespace(Namespace); } if (shouldBuild) { await DockerHelpers.DockerPush(resolvedImageName); } foreach (var resource in resources) { await KubectlHelper.KubectlApply(resource, showOutput : true, ignoreError : IgnoreErrors, @namespace : Namespace); } //Print the function keys message to the console await KubernetesHelper.PrintFunctionsInfo($"{Name}-http", Namespace, funcKeys, triggers); } }
private static ScaledObjectV1Alpha1 GetScaledObject(string name, string @namespace, TriggersPayload triggers, DeploymentV1Apps deployment, int?pollingInterval, int?cooldownPeriod, int?minReplicas, int?maxReplicas) { return(new ScaledObjectV1Alpha1 { ApiVersion = "keda.k8s.io/v1alpha1", Kind = "ScaledObject", Metadata = new ObjectMetadataV1 { Name = name, Namespace = @namespace, Labels = new Dictionary <string, string> { { "deploymentName", deployment.Metadata.Name } } }, Spec = new ScaledObjectSpecV1Alpha1 { ScaleTargetRef = new ScaledObjectScaleTargetRefV1Alpha1 { DeploymentName = deployment.Metadata.Name }, PollingInterval = pollingInterval, CooldownPeriod = cooldownPeriod, MinReplicaCount = minReplicas, MaxReplicaCount = maxReplicas, Triggers = triggers .FunctionsJson .Select(kv => kv.Value) .Where(v => v["bindings"] != null) .Select(b => b["bindings"]) .SelectMany(i => i) .Where(b => b?["type"] != null) .Where(b => b["type"].ToString().IndexOf("Trigger", StringComparison.OrdinalIgnoreCase) != -1) .Where(b => b["type"].ToString().IndexOf("httpTrigger", StringComparison.OrdinalIgnoreCase) == -1) .Select(t => new ScaledObjectTriggerV1Alpha1 { Type = GetKedaTrigger(t["type"]?.ToString()), Metadata = PopulateMetadataDictionary(t) }) } }); }
internal async static Task <(IEnumerable <IKubernetesResource>, IDictionary <string, string>)> GetFunctionsDeploymentResources( string name, string imageName, string @namespace, TriggersPayload triggers, IDictionary <string, string> secrets, string pullSecret = null, string secretsCollectionName = null, string configMapName = null, bool useConfigMap = false, int?pollingInterval = null, int?cooldownPeriod = null, string serviceType = "LoadBalancer", int?minReplicas = null, int?maxReplicas = null, string keysSecretCollectionName = null, bool mountKeysAsContainerVolume = false) { ScaledObjectV1Alpha1 scaledobject = null; var result = new List <IKubernetesResource>(); var deployments = new List <DeploymentV1Apps>(); var httpFunctions = triggers.FunctionsJson .Where(b => b.Value["bindings"]?.Any(e => e?["type"].ToString().IndexOf("httpTrigger", StringComparison.OrdinalIgnoreCase) != -1) == true); var nonHttpFunctions = triggers.FunctionsJson.Where(f => httpFunctions.All(h => h.Key != f.Key)); keysSecretCollectionName = string.IsNullOrEmpty(keysSecretCollectionName) ? $"func-keys-kube-secret-{name}" : keysSecretCollectionName; if (httpFunctions.Any()) { int position = 0; var enabledFunctions = httpFunctions.ToDictionary(k => $"AzureFunctionsJobHost__functions__{position++}", v => v.Key); //Environment variables for the func app keys kubernetes secret var kubernetesSecretEnvironmentVariable = FuncAppKeysHelper.FuncKeysKubernetesEnvironVariables(keysSecretCollectionName, mountKeysAsContainerVolume); var additionalEnvVars = enabledFunctions.Concat(kubernetesSecretEnvironmentVariable).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); var deployment = GetDeployment(name + "-http", @namespace, imageName, pullSecret, 1, additionalEnvVars, port: 80); deployments.Add(deployment); var service = GetService(name + "-http", @namespace, deployment, serviceType); result.Add(service); } if (nonHttpFunctions.Any()) { int position = 0; var enabledFunctions = nonHttpFunctions.ToDictionary(k => $"AzureFunctionsJobHost__functions__{position++}", v => v.Key); var deployment = GetDeployment(name, @namespace, imageName, pullSecret, minReplicas ?? 0, enabledFunctions); deployments.Add(deployment); scaledobject = GetScaledObject(name, @namespace, triggers, deployment, pollingInterval, cooldownPeriod, minReplicas, maxReplicas); } // Set worker runtime if needed. if (!secrets.ContainsKey(Constants.FunctionsWorkerRuntime)) { secrets[Constants.FunctionsWorkerRuntime] = GlobalCoreToolsSettings.CurrentWorkerRuntime.ToString(); } int resourceIndex = 0; if (useConfigMap) { var configMap = GetConfigMap(name, @namespace, secrets); result.Insert(resourceIndex, configMap); resourceIndex++; foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { ConfigMapRef = new NamedObjectV1 { Name = configMap.Metadata.Name } } }; } } else if (!string.IsNullOrEmpty(secretsCollectionName)) { foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { SecretRef = new NamedObjectV1 { Name = secretsCollectionName } } }; } } else if (!string.IsNullOrEmpty(configMapName)) { foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { ConfigMapRef = new NamedObjectV1 { Name = configMapName } } }; } } else { var secret = GetSecret(name, @namespace, secrets); result.Insert(resourceIndex, secret); resourceIndex++; foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { SecretRef = new NamedObjectV1 { Name = secret.Metadata.Name } } }; } } IDictionary <string, string> resultantFunctionKeys = new Dictionary <string, string>(); if (httpFunctions.Any()) { var currentImageFuncKeys = FuncAppKeysHelper.CreateKeys(httpFunctions.Select(f => f.Key)); resultantFunctionKeys = GetFunctionKeys(currentImageFuncKeys, await GetExistingFunctionKeys(keysSecretCollectionName, @namespace)); if (resultantFunctionKeys?.Any() == true) { result.Insert(resourceIndex, GetSecret(keysSecretCollectionName, @namespace, resultantFunctionKeys)); resourceIndex++; } //if function keys Secrets needs to be mounted as volume in the function runtime container if (mountKeysAsContainerVolume) { FuncAppKeysHelper.CreateFuncAppKeysVolumeMountDeploymentResource(deployments, keysSecretCollectionName); } //Create the Pod identity with the role to modify the function kubernetes secret else { var svcActName = $"{name}-function-keys-identity-svc-act"; var svcActDeploymentResource = GetServiceAccount(svcActName, @namespace); result.Insert(resourceIndex, svcActDeploymentResource); resourceIndex++; var funcKeysManagerRoleName = "functions-keys-manager-role"; var secretManagerRole = GetSecretManagerRole(funcKeysManagerRoleName, @namespace); result.Insert(resourceIndex, secretManagerRole); resourceIndex++; var roleBindingName = $"{svcActName}-functions-keys-manager-rolebinding"; var funcKeysRoleBindingDeploymentResource = GetRoleBinding(roleBindingName, @namespace, funcKeysManagerRoleName, svcActName); result.Insert(resourceIndex, funcKeysRoleBindingDeploymentResource); resourceIndex++; //add service account identity to the pod foreach (var deployment in deployments) { deployment.Spec.Template.Spec.ServiceAccountName = svcActName; } } } result = result.Concat(deployments).ToList(); return(scaledobject != null ? result.Append(scaledobject) : result, resultantFunctionKeys); }
internal async static Task PrintFunctionsInfo(string serviceName, string @namespace, IDictionary <string, string> funcKeys, TriggersPayload triggers) { if (string.IsNullOrWhiteSpace(serviceName) || string.IsNullOrWhiteSpace(@namespace) || funcKeys?.Any() == false || triggers == null) { return; } var httpFunctions = triggers.FunctionsJson .Where(b => b.Value["bindings"]?.Any(e => e?["type"].ToString().IndexOf("httpTrigger", StringComparison.OrdinalIgnoreCase) != -1) == true) .Select(item => item.Key); var loadBalancerIp = await GetLoadBalancerIp(serviceName, @namespace, 24); if (string.IsNullOrEmpty(loadBalancerIp)) { ColoredConsole.WriteLine(WarningColor($"The service: {serviceName} is not yet ready, please re-run the deployment to get the function keys.")); return; } var masterKey = funcKeys["host.master"]; if (httpFunctions?.Any() == true) { foreach (var functionName in httpFunctions) { var getFunctionAdminUri = $"http://{loadBalancerIp}/admin/functions/{functionName}?code={masterKey}"; var httpResponseMessage = await GetHttpResponse(new HttpRequestMessage(HttpMethod.Get, getFunctionAdminUri), 20); if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.NotFound) { ColoredConsole.WriteLine(WarningColor($"The service: {functionName} is not yet ready in the runtime yet, please re-run the deployment to get the function keys.")); return; } if (httpResponseMessage.IsSuccessStatusCode) { var responseContent = await httpResponseMessage.Content.ReadAsStringAsync(); var functionsInfo = JsonConvert.DeserializeObject <FunctionInfo>(responseContent); var trigger = functionsInfo .Config?["bindings"] ?.FirstOrDefault(o => o["type"]?.ToString().IndexOf("Trigger", StringComparison.OrdinalIgnoreCase) != -1) ?["type"]; trigger = trigger ?? "No Trigger Found"; var showFunctionKey = true; var authLevel = functionsInfo .Config?["bindings"] ?.FirstOrDefault(o => !string.IsNullOrEmpty(o["authLevel"]?.ToString())) ?["authLevel"]; if (authLevel != null && authLevel.ToString().Equals("anonymous", StringComparison.OrdinalIgnoreCase)) { showFunctionKey = false; } ColoredConsole.WriteLine($"\t{functionName} - [{VerboseColor(trigger.ToString())}]"); if (!string.IsNullOrEmpty(functionsInfo.InvokeUrlTemplate)) { if (showFunctionKey) { ColoredConsole.WriteLine($"\tInvoke url: {VerboseColor($"{functionsInfo.InvokeUrlTemplate}?code={funcKeys[$"functions.{functionName.ToLower()}.default"]}")}"); } else { ColoredConsole.WriteLine($"\tInvoke url: {VerboseColor(functionsInfo.InvokeUrlTemplate)}"); } } ColoredConsole.WriteLine(); } } } //Print the master key as well for the user ColoredConsole.WriteLine($"\tMaster key: {VerboseColor($"{funcKeys[$"host.master"]}")}"); }
internal static IEnumerable <IKubernetesResource> GetFunctionsDeploymentResources( string name, string imageName, string @namespace, TriggersPayload triggers, IDictionary <string, string> secrets, string pullSecret = null, string secretsCollectionName = null, string configMapName = null, bool useConfigMap = false, int?pollingInterval = null, int?cooldownPeriod = null, string serviceType = "LoadBalancer", int?minReplicas = null, int?maxReplicas = null) { ScaledObjectV1Alpha1 scaledobject = null; var result = new List <IKubernetesResource>(); var deployments = new List <DeploymentV1Apps>(); var httpFunctions = triggers.FunctionsJson .Where(b => b.Value["bindings"]?.Any(e => e?["type"].ToString().IndexOf("httpTrigger", StringComparison.OrdinalIgnoreCase) != -1) == true); var nonHttpFunctions = triggers.FunctionsJson.Where(f => httpFunctions.All(h => h.Key != f.Key)); if (httpFunctions.Any()) { int position = 0; var enabledFunctions = httpFunctions.ToDictionary(k => $"AzureFunctionsJobHost__functions__{position++}", v => v.Key); var deployment = GetDeployment(name + "-http", @namespace, imageName, pullSecret, 1, enabledFunctions, new Dictionary <string, string> { { "osiris.deislabs.io/enabled", "true" }, { "osiris.deislabs.io/minReplicas", "1" } }, port: 80); deployments.Add(deployment); var service = GetService(name + "-http", @namespace, deployment, serviceType, new Dictionary <string, string> { { "osiris.deislabs.io/enabled", "true" }, { "osiris.deislabs.io/deployment", deployment.Metadata.Name } }); result.Add(service); } if (nonHttpFunctions.Any()) { int position = 0; var enabledFunctions = nonHttpFunctions.ToDictionary(k => $"AzureFunctionsJobHost__functions__{position++}", v => v.Key); var deployment = GetDeployment(name, @namespace, imageName, pullSecret, minReplicas ?? 0, enabledFunctions); deployments.Add(deployment); scaledobject = GetScaledObject(name, @namespace, triggers, deployment, pollingInterval, cooldownPeriod, minReplicas, maxReplicas); } // Set worker runtime if needed. if (!secrets.ContainsKey(Constants.FunctionsWorkerRuntime)) { secrets[Constants.FunctionsWorkerRuntime] = GlobalCoreToolsSettings.CurrentWorkerRuntime.ToString(); } if (useConfigMap) { var configMap = GetConfigMap(name, @namespace, secrets); result.Insert(0, configMap); foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { ConfigMapRef = new NamedObjectV1 { Name = configMap.Metadata.Name } } }; } } else if (!string.IsNullOrEmpty(secretsCollectionName)) { foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { SecretRef = new NamedObjectV1 { Name = secretsCollectionName } } }; } } else if (!string.IsNullOrEmpty(configMapName)) { foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { ConfigMapRef = new NamedObjectV1 { Name = configMapName } } }; } } else { var secret = GetSecret(name, @namespace, secrets); result.Insert(0, secret); foreach (var deployment in deployments) { deployment.Spec.Template.Spec.Containers.First().EnvFrom = new ContainerEnvironmentFromV1[] { new ContainerEnvironmentFromV1 { SecretRef = new NamedObjectV1 { Name = secret.Metadata.Name } } }; } } result = result.Concat(deployments).ToList(); return(scaledobject != null ? result.Append(scaledobject) : result); }