Exemplo n.º 1
0
        /// <summary>
        ///     Create a new <see cref="DatabaseServerProvisioner"/>.
        /// </summary>
        /// <param name="logger">
        ///     The provisioner's logger.
        /// </param>
        /// <param name="kubeClient">
        ///     The <see cref="KubeApiClient"/> used to communicate with the Kubernetes API.
        /// </param>
        /// <param name="kubeOptions">
        ///     Application-level Kubernetes settings.
        /// </param>
        /// <param name="kubeResources">
        ///     A factory for Kubernetes resource models.
        /// </param>
        /// <param name="databaseProxyClient">
        ///     The <see cref="DatabaseProxyApiClient"/> used to communicate with the Database Proxy API.
        /// </param>
        public DatabaseServerProvisioner(ILogger <DatabaseServerProvisioner> logger, KubeApiClient kubeClient, DatabaseProxyApiClient databaseProxyClient, IOptions <KubernetesOptions> kubeOptions, KubeResources kubeResources)
            : base(logger)
        {
            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            if (kubeClient == null)
            {
                throw new ArgumentNullException(nameof(kubeClient));
            }

            if (databaseProxyClient == null)
            {
                throw new ArgumentNullException(nameof(databaseProxyClient));
            }

            if (kubeOptions == null)
            {
                throw new ArgumentNullException(nameof(kubeOptions));
            }

            if (kubeResources == null)
            {
                throw new ArgumentNullException(nameof(kubeResources));
            }

            KubeClient          = kubeClient;
            DatabaseProxyClient = databaseProxyClient;
            KubeOptions         = kubeOptions.Value;
            KubeResources       = kubeResources;
        }
        /// <summary>
        ///     Create a <see cref="SecretV1"/> for deploying a Glider Gun Remote node.
        /// </summary>
        /// <param name="resources">
        ///     The Kubernetes resource template service.
        /// </param>
        /// <param name="options">
        ///     The current options for the deployment tool.
        /// </param>
        /// <returns>
        ///     The configured <see cref="SecretV1"/>.
        /// </returns>
        public static SecretV1 DeployGliderGunRemoteSecret(this KubeResources resources, ProgramOptions options)
        {
            if (resources == null)
            {
                throw new ArgumentNullException(nameof(resources));
            }

            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            return(resources.OpaqueSecret(
                       name: resources.Names.DeployGliderGunRemoteSecret(options),
                       kubeNamespace: options.KubeNamespace,
                       labels: new Dictionary <string, string>
            {
                ["glider-gun.job.name"] = options.JobName,
                ["glider-gun.job.type"] = "deploy.glider-gun.remote"
            },
                       data: new Dictionary <string, string>
            {
                ["id_rsa"] = Convert.ToBase64String(
                    File.ReadAllBytes(options.SshPrivateKeyFile)
                    ),
                ["id_rsa.pub"] = Convert.ToBase64String(
                    File.ReadAllBytes(options.SshPublicKeyFile ?? options.SshPrivateKeyFile + ".pub")
                    )
            }
                       ));
        }
        /// <summary>
        ///     Create a <see cref="JobV1"/> for deploying a Glider Gun Remote node.
        /// </summary>
        /// <param name="resources">
        ///     The Kubernetes resource template service.
        /// </param>
        /// <param name="options">
        ///     The current options for the deployment tool.
        /// </param>
        /// <returns>
        ///     The configured <see cref="JobV1"/>.
        /// </returns>
        public static JobV1 DeployGliderGunRemoteJob(this KubeResources resources, ProgramOptions options)
        {
            if (resources == null)
            {
                throw new ArgumentNullException(nameof(resources));
            }

            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            return(resources.Job(
                       name: resources.Names.DeployGliderGunRemoteJob(options),
                       kubeNamespace: options.KubeNamespace,
                       spec: resources.Specs.DeployGliderGunRemoteJob(options),
                       labels: new Dictionary <string, string>
            {
                ["glider-gun.job.name"] = options.JobName,
                ["glider-gun.job.type"] = "deploy.glider-gun.remote"
            }
                       ));
        }
Exemplo n.º 4
0
        /// <summary>
        ///     Ensure that a Secret for data exists for the specified database server.
        /// </summary>
        /// <returns>
        ///     The Secret resource, as a <see cref="SecretV1"/>.
        /// </returns>
        public async Task <SecretV1> EnsureCredentialsSecretPresent()
        {
            RequireCurrentState();

            SecretV1 existingSecret = await FindCredentialsSecret();

            if (existingSecret != null)
            {
                Log.LogInformation("Found existing credentials secret {SecretName} for server {ServerId}.",
                                   existingSecret.Metadata.Name,
                                   State.Id
                                   );

                return(existingSecret);
            }

            Log.LogInformation("Creating credentials secret for server {ServerId}...",
                               State.Id
                               );

            Log.LogInformation("Requesting X.509 certificate...");

            CertificateCredentials serverCertificate = await RequestServerCertificate();

            SecretV1 createdSecret = await KubeClient.SecretsV1().Create(
                KubeResources.CredentialsSecret(State, serverCertificate,
                                                kubeNamespace: KubeOptions.KubeNamespace
                                                )
                );

            Log.LogInformation("Successfully created credentials secret {SecretName} for server {ServerId}.",
                               createdSecret.Metadata.Name,
                               State.Id
                               );

            return(createdSecret);
        }
Exemplo n.º 5
0
        /// <summary>
        ///     Create a new <see cref="ServerCredentialsProvisioner"/>.
        /// </summary>
        /// <param name="logger">
        ///     The provisioner's logger.
        /// </param>
        /// <param name="kubeClient">
        ///     The <see cref="KubeApiClient"/> used to communicate with the Kubernetes API.
        /// </param>
        /// <param name="vaultClient">
        ///     The <see cref="IVaultClient"/> used to communicate with the Vault API.
        /// </param>
        /// <param name="kubeResources">
        ///     A factory for Kubernetes resource models.
        /// </param>
        /// <param name="vaultOptions">
        ///     Application-level Vault settings.
        /// </param>
        /// <param name="kubeOptions">
        ///     Application-level Kubernetes settings.
        /// </param>
        public ServerCredentialsProvisioner(ILogger <DatabaseServerProvisioner> logger, KubeApiClient kubeClient, IVaultClient vaultClient, KubeResources kubeResources, IOptions <VaultOptions> vaultOptions, IOptions <KubernetesOptions> kubeOptions)
            : base(logger)
        {
            if (kubeClient == null)
            {
                throw new ArgumentNullException(nameof(kubeClient));
            }

            if (vaultClient == null)
            {
                throw new ArgumentNullException(nameof(vaultClient));
            }

            if (kubeResources == null)
            {
                throw new ArgumentNullException(nameof(kubeResources));
            }

            if (vaultOptions == null)
            {
                throw new ArgumentNullException(nameof(vaultOptions));
            }

            if (kubeOptions == null)
            {
                throw new ArgumentNullException(nameof(kubeOptions));
            }

            KubeClient  = kubeClient;
            VaultClient = vaultClient;

            KubeResources = kubeResources;

            VaultOptions = vaultOptions.Value;
            KubeOptions  = kubeOptions.Value;
        }
Exemplo n.º 6
0
        /// <summary>
        ///     The main program entry-point.
        /// </summary>
        /// <param name="commandLineArguments">
        ///     The program's command-line arguments.
        /// </param>
        /// <returns>
        ///     The program exit-code.
        /// </returns>
        static async Task <int> Main(string[] commandLineArguments)
        {
            // Show help if no arguments are specified.
            bool showHelp = commandLineArguments.Length == 0;

            if (showHelp)
            {
                commandLineArguments = new[] { "--help" }
            }
            ;

            try
            {
                SynchronizationContext.SetSynchronizationContext(
                    new SynchronizationContext()
                    );

                ProgramOptions options = ProgramOptions.Parse(commandLineArguments);
                if (options == null)
                {
                    return(showHelp ? ExitCodes.Success : ExitCodes.InvalidArguments);
                }

                ConfigureLogging(options);

                using (ServiceProvider serviceProvider = BuildServiceProvider(options))
                    using (AutoResetEvent done = new AutoResetEvent(initialState: false))
                    {
                        KubeApiClient client        = serviceProvider.GetRequiredService <KubeApiClient>();
                        KubeResources kubeResources = serviceProvider.GetRequiredService <KubeResources>();

                        string jobName = kubeResources.Names.DeployGliderGunRemoteJob(options);

                        JobV1 existingJob = await client.JobsV1().Get(jobName);

                        if (existingJob != null)
                        {
                            Log.Information("Found existing job {JobName} in namespace {KubeNamespace}; deleting...",
                                            existingJob.Metadata.Name,
                                            existingJob.Metadata.Namespace
                                            );

                            await client.JobsV1().Delete(jobName,
                                                         propagationPolicy: DeletePropagationPolicy.Foreground
                                                         );

                            Log.Information("Deleted existing job {JobName}.",
                                            existingJob.Metadata.Name,
                                            existingJob.Metadata.Namespace
                                            );
                        }

                        string secretName = kubeResources.Names.DeployGliderGunRemoteSecret(options);

                        SecretV1 existingSecret = await client.SecretsV1().Get(secretName);

                        if (existingSecret != null)
                        {
                            Log.Information("Found existing secret {SecretName} in namespace {KubeNamespace}; deleting...",
                                            existingSecret.Metadata.Name,
                                            existingSecret.Metadata.Namespace
                                            );

                            await client.SecretsV1().Delete(secretName);

                            Log.Information("Deleted existing secret {SecretName}.",
                                            existingSecret.Metadata.Name,
                                            existingSecret.Metadata.Namespace
                                            );
                        }

                        Log.Information("Creating deployment secret {SecretName}...", secretName);

                        SecretV1 deploymentSecret = kubeResources.DeployGliderGunRemoteSecret(options);
                        try
                        {
                            deploymentSecret = await client.SecretsV1().Create(deploymentSecret);
                        }
                        catch (HttpRequestException <StatusV1> createSecretFailed)
                        {
                            Log.Error(createSecretFailed, "Failed to create Kubernetes Secret {SecretName} for deployment ({Reason}): {ErrorMessage}",
                                      secretName,
                                      createSecretFailed.Response.Reason,
                                      createSecretFailed.Response.Message
                                      );

                            return(ExitCodes.JobFailed);
                        }

                        Log.Information("Created deployment secret {SecretName}.", deploymentSecret.Metadata.Name);

                        // Watch for job's associated pod to start, then monitor the pod's log until it completes.
                        IDisposable jobLogWatch = null;
                        IDisposable jobPodWatch = client.PodsV1().WatchAll(
                            labelSelector: $"job-name={jobName}",
                            kubeNamespace: options.KubeNamespace
                            ).Subscribe(
                            podEvent =>
                        {
                            if (jobLogWatch != null)
                            {
                                return;
                            }

                            PodV1 jobPod = podEvent.Resource;
                            if (jobPod.Status.Phase != "Pending")
                            {
                                Log.Information("Job {JobName} has started.", jobName);

                                Log.Verbose("Hook up log monitor for Pod {PodName} of Job {JobName}...",
                                            jobPod.Metadata.Name,
                                            jobName
                                            );

                                jobLogWatch = client.PodsV1().StreamLogs(
                                    name: jobPod.Metadata.Name,
                                    kubeNamespace: jobPod.Metadata.Namespace
                                    ).Subscribe(
                                    logEntry =>
                                {
                                    Log.Information("[{PodName}] {LogEntry}", jobPod.Metadata.Name, logEntry);
                                },
                                    error =>
                                {
                                    if (error is HttpRequestException <StatusV1> requestError)
                                    {
                                        Log.Error(requestError, "Kubernetes API request error ({Reason}): {ErrorMessage:l}",
                                                  requestError.Response.Reason,
                                                  requestError.Response.Message
                                                  );
                                    }
                                    else
                                    {
                                        Log.Error(error, "JobLog Error");
                                    }
                                },
                                    () =>
                                {
                                    Log.Information("[{PodName}] <end of log>", jobPod.Metadata.Name);

                                    done.Set();
                                }
                                    );

                                Log.Information("Monitoring log for Pod {PodName} of Job {JobName}.",
                                                jobPod.Metadata.Name,
                                                jobName
                                                );
                            }
                        },
                            error =>
                        {
                            Log.Error(error, "PodWatch Error");
                        },
                            () =>
                        {
                            Log.Information("PodWatch End");
                        }
                            );

                        Log.Information("Creating deployment job {JobName}...", jobName);

                        JobV1 deploymentJob = kubeResources.DeployGliderGunRemoteJob(options);
                        try
                        {
                            deploymentJob = await client.JobsV1().Create(deploymentJob);
                        }
                        catch (HttpRequestException <StatusV1> createJobFailed)
                        {
                            Log.Error(createJobFailed, "Failed to create Kubernetes Job {JobName} for deployment ({Reason}): {ErrorMessage}",
                                      jobName,
                                      createJobFailed.Response.Reason,
                                      createJobFailed.Response.Message
                                      );

                            return(ExitCodes.JobFailed);
                        }

                        Log.Information("Created deployment job {JobName}.", deploymentJob.Metadata.Name);

                        TimeSpan timeout = TimeSpan.FromSeconds(options.Timeout);
                        Log.Information("Waiting up to {TimeoutSeconds} seconds for deployment job {JobName} to complete.",
                                        timeout.TotalSeconds,
                                        jobName
                                        );
                        if (!done.WaitOne(timeout))
                        {
                            using (jobPodWatch)
                                using (jobLogWatch)
                                {
                                    Log.Error("Timed out after waiting {TimeoutSeconds} seconds for deployment job {JobName} to complete.",
                                              timeout.TotalSeconds,
                                              jobName
                                              );

                                    return(ExitCodes.JobTimeout);
                                }
                        }

                        jobPodWatch?.Dispose();
                        jobLogWatch?.Dispose();

                        deploymentJob = await client.JobsV1().Get(jobName);

                        if (deploymentJob == null)
                        {
                            Log.Error("Cannot find deployment job {JobName} in namespace {KubeNamespace}.",
                                      deploymentJob.Metadata.Name,
                                      deploymentJob.Metadata.Namespace
                                      );

                            return(ExitCodes.UnexpectedError);
                        }

                        if (deploymentJob.Status.Failed > 0)
                        {
                            Log.Error("Deployment job {JobName} failed.",
                                      deploymentJob.Metadata.Name
                                      );
                            foreach (JobConditionV1 jobCondition in deploymentJob.Status.Conditions)
                            {
                                Log.Error("Deployment job {JobName} failed ({Reason}): {ErrorMessage}.",
                                          deploymentJob.Metadata.Name,
                                          jobCondition.Reason,
                                          jobCondition.Message
                                          );
                            }

                            return(ExitCodes.JobFailed);
                        }

                        if (deploymentJob.Status.Succeeded > 0)
                        {
                            Log.Information("Deployment job {JobName} completed successfully.",
                                            deploymentJob.Metadata.Name
                                            );
                        }
                    }

                Log.Information("Done.");

                return(ExitCodes.Success);
            }
            catch (HttpRequestException <StatusV1> kubeRequestError)
            {
                Log.Error(kubeRequestError, "A Kubernetes API request failed while deploying the remote node ({Reason}): {ErrorMessage}",
                          kubeRequestError.Response.Reason,
                          kubeRequestError.Response.Message
                          );

                return(ExitCodes.JobFailed);
            }
            catch (Exception unexpectedError)
            {
                Log.Error(unexpectedError, "An unexpected error occurred while deploying the remote node.");

                return(ExitCodes.UnexpectedError);
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }