public LoginInfo(KubeConfigContext context) { this.Name = context.Name; var sbInfo = new StringBuilder(); if (context.Extension.SetupDetails.SetupPending) { sbInfo.AppendWithSeparator("setup"); } this.Info = sbInfo.ToString(); }
/// <inheritdoc/> public override void Run(CommandLine commandLine) { KubeConfigContext context = null; KubeContextName contextName = null; if (commandLine.Arguments.Length > 0) { contextName = KubeContextName.Parse(commandLine.Arguments.FirstOrDefault()); } if (contextName != null) { context = KubeHelper.Config.GetContext(contextName); if (context == null) { Console.Error.WriteLine($"*** ERROR: Context [{contextName}] not found."); Program.Exit(1); } } else { context = KubeHelper.CurrentContext; if (context == null) { Console.Error.WriteLine($"*** ERROR: You are not logged into a cluster."); Program.Exit(1); } contextName = (KubeContextName)context.Name; } if (!commandLine.HasOption("--force") && !Program.PromptYesNo($"*** Are you sure you want to remove: {contextName}?")) { return; } if (KubeHelper.CurrentContextName == contextName) { Console.WriteLine($"Logging out of: {contextName}"); KubeHelper.Desktop.Logout().Wait(); } KubeHelper.Config.RemoveContext(context); Console.WriteLine($"Removed: {contextName}"); // Notify the desktop application. KubeHelper.Desktop.UpdateUIAsync().Wait(); }
/// <summary> /// Updates the running proxies to match the current cluster /// (if there is one). This may only be called on the UI thread. /// </summary> private async Task UpdateProxiesAsync() { if (InvokeRequired) { throw new InvalidOperationException($"[{nameof(UpdateProxiesAsync)}()] may only be called on the UI thread."); } if (KubeHelper.CurrentContext == null) { StopProxies(); StopPortForwards(); } else { var cluster = Program.GetCluster(); // We're going to use the current Kubenetes context name and cluster ID // to determine whether we're still connected to the same cluster. if (proxiedContext != null) { if (proxiedContext.Name == KubeHelper.CurrentContext.Name && proxiedContext.Extension.ClusterId == KubeHelper.CurrentContext.Extension.ClusterId) { // We're still proxying the same cluster so no changes // are required. return; } } StopProxies(); StopPortForwards(); if (KubeHelper.CurrentContext == null) { // Wr're not logged into a cluster so don't start any proxies. return; } try { // Start the connecting animation if we're not already in the error state. if (!InErrorState) { StartNotifyAnimation(connectingAnimation, $"{KubeHelper.CurrentContextName}: Connecting...", isTransient: true); } //------------------------------------------------------------- // The Kubernetes dashboard reverse proxy. // Setup a callback that transparently adds an [Authentication] header // to all requests with the correct bearer token. We'll need to // obtain the token secret via two steps: // // 1. Identify the dashboard token secret by listing all secrets // in the [kube-system] namespace looking for one named like // [root-user-token-*]. // // 2. Reading that secret and extracting the value. var response = (ExecuteResponse)null; var secretName = string.Empty; var dashboardToken = string.Empty; await Task.Run( async() => { response = KubeHelper.Kubectl("--namespace", "kube-system", "get", "secrets", "-o=name"); await Task.CompletedTask; }); if (response.ExitCode != 0) { try { response.EnsureSuccess(); } catch (Exception e) { Program.LogError(e); SetErrorState($"{KubeHelper.CurrentContextName}: Kubernetes API failure"); return; } } // Step 1: Determine the secret name. using (var reader = new StringReader(response.OutputText)) { const string secretPrefix = "secret/"; secretName = reader.Lines().FirstOrDefault(line => line.StartsWith($"{secretPrefix}root-user-token-")); Covenant.Assert(!string.IsNullOrEmpty(secretName)); secretName = secretName.Substring(secretPrefix.Length); } // Step 2: Describe the secret and extract the token value. This // is a bit of a hack because I'm making assumptions about // the output format. await Task.Run( async() => { response = KubeHelper.Kubectl("--namespace", "kube-system", "describe", "secret", secretName); await Task.CompletedTask; }); if (response.ExitCode != 0) { try { response.EnsureSuccess(); } catch (Exception e) { Program.LogError(e); SetErrorState($"{KubeHelper.CurrentContextName}: Kubernetes API failure"); return; } } using (var reader = new StringReader(response.OutputText)) { var tokenLine = reader.Lines().FirstOrDefault(line => line.StartsWith("token:")); Covenant.Assert(!string.IsNullOrEmpty(tokenLine)); dashboardToken = tokenLine.Split(new char[] { ' ' }, 2).Skip(1).First().Trim(); } Action <RequestContext> dashboardRequestHandler = context => { context.Request.Headers.Add("Authorization", $"Bearer {dashboardToken}"); }; // Start the proxy. var userContext = KubeHelper.Config.GetUser(KubeHelper.CurrentContext.Properties.User); var certPem = Encoding.UTF8.GetString(Convert.FromBase64String(userContext.Properties.ClientCertificateData)); var keyPem = Encoding.UTF8.GetString(Convert.FromBase64String(userContext.Properties.ClientKeyData)); var dashboardCert = TlsCertificate.Parse(KubeHelper.CurrentContext.Extension.KubernetesDashboardCertificate).ToX509(publicOnly: true); var kubeDashboardProxy = new ReverseProxy( localPort: KubeHelper.ClientConfig.KubeDashboardProxyPort, remotePort: KubeHostPorts.KubeDashboard, remoteHost: cluster.GetReachableMaster().PrivateAddress.ToString(), validCertificate: dashboardCert, requestHandler: dashboardRequestHandler); proxies.Add(kubeDashboardProxy); var kibanaDashboardProxy = new PortForward( serviceName: "kibana-kibana", localPort: KubeConst.KibanaDashboardProxyPort, remotePort: KubeConst.KibanaDashboardProxyPort, @namespace: "logging"); portForwards.Add(kibanaDashboardProxy); var prometheusDashboardProxy = new PortForward( serviceName: "prometheus", localPort: KubeConst.PrometheusDashboardProxyPort, remotePort: KubeConst.PrometheusDashboardProxyPort, @namespace: "istio-system"); portForwards.Add(prometheusDashboardProxy); var kialiDashboardProxy = new PortForward( serviceName: "kiali", localPort: KubeConst.KialiDashboardProxyPort, remotePort: KubeConst.KialiDashboardProxyPort, @namespace: "istio-system"); portForwards.Add(kialiDashboardProxy); var grafanaDashboardProxy = new PortForward( serviceName: "grafana", localPort: KubeConst.GrafanaDashboardProxyPort, remotePort: KubeConst.GrafanaDashboardProxyPort, @namespace: "istio-system"); portForwards.Add(grafanaDashboardProxy); //------------------------------------------------------------- // Remember which cluster context we're proxying. proxiedContext = KubeHelper.CurrentContext; } catch (Exception e) { Console.WriteLine(e); } finally { // Stop any non-error transient animation on the top of the notify stack. if (notifyStack.Count > 0 && notifyStack.Peek().IsTransient&& !notifyStack.Peek().IsError) { StopNotifyAnimation(); } } } }
/// <inheritdoc/> public override async Task RunAsync(CommandLine commandLine) { if (commandLine.Arguments.Length < 1) { Console.Error.WriteLine("*** ERROR: [root@CLUSTER-NAME] argument is required."); Program.Exit(1); } Console.WriteLine(); // Cluster prepare/setup uses the [ProfileClient] to retrieve secrets and profile values. // We need to inject an implementation for [PreprocessReader] so it will be able to // perform the lookups. NeonHelper.ServiceContainer.AddSingleton <IProfileClient>(new ProfileClient()); var contextName = KubeContextName.Parse(commandLine.Arguments[0]); var kubeCluster = KubeHelper.Config.GetCluster(contextName.Cluster); var unredacted = commandLine.HasOption("--unredacted"); var debug = commandLine.HasOption("--debug"); var check = commandLine.HasOption("--check"); var uploadCharts = commandLine.HasOption("--upload-charts") || debug; var clusterspace = commandLine.GetOption("--clusterspace"); var maxParallelOption = commandLine.GetOption("--max-parallel", "6"); var disablePending = commandLine.HasOption("--disable-pending"); if (!int.TryParse(maxParallelOption, out var maxParallel) || maxParallel <= 0) { Console.Error.WriteLine($"*** ERROR: [--max-parallel={maxParallelOption}] is not valid."); Program.Exit(1); } clusterLogin = KubeHelper.GetClusterLogin(contextName); if (clusterLogin == null) { Console.Error.WriteLine($"*** ERROR: Be sure to prepare the cluster first via: neon cluster prepare..."); Program.Exit(1); } if (string.IsNullOrEmpty(clusterLogin.SshPassword)) { Console.Error.WriteLine($"*** ERROR: No cluster node SSH password found."); Program.Exit(1); } if (kubeCluster != null && !clusterLogin.SetupDetails.SetupPending) { if (commandLine.GetOption("--force") == null && !Program.PromptYesNo($"One or more logins reference [{kubeCluster.Name}]. Do you wish to delete these?")) { Program.Exit(0); } // Remove the cluster from the kubeconfig and remove any // contexts that reference it. KubeHelper.Config.Clusters.Remove(kubeCluster); var delList = new List <KubeConfigContext>(); foreach (var context in KubeHelper.Config.Contexts) { if (context.Properties.Cluster == kubeCluster.Name) { delList.Add(context); } } foreach (var context in delList) { KubeHelper.Config.Contexts.Remove(context); } if (KubeHelper.CurrentContext != null && KubeHelper.CurrentContext.Properties.Cluster == kubeCluster.Name) { KubeHelper.Config.CurrentContext = null; } KubeHelper.Config.Save(); } kubeContext = new KubeConfigContext(contextName); KubeHelper.InitContext(kubeContext); // Create and run the cluster setup controller. var clusterDefinition = clusterLogin.ClusterDefinition; var controller = KubeSetup.CreateClusterSetupController( clusterDefinition, maxParallel: maxParallel, unredacted: unredacted, debugMode: debug, uploadCharts: uploadCharts, clusterspace: clusterspace); controller.DisablePendingTasks = disablePending; controller.StatusChangedEvent += status => { status.WriteToConsole(); }; switch (await controller.RunAsync()) { case SetupDisposition.Succeeded: var pendingGroups = controller.GetPendingGroups(); if (pendingGroups.Count > 0) { Console.WriteLine($"*** ERROR: [{pendingGroups.Count}] pending task groups have not been awaited:"); Console.WriteLine(); foreach (var groupName in pendingGroups) { Console.WriteLine($" {groupName}"); } Program.Exit(1); } Console.WriteLine(); Console.WriteLine($" [{clusterDefinition.Name}] cluster is ready."); Console.WriteLine(); if (check && !debug) { var k8s = new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile(KubeHelper.KubeConfigPath)); if (!await ClusterChecker.CheckAsync(clusterLogin, k8s)) { Program.Exit(1); } } Program.Exit(0); break; case SetupDisposition.Cancelled: Console.WriteLine(" *** CANCELLED: Cluster setup was cancelled."); Console.WriteLine(); Console.WriteLine(); Program.Exit(1); break; case SetupDisposition.Failed: Console.WriteLine(); Console.WriteLine(" *** ERROR: Cluster setup failed. Examine the logs here:"); Console.WriteLine(); Console.WriteLine($" {KubeHelper.LogFolder}"); Console.WriteLine(); Program.Exit(1); break; default: throw new NotImplementedException(); } await Task.CompletedTask; }
/// <inheritdoc/> public override async Task RunAsync(CommandLine commandLine) { KubeConfigContext context = null; KubeContextName contextName = null; Console.WriteLine(); var force = commandLine.HasOption("--force"); if (commandLine.Arguments.Length > 0) { contextName = KubeContextName.Parse(commandLine.Arguments.FirstOrDefault()); if (!contextName.IsNeonKube) { Console.Error.WriteLine($"*** ERROR: [{contextName}] is not a neonKUBE context."); Program.Exit(1); } } if (contextName != null) { context = KubeHelper.Config.GetContext(contextName); if (context == null) { if (!force) { Console.Error.WriteLine($"*** ERROR: Context [{contextName}] not found."); Program.Exit(1); } else { KubeHelper.Config.RemoveContext(contextName); Program.Exit(0); } } } else { context = KubeHelper.CurrentContext; if (context == null) { if (!force) { Console.Error.WriteLine($"*** ERROR: You are not logged into a neonKUBE cluster."); Program.Exit(1); } else { Program.Exit(0); } } contextName = (KubeContextName)context.Name; } if (!force && !Program.PromptYesNo($"*** Are you sure you want to remove: {contextName}?")) { return; } if (KubeHelper.CurrentContextName == contextName) { Console.WriteLine($"Logging out of: {contextName}"); } KubeHelper.Config.RemoveContext(context); Console.WriteLine($"Removed: {contextName}"); Console.WriteLine(); await Task.CompletedTask; }