예제 #1
0
            public LoginInfo(KubeConfigContext context)
            {
                this.Name = context.Name;

                var sbInfo = new StringBuilder();

                if (context.Extension.SetupDetails.SetupPending)
                {
                    sbInfo.AppendWithSeparator("setup");
                }

                this.Info = sbInfo.ToString();
            }
예제 #2
0
        /// <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();
        }
예제 #3
0
        /// <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();
                    }
                }
            }
        }
예제 #4
0
        /// <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;
        }
예제 #5
0
        /// <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;
        }