diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesConfig.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesConfig.cs index b29218fa1..f3985bf6d 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesConfig.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesConfig.cs @@ -10,6 +10,8 @@ public static class KubernetesConfig const string ServerCommsAddressVariableName = "ServerCommsAddress"; const string EnvVarPrefix = "OCTOPUS__K8STENTACLE"; + public static string KubeContextVariableName = $"{EnvVarPrefix}__KUBECONTEXT"; + public static string NamespaceVariableName => $"{EnvVarPrefix}__NAMESPACE"; public static string Namespace => GetRequiredEnvVar(NamespaceVariableName, "Unable to determine Kubernetes namespace."); public static string PodServiceAccountName => GetRequiredEnvVar($"{EnvVarPrefix}__PODSERVICEACCOUNTNAME", "Unable to determine Kubernetes Pod service account name."); diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesConfigMapService.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesConfigMapService.cs index 9b8f4657c..b2bdbca66 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesConfigMapService.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesConfigMapService.cs @@ -14,6 +14,7 @@ public interface IKubernetesConfigMapService Task TryGet(string name, CancellationToken cancellationToken); Task Patch(string name, IDictionary data, CancellationToken cancellationToken); + Task Create(string name,IDictionary data, CancellationToken cancellationToken); } public class KubernetesConfigMapService : KubernetesService, IKubernetesConfigMapService @@ -51,5 +52,8 @@ public async Task Patch(string name, IDictionary da return await RetryPolicy.ExecuteAsync(async () => await Client.CoreV1.PatchNamespacedConfigMapAsync(new V1Patch(configMapJson, V1Patch.PatchType.MergePatch), name, KubernetesConfig.Namespace, cancellationToken: cancellationToken)); } + + public async Task Create(string name, IDictionary data, CancellationToken cancellationToken) => + await RetryPolicy.ExecuteAsync(async () => await Client.CoreV1.CreateNamespacedConfigMapAsync(new V1ConfigMap(metadata: new V1ObjectMeta(){ Name = name }, binaryData: data), KubernetesConfig.Namespace, cancellationToken: cancellationToken)); } } \ No newline at end of file diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesPhysicalFileSystem.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesPhysicalFileSystem.cs index 35105c7ee..6b0f904c9 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesPhysicalFileSystem.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesPhysicalFileSystem.cs @@ -45,6 +45,8 @@ public override void EnsureDiskHasEnoughFreeSpace(string directoryPath, long req public (ulong freeSpaceBytes, ulong totalSpaceBytes)? GetStorageInformation() { + if(Environment.GetEnvironmentVariable("OCTOPUS__SKIP_DISK_SPACE_CHECK") == "true") return null; + var bytesUsed = directoryInformationProvider.GetPathUsedBytes(HomeDir); var bytesTotal = directoryInformationProvider.GetPathTotalBytes(); if (bytesUsed.HasValue && bytesTotal.HasValue) diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs index 5d74e2bd9..916a592eb 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesRawScriptPodCreator.cs @@ -9,6 +9,7 @@ using Octopus.Tentacle.Core.Diagnostics; using Octopus.Tentacle.Core.Services.Scripts.Locking; using Octopus.Tentacle.Kubernetes.Crypto; +using Octopus.Tentacle.Scripts; namespace Octopus.Tentacle.Kubernetes { @@ -24,6 +25,7 @@ public KubernetesRawScriptPodCreator( IKubernetesPodService podService, IKubernetesPodMonitor podMonitor, IKubernetesSecretService secretService, + IKubernetesConfigMapService configMapService, IKubernetesPodTemplateService podTemplateService, IKubernetesPodContainerResolver containerResolver, IApplicationInstanceSelector appInstanceSelector, @@ -33,7 +35,7 @@ public KubernetesRawScriptPodCreator( KubernetesPhysicalFileSystem kubernetesPhysicalFileSystem, IScriptPodLogEncryptionKeyProvider scriptPodLogEncryptionKeyProvider, ScriptIsolationMutex scriptIsolationMutex) - : base(podService, podMonitor, secretService, podTemplateService, containerResolver, appInstanceSelector, log, scriptLogProvider, homeConfiguration, kubernetesPhysicalFileSystem, scriptPodLogEncryptionKeyProvider, scriptIsolationMutex) + : base(podService, podMonitor, secretService, configMapService, podTemplateService, containerResolver, appInstanceSelector, log, scriptLogProvider, homeConfiguration, kubernetesPhysicalFileSystem, scriptPodLogEncryptionKeyProvider, scriptIsolationMutex) { this.containerResolver = containerResolver; } @@ -50,16 +52,16 @@ protected override async Task> CreateInitContainers(StartKube container.Image = command.PodImageConfiguration?.Image ?? await containerResolver.GetContainerImageForCluster(); container.ImagePullPolicy = KubernetesConfig.ScriptPodPullPolicy; container.Command = new List { "sh", "-c", GetInitExecutionScript("/nfs-mount", homeDir, workspacePath) }; - container.VolumeMounts = Merge(container.VolumeMounts, new[] { new V1VolumeMount("/nfs-mount", "init-nfs-volume"), new V1VolumeMount(homeDir, "tentacle-home") }); + //container.VolumeMounts = Merge(container.VolumeMounts, new[] { new V1VolumeMount("/nfs-mount", "init-nfs-volume"), new V1VolumeMount(homeDir, "tentacle-home") }); return new List { container }; } - protected override async Task> CreateScriptContainers(StartKubernetesScriptCommandV1 command, string podName, string scriptName, string homeDir, string workspacePath, string[]? scriptArguments, InMemoryTentacleScriptLog tentacleScriptLog, ScriptPodTemplate? template) + protected override async Task> CreateScriptContainers(StartKubernetesScriptCommandV1 command, string podName, string scriptName, string homeDir, string workspacePath, string[]? scriptArguments, InMemoryTentacleScriptLog tentacleScriptLog, ScriptPodTemplate? template, IScriptWorkspace workspace) { return new List { - await CreateScriptContainer(command, podName, scriptName, homeDir, workspacePath, scriptArguments, tentacleScriptLog, template?.ScriptContainerSpec) + await CreateScriptContainer(command, podName, scriptName, homeDir, workspacePath, scriptArguments, tentacleScriptLog, template?.ScriptContainerSpec, workspace) }; } @@ -69,15 +71,21 @@ protected override IList CreateVolumes(StartKubernetesScriptCommandV1 { new() { - Name = "tentacle-home", + Name = "workspace", EmptyDir = new V1EmptyDirVolumeSource() }, - new() + new V1Volume { - Name = "init-nfs-volume", - PersistentVolumeClaim = new V1PersistentVolumeClaimVolumeSource - { - ClaimName = KubernetesConfig.PodVolumeClaimName + Name = "calamari", + EmptyDir = new V1EmptyDirVolumeSource() + }, + + new () { + Name = "bootstrap-runner", + Image = + new() { + Reference = "octopusdeploy/kubernetes-agent-tentacle:8.3.3359", + PullPolicy = "IfNotPresent" } }, CreateAgentUpgradeSecretVolume(), diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs index 9bada0fa1..27a15ca79 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesScriptPodCreator.cs @@ -5,11 +5,13 @@ using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using k8s; using k8s.Models; using Newtonsoft.Json; +using Octopus.Client.Extensions; using Octopus.Tentacle.Configuration; using Octopus.Tentacle.Configuration.Instances; using Octopus.Tentacle.Contracts; @@ -33,6 +35,7 @@ public class KubernetesScriptPodCreator : IKubernetesScriptPodCreator readonly IKubernetesPodService podService; readonly IKubernetesPodMonitor podMonitor; readonly IKubernetesSecretService secretService; + readonly IKubernetesConfigMapService configMapService; readonly IKubernetesPodTemplateService podTemplateService; readonly IKubernetesPodContainerResolver containerResolver; readonly IApplicationInstanceSelector appInstanceSelector; @@ -47,6 +50,7 @@ public KubernetesScriptPodCreator( IKubernetesPodService podService, IKubernetesPodMonitor podMonitor, IKubernetesSecretService secretService, + IKubernetesConfigMapService configMapService, IKubernetesPodTemplateService podTemplateService, IKubernetesPodContainerResolver containerResolver, IApplicationInstanceSelector appInstanceSelector, @@ -69,6 +73,7 @@ public KubernetesScriptPodCreator( this.kubernetesPhysicalFileSystem = kubernetesPhysicalFileSystem; this.scriptPodLogEncryptionKeyProvider = scriptPodLogEncryptionKeyProvider; this.scriptIsolationMutex = scriptIsolationMutex; + this.configMapService = configMapService; } public async Task CreatePod(StartKubernetesScriptCommandV1 command, IScriptWorkspace workspace, CancellationToken cancellationToken) @@ -180,11 +185,18 @@ async Task CreatePod(StartKubernetesScriptCommandV1 command, IScriptWorkspace wo LogVerboseToBothLogs($"Creating Kubernetes Pod '{podName}'.", tentacleScriptLog); - workspace.CopyFile(KubernetesConfig.BootstrapRunnerExecutablePath, "bootstrapRunner", true); - var scriptName = Path.GetFileName(workspace.BootstrapScriptFilePath); var workspacePath = Path.Combine("Work", workspace.ScriptTicket.TaskId); + var scriptFilesConfigMapName = $"octopus-podcm-{command.ScriptTicket.TaskId}".ToLowerInvariant(); // TODO: Double check if this causes issues for rerunning tasks. + var files = command.Files.ToDictionary(commandFile => commandFile.Name, commandFile => + { + var filePath = workspace.ResolvePath(commandFile.Name); + return File.ReadAllBytes(filePath); + }); + files.Add(scriptName, Encoding.UTF8.GetBytes(command.Scripts[ScriptType.Bash])); //Ideally this would just be using command.ScriptBody, but Octopus Server thinks its a windows machine because tentacle is running locally. + await configMapService.Create(scriptFilesConfigMapName, files, cancellationToken); + var serviceAccountName = !string.IsNullOrWhiteSpace(command.ScriptPodServiceAccountName) ? command.ScriptPodServiceAccountName : KubernetesConfig.PodServiceAccountName; @@ -218,11 +230,16 @@ async Task CreatePod(StartKubernetesScriptCommandV1 command, IScriptWorkspace wo } }; - pod.Spec.InitContainers = await CreateInitContainers(command, podName, homeDir, workspacePath, tentacleScriptLog, scriptPodTemplate?.ScriptInitContainerSpec); - pod.Spec.Containers = await CreateScriptContainers(command, podName, scriptName, homeDir, workspacePath, workspace.ScriptArguments, tentacleScriptLog, scriptPodTemplate); + //pod.Spec.InitContainers = await CreateInitContainers(command, podName, homeDir, workspacePath, tentacleScriptLog, scriptPodTemplate?.ScriptInitContainerSpec); + pod.Spec.Containers = await CreateScriptContainers(command, podName, scriptName, homeDir, workspacePath, workspace.ScriptArguments, tentacleScriptLog, scriptPodTemplate, workspace); pod.Spec.ImagePullSecrets = imagePullSecretNames; pod.Spec.ServiceAccountName = serviceAccountName; pod.Spec.Volumes = Merge(pod.Spec.Volumes, CreateVolumes(command)); + pod.Spec.Volumes.Add(new V1Volume() + { + Name = "workspace-files", + ConfigMap = new V1ConfigMapVolumeSource() { Name = scriptFilesConfigMapName } + }); var createdPod = await podService.Create(pod, cancellationToken); podMonitor.AddPendingPod(command.ScriptTicket, createdPod); @@ -231,12 +248,12 @@ async Task CreatePod(StartKubernetesScriptCommandV1 command, IScriptWorkspace wo LogVerboseToBothLogs($"Executing script in Kubernetes Pod '{podName}'. Image: '{scriptContainer.Image}'.", tentacleScriptLog); } - protected virtual async Task> CreateScriptContainers(StartKubernetesScriptCommandV1 command, string podName, string scriptName, string homeDir, string workspacePath, string[]? scriptArguments, InMemoryTentacleScriptLog tentacleScriptLog, ScriptPodTemplate? template) + protected virtual async Task> CreateScriptContainers(StartKubernetesScriptCommandV1 command, string podName, string scriptName, string homeDir, string workspacePath, string[]? scriptArguments, InMemoryTentacleScriptLog tentacleScriptLog, ScriptPodTemplate? template, IScriptWorkspace workspace) { return new List { - await CreateScriptContainer(command, podName, scriptName, homeDir, workspacePath, scriptArguments, tentacleScriptLog, template?.ScriptContainerSpec) - }.AddIfNotNull(CreateWatchdogContainer(homeDir, template?.WatchdogContainerSpec)); + await CreateScriptContainer(command, podName, scriptName, homeDir, workspacePath, scriptArguments, tentacleScriptLog, template?.ScriptContainerSpec, workspace) + }; //.AddIfNotNull(CreateWatchdogContainer(homeDir, template?.WatchdogContainerSpec)); } protected virtual async Task> CreateInitContainers(StartKubernetesScriptCommandV1 command, string podName, string homeDir, string workspacePath, InMemoryTentacleScriptLog tentacleScriptLog, V1Container? containerSpec) @@ -245,20 +262,59 @@ protected virtual async Task> CreateInitContainers(StartKuber return new List(); } + + record CalamariDetail(string Platform, string Version); + + CalamariDetail? GetCalamariDetail(StartKubernetesScriptCommandV1 command) + { + var match = Regex.Match(command.Scripts[ScriptType.Bash], "Tools\\/Calamari.([^\\/]*)\\/([^\\/]*)"); + if (!match.Success) return null; + var platform = match.Groups[1].Value; + var version = match.Groups[2].Value; + return new CalamariDetail(platform, version); + + } + + protected virtual IList CreateVolumes(StartKubernetesScriptCommandV1 command) { - return new List + var volumnes = new List { new() { - Name = "tentacle-home", - PersistentVolumeClaim = new V1PersistentVolumeClaimVolumeSource + Name = "workspace", + EmptyDir = new V1EmptyDirVolumeSource() + }, + new() + { + Name = "bootstrap-runner", + Image = new() { - ClaimName = KubernetesConfig.PodVolumeClaimName + Reference = "octopusdeploy/kubernetes-agent-tentacle:8.3.3359", + PullPolicy = "IfNotPresent" } }, CreateAgentUpgradeSecretVolume(), }; + + var calamariDetail = GetCalamariDetail(command); + volumnes.Add(calamariDetail == null + ? new V1Volume + { + Name = "calamari", + EmptyDir = new V1EmptyDirVolumeSource() + } + : new V1Volume() + { + Name = "calamari", + Image = new() + { + Reference = $"octopus.calamari:{calamariDetail.Platform}.{calamariDetail.Version}", + PullPolicy = "IfNotPresent" + }, + + }); + return volumnes; } protected V1Volume CreateAgentUpgradeSecretVolume() @@ -288,18 +344,10 @@ void LogVerboseToBothLogs(string message, InMemoryTentacleScriptLog tentacleScri tentacleScriptLog.Verbose(message); } - protected async Task CreateScriptContainer(StartKubernetesScriptCommandV1 command, string podName, string scriptName, string homeDir, string workspacePath, string[]? scriptArguments, InMemoryTentacleScriptLog tentacleScriptLog, V1Container? containerSpec) + protected async Task CreateScriptContainer(StartKubernetesScriptCommandV1 command, string podName, string scriptName, string homeDir, string workspacePath, string[]? scriptArguments, InMemoryTentacleScriptLog tentacleScriptLog, V1Container? containerSpec, IScriptWorkspace workspace) { var spaceInformation = kubernetesPhysicalFileSystem.GetStorageInformation(); - var commandString = string.Join(" ", new[] - { - $"{homeDir}/Work/{command.ScriptTicket.TaskId}/bootstrapRunner", - Path.Combine(homeDir, workspacePath), - Path.Combine(homeDir, workspacePath, scriptName) - }.Concat(scriptArguments ?? Array.Empty()) - .Select(x => $"\"{x}\"")); - var envFrom = new List(); //if there is a scrip pod proxies defined if (!string.IsNullOrWhiteSpace(KubernetesConfig.ScriptPodProxiesSecretName)) @@ -332,24 +380,33 @@ protected async Task CreateScriptContainer(StartKubernetesScriptCom container.Image = command.PodImageConfiguration?.Image ?? await containerResolver.GetContainerImageForCluster(); container.ImagePullPolicy = KubernetesConfig.ScriptPodPullPolicy; container.Command = new List { "sh" }; + + var args = (scriptArguments ?? Array.Empty()).Select(x => $"\"{x}\"").StringJoin(" "); container.Args = new List { "-c", - commandString + "cd /workspace " + + " && cp /workspace-files/* ./" + + " && echo $KEYFILE > ./keyfile " + //TODO: Support sourcing the keyfile in bootstrapper straight from env variable. + $" && /bootstrap-runner/bootstrapRunner ./ ./{scriptName} {args}" }; - container.VolumeMounts = Merge(container.VolumeMounts, new[] { - new V1VolumeMount(homeDir, "tentacle-home"), - new V1VolumeMount("/root/agent_upgrade/", "agent-upgrade"), - new V1VolumeMount("/tmp/agent_upgrade/", "agent-upgrade") + new V1VolumeMount("/workspace", "workspace"), + new V1VolumeMount("/bootstrap-runner", "bootstrap-runner", readOnlyProperty: true), + new V1VolumeMount("/workspace-files", "workspace-files", readOnlyProperty: true), + new V1VolumeMount($"{homeDir}/Tools", "calamari", readOnlyProperty: true) + // new V1VolumeMount("/root/agent_upgrade/", "agent-upgrade"), + // new V1VolumeMount("/tmp/agent_upgrade/", "agent-upgrade") }); + var keyfile = File.ReadAllText(Path.Combine(workspace.WorkingDirectory, "keyfile")); container.Env = Merge(container.Env, new List { + new("KEYFILE", keyfile), new(KubernetesConfig.NamespaceVariableName, KubernetesConfig.Namespace), - new(KubernetesConfig.HelmReleaseNameVariableName, KubernetesConfig.HelmReleaseName), - new(KubernetesConfig.HelmChartVersionVariableName, KubernetesConfig.HelmChartVersion), + //new(KubernetesConfig.HelmReleaseNameVariableName, KubernetesConfig.HelmReleaseName), + //new(KubernetesConfig.HelmChartVersionVariableName, KubernetesConfig.HelmChartVersion), new(KubernetesConfig.KubernetesMonitorEnabledVariableName, KubernetesConfig.KubernetesMonitorEnabled), new(KubernetesConfig.ServerCommsAddressesVariableName, string.Join(",", KubernetesConfig.ServerCommsAddresses)), new(KubernetesConfig.PersistentVolumeFreeBytesVariableName, spaceInformation?.freeSpaceBytes.ToString()), @@ -603,4 +660,19 @@ protected static IList Merge(IEnumerable? a, IEnumerable? b) return list; } } -} \ No newline at end of file +} +/* + DOCKERFILE - post build. + +FROM busybox +COPY Calamari.linux-x64.2026.1.169 /Calamari +RUN chmod +x /Calamari/Calamari && touch /Calamari/Success.txt + +FROM scratch +COPY --from=0 /Calamari /Calamari.linux-x64/2026.1.169 + +#docker build . -t octopus.calamari:linux-x64.2026.1.169 +# kind load docker-image octopus.calamari:linux-x64.2026.1.169 --name imagepull + + + */ \ No newline at end of file diff --git a/source/Octopus.Tentacle/Kubernetes/KubernetesService.cs b/source/Octopus.Tentacle/Kubernetes/KubernetesService.cs index f4ba7e10a..36e3ff325 100644 --- a/source/Octopus.Tentacle/Kubernetes/KubernetesService.cs +++ b/source/Octopus.Tentacle/Kubernetes/KubernetesService.cs @@ -41,11 +41,11 @@ protected void AddStandardMetadata(IKubernetesObject k8sObject) //Add helm specific metadata so it's removed if the helm release is uninstalled k8sObject.Metadata.Annotations ??= new Dictionary(); - k8sObject.Metadata.Annotations["meta.helm.sh/release-name"] = KubernetesConfig.HelmReleaseName; - k8sObject.Metadata.Annotations["meta.helm.sh/release-namespace"] = KubernetesConfig.Namespace; + //k8sObject.Metadata.Annotations["meta.helm.sh/release-name"] = KubernetesConfig.HelmReleaseName; + //k8sObject.Metadata.Annotations["meta.helm.sh/release-namespace"] = KubernetesConfig.Namespace; k8sObject.Metadata.Labels ??= new Dictionary(); - k8sObject.Metadata.Labels["app.kubernetes.io/managed-by"] = "Helm"; + //k8sObject.Metadata.Labels["app.kubernetes.io/managed-by"] = "Helm"; } protected async Task TryGetAsync(Func> loadAction) where T: class diff --git a/source/Octopus.Tentacle/Kubernetes/LocalMachineKubernetesClientConfigProvider.cs b/source/Octopus.Tentacle/Kubernetes/LocalMachineKubernetesClientConfigProvider.cs index 91a475256..2801e272f 100644 --- a/source/Octopus.Tentacle/Kubernetes/LocalMachineKubernetesClientConfigProvider.cs +++ b/source/Octopus.Tentacle/Kubernetes/LocalMachineKubernetesClientConfigProvider.cs @@ -19,27 +19,40 @@ public KubernetesClientConfiguration Get() { return GetTelepresenceConfig(telepresenceRoot); } - var kubeConfigEnvVar = Environment.GetEnvironmentVariable("KUBECONFIG"); - if (kubeConfigEnvVar != null && !Path.IsPathRooted(kubeConfigEnvVar)) + + var kubeConfigEnvVar = GetKubeConfigPath(); + + var contextEnvVar = Environment.GetEnvironmentVariable(KubernetesConfig.KubeContextVariableName); + var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigEnvVar, contextEnvVar); + + var namespaceEnvVar = Environment.GetEnvironmentVariable(KubernetesConfig.NamespaceVariableName); + if (!string.IsNullOrEmpty(namespaceEnvVar)) { - // Path.GetFullPath doesn't work with ~, so we need to expand it manually - if (kubeConfigEnvVar.StartsWith("~")) - { - kubeConfigEnvVar = kubeConfigEnvVar - .Replace("~", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) - .Replace("//", "/"); - } - else - { - kubeConfigEnvVar = Path.GetFullPath(kubeConfigEnvVar); - } + config.Namespace = namespaceEnvVar; } - return KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigEnvVar); + + return config; #else throw new NotSupportedException("Local machine configuration is only supported when debugging."); #endif } + static string? GetKubeConfigPath() + { + var kubeConfigEnvVar = Environment.GetEnvironmentVariable("KUBECONFIG"); + if (string.IsNullOrEmpty(kubeConfigEnvVar) || Path.IsPathRooted(kubeConfigEnvVar)) return kubeConfigEnvVar; + + // Path.GetFullPath doesn't work with ~, so we need to expand it manually + if (kubeConfigEnvVar.StartsWith("~")) + { + return kubeConfigEnvVar + .Replace("~", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Replace("//", "/"); + } + + return Path.GetFullPath(kubeConfigEnvVar); + } + KubernetesClientConfiguration GetTelepresenceConfig(string telepresenceRoot) { var serviceAccountPath = @@ -70,7 +83,7 @@ KubernetesClientConfiguration GetTelepresenceConfig(string telepresenceRoot) SslCaCerts = certificates, }; - var namespaceVar = Environment.GetEnvironmentVariable("OCTOPUS__K8STENTACLE__NAMESPACE"); + var namespaceVar = Environment.GetEnvironmentVariable(KubernetesConfig.NamespaceVariableName); if (!string.IsNullOrEmpty(namespaceVar)) {