/// <summary> /// <para> /// Ensures that <b>kubectl</b> tool whose version is at least as great as the Kubernetes /// cluster version is installed to the <b>neonKUBE</b> programs folder by copying the /// tool from the cache if necessary. /// </para> /// <note> /// This will probably require elevated privileges. /// </note> /// <note> /// This assumes that <b>kubectl</b> has already been downloaded and cached and also that /// more recent <b>kubectl</b> releases are backwards compatible with older deployed versions /// of Kubernetes. /// </note> /// </summary> /// <param name="setupInfo">The KUbernetes setup information.</param> public static void InstallKubeCtl(KubeSetupInfo setupInfo) { Covenant.Requires <ArgumentNullException>(setupInfo != null); var hostPlatform = KubeHelper.HostPlatform; var cachedKubeCtlPath = KubeHelper.GetCachedComponentPath(hostPlatform, "kubectl", setupInfo.Versions.Kubernetes); var targetPath = Path.Combine(KubeHelper.ProgramFolder); switch (hostPlatform) { case KubeHostPlatform.Windows: targetPath = Path.Combine(targetPath, "kubectl.exe"); // Ensure that the KUBECONFIG environment variable exists and includes // the path to the user's [.neonkube] configuration. var kubeConfigVar = Environment.GetEnvironmentVariable("KUBECONFIG"); if (string.IsNullOrEmpty(kubeConfigVar)) { // The [KUBECONFIG] environment variable doesn't exist so we'll set it. Registry.SetValue(@"HKEY_CURRENT_USER\Environment", "KUBECONFIG", KubeConfigPath, RegistryValueKind.ExpandString); Environment.SetEnvironmentVariable("KUBECONFIG", KubeConfigPath); } else { // The [KUBECONFIG] environment variable exists but we still need to // ensure that the path to our [USER/.neonkube] config is present. var sb = new StringBuilder(); var found = false; foreach (var path in kubeConfigVar.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) { if (path == KubeConfigPath) { found = true; } sb.AppendWithSeparator(path, ";"); } if (!found) { sb.AppendWithSeparator(KubeConfigPath, ";"); } var newKubeConfigVar = sb.ToString(); if (newKubeConfigVar != kubeConfigVar) { Registry.SetValue(@"HKEY_CURRENT_USER\Environment", "KUBECONFIG", newKubeConfigVar, RegistryValueKind.ExpandString); Environment.SetEnvironmentVariable("KUBECONFIG", newKubeConfigVar); } } if (!File.Exists(targetPath)) { File.Copy(cachedKubeCtlPath, targetPath); } else { // Execute the existing target to obtain its version and update it // to the cached copy if the cluster installed a more recent version // of Kubernetes. // $hack(jeff.lill): Simple client version extraction var pattern = "GitVersion:\"v"; var response = NeonHelper.ExecuteCapture(targetPath, "version"); var pStart = response.OutputText.IndexOf(pattern); var error = "Cannot identify existing [kubectl] version."; if (pStart == -1) { throw new KubeException(error); } pStart += pattern.Length; var pEnd = response.OutputText.IndexOf("\"", pStart); if (pEnd == -1) { throw new KubeException(error); } var currentVersionString = response.OutputText.Substring(pStart, pEnd - pStart); if (!Version.TryParse(currentVersionString, out var currentVersion)) { throw new KubeException(error); } if (Version.Parse(setupInfo.Versions.Kubernetes) > currentVersion) { // We need to copy the latest version. if (File.Exists(targetPath)) { File.Delete(targetPath); } File.Copy(cachedKubeCtlPath, targetPath); } } break; case KubeHostPlatform.Linux: case KubeHostPlatform.Osx: default: throw new NotImplementedException($"[{hostPlatform}] support is not implemented."); } }
/// <summary> /// <para> /// Ensures that <b>helm</b> tool whose version is at least as great as the requested /// cluster version is installed to the <b>neonKUBE</b> programs folder by copying the /// tool from the cache if necessary. /// </para> /// <note> /// This will probably require elevated privileges. /// </note> /// <note> /// This assumes that <b>Helm</b> has already been downloaded and cached and also that /// more recent <b>Helm</b> releases are backwards compatible with older deployed versions /// of Tiller. /// </note> /// </summary> /// <param name="setupInfo">The KUbernetes setup information.</param> public static void InstallHelm(KubeSetupInfo setupInfo) { Covenant.Requires <ArgumentNullException>(setupInfo != null); var hostPlatform = KubeHelper.HostPlatform; var cachedHelmPath = KubeHelper.GetCachedComponentPath(hostPlatform, "helm", setupInfo.Versions.Helm); var targetPath = Path.Combine(KubeHelper.ProgramFolder); switch (hostPlatform) { case KubeHostPlatform.Windows: targetPath = Path.Combine(targetPath, "helm.exe"); if (!File.Exists(targetPath)) { File.Copy(cachedHelmPath, targetPath); } else { // Execute the existing target to obtain its version and update it // to the cached copy if the cluster installed a more recent version // of Kubernetes. // $hack(jeff.lill): Simple client version extraction var pattern = "SemVer:\"v"; var response = NeonHelper.ExecuteCapture(targetPath, "version"); var pStart = response.OutputText.IndexOf(pattern); var error = "Cannot identify existing [helm] version."; if (pStart == -1) { throw new KubeException(error); } pStart += pattern.Length; var pEnd = response.OutputText.IndexOf("\"", pStart); if (pEnd == -1) { throw new KubeException(error); } var currentVersionString = response.OutputText.Substring(pStart, pEnd - pStart); if (!Version.TryParse(currentVersionString, out var currentVersion)) { throw new KubeException(error); } if (Version.Parse(setupInfo.Versions.Helm) > currentVersion) { // We need to copy the latest version. File.Copy(cachedHelmPath, targetPath); } } break; case KubeHostPlatform.Linux: case KubeHostPlatform.Osx: default: throw new NotImplementedException($"[{hostPlatform}] support is not implemented."); } }
/// <summary> /// Returns information required for setting up a Kubernetes cluster. This /// includes things like the URIs to be used for downloading the <b>kubectl</b> /// and <b>kubeadm</b> tools. /// </summary> /// <param name="clusterDefinition">The cluster definition.</param> /// <returns>A <see cref="KubeSetupInfo"/> with the information.</returns> public async Task <KubeSetupInfo> GetSetupInfoAsync(ClusterDefinition clusterDefinition) { await SyncContext.ClearAsync; Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition)); var kubeVersion = Version.Parse(defaultKubeVersion); if (!clusterDefinition.Kubernetes.Version.Equals("default", StringComparison.InvariantCultureIgnoreCase)) { kubeVersion = Version.Parse(clusterDefinition.Kubernetes.Version); } var kubeDashboardVersion = Version.Parse(defaultKubeDashboardVersion); if (!clusterDefinition.Kubernetes.DashboardVersion.Equals("default", StringComparison.InvariantCultureIgnoreCase)) { kubeDashboardVersion = Version.Parse(clusterDefinition.Kubernetes.DashboardVersion); } // Ensure that the we have package versions defined for the selected Kubernetes version. Covenant.Assert(ubuntuKubeAdmPackages.ContainsKey(kubeVersion.ToString())); Covenant.Assert(ubuntuKubeCtlPackages.ContainsKey(kubeVersion.ToString())); Covenant.Assert(ubuntuKubeletPackages.ContainsKey(kubeVersion.ToString())); // $todo(jefflill): Hardcoded // $todo(jefflill): Verify Docker/Kubernetes version compatibility. var dockerVersion = clusterDefinition.Docker.Version; if (dockerVersion == "default") { dockerVersion = defaultDockerVersion; } if (supportedDockerVersions.SingleOrDefault(v => v == dockerVersion) == null) { throw new KubeException($"[{dockerVersion}] is not a supported Docker version."); } var helmVersion = clusterDefinition.Kubernetes.HelmVersion; if (helmVersion == "default") { helmVersion = defaultHelmVersion; } if (supportedHelmVersions.SingleOrDefault(v => v == helmVersion) == null) { throw new KubeException($"[{helmVersion}] is not a supported Helm version."); } // $todo(jefflill): // // The code below supports only the Calico CNI for now. This will probably be // replaced by the integrated Istio CNI soon. if (clusterDefinition.Network.Cni != NetworkCni.Calico) { throw new NotImplementedException($"The [{clusterDefinition.Network.Cni}] CNI is not currently supported."); } var calicoVersion = clusterDefinition.Network.CniVersion; if (calicoVersion == "default") { calicoVersion = defaultCalicoVersion; } if (clusterDefinition.Network.Cni == NetworkCni.Calico && supportedCalicoVersions.SingleOrDefault(v => v == calicoVersion) == null) { throw new KubeException($"[{calicoVersion}] is not a supported Calico version."); } var istioVersion = clusterDefinition.Network.IstioVersion; if (istioVersion == "default") { istioVersion = defaultIstioVersion; } if (supportedIstioVersions.SingleOrDefault(v => v == istioVersion) == null) { throw new KubeException($"[{istioVersion}] is not a supported Istio version."); } var setupInfo = new KubeSetupInfo() { KubeAdmLinuxUri = $"https://storage.googleapis.com/kubernetes-release/release/v{kubeVersion}/linux/amd64/kubeadm", KubeCtlLinuxUri = $"https://storage.googleapis.com/kubernetes-release/release/v{kubeVersion}/linux/amd64/kubectl", KubeletLinuxUri = $"https://storage.googleapis.com/kubernetes-release/release/v{kubeVersion}/linux/amd64/kubelet", KubeCtlOsxUri = $"https://storage.googleapis.com/kubernetes-release/release/v{kubeVersion}/bin/darwin/amd64/kubectl", KubeCtlWindowsUri = $"https://storage.googleapis.com/kubernetes-release/release/v{kubeVersion}/bin/windows/amd64/kubectl.exe", DockerPackageUbuntuUri = $"https://s3-us-west-2.amazonaws.com/neonforge/kube/{dockerVersion}-ubuntu-bionic-stable-amd64.deb", KubeAdmPackageUbuntuVersion = ubuntuKubeAdmPackages[kubeVersion.ToString()], KubeCtlPackageUbuntuVersion = ubuntuKubeCtlPackages[kubeVersion.ToString()], KubeletPackageUbuntuVersion = ubuntuKubeletPackages[kubeVersion.ToString()], HelmLinuxUri = $"https://storage.googleapis.com/kubernetes-helm/helm-v{helmVersion}-linux-amd64.tar.gz", HelmOsxUri = $"https://storage.googleapis.com/kubernetes-helm/helm-v{helmVersion}-darwin-amd64.tar.gz", HelmWindowsUri = $"https://storage.googleapis.com/kubernetes-helm/helm-v{helmVersion}-windows-amd64.zip", // $todo(jefflill): // // I'm a little worried about where the "1.7" in the [CalicoSetupUri] came from. I suspect that // this will vary too. Once the Istio CNI is stable, we'll probably delete this anyway but if // we do decide to allow for custom CNIs, we'll need to figure this out. CalicoRbacYamlUri = $"https://docs.projectcalico.org/v{calicoVersion}/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml", CalicoSetupYamlUri = $"https://docs.projectcalico.org/v{calicoVersion}/manifests/calico.yaml", IstioLinuxUri = $"https://github.com/istio/istio/releases/download/{istioVersion}/istio-{istioVersion}-linux.tar.gz" }; var kubeVersionString = kubeVersion.ToString(); setupInfo.Versions.Kubernetes = kubeVersionString; var kubeDashboardConfig = KubeDashboardConfig.GetDashboardConfigFor(kubeVersion.ToString()); setupInfo.KubeDashboardYaml = kubeDashboardConfig.ConfigYaml; setupInfo.Versions.KubernetesDashboard = kubeDashboardConfig.Version; setupInfo.Versions.Docker = dockerVersion; setupInfo.Versions.Helm = helmVersion; setupInfo.Versions.Calico = calicoVersion; setupInfo.Versions.Istio = istioVersion; await Task.CompletedTask; return(setupInfo); }