예제 #1
0
        /// <summary>
        ///     Request creation of a <see cref="ReplicaSetV1"/>.
        /// </summary>
        /// <param name="newReplicaSet">
        ///     A <see cref="ReplicaSetV1"/> representing the ReplicaSet to create.
        /// </param>
        /// <param name="cancellationToken">
        ///     An optional <see cref="CancellationToken"/> that can be used to cancel the request.
        /// </param>
        /// <returns>
        ///     A <see cref="ReplicaSetV1"/> representing the current state for the newly-created ReplicaSet.
        /// </returns>
        public async Task <ReplicaSetV1> Create(ReplicaSetV1 newReplicaSet, CancellationToken cancellationToken = default)
        {
            if (newReplicaSet == null)
            {
                throw new ArgumentNullException(nameof(newReplicaSet));
            }

            return(await Http
                   .PostAsJsonAsync(
                       Requests.Collection.WithTemplateParameters(new
            {
                Namespace = newReplicaSet?.Metadata?.Namespace ?? KubeClient.DefaultNamespace
            }),
                       postBody : newReplicaSet,
                       cancellationToken : cancellationToken
                       )
                   .ReadContentAsObjectV1Async <ReplicaSetV1>(
                       operationDescription: $"create v1/ReplicaSet in namespace '{newReplicaSet?.Metadata?.Namespace ?? KubeClient.DefaultNamespace}'"
                       ));
        }
예제 #2
0
        /// <summary>
        ///     Find the ReplicaSet that corresponds to the specified revision of the specified Deployment.
        /// </summary>
        /// <param name="client">
        ///     The Kubernetes API client.
        /// </param>
        /// <param name="deployment">
        ///     The target Deployment.
        /// </param>
        /// <param name="targetRevision">
        ///     The target revision.
        /// </param>
        /// <param name="cancellationToken">
        ///     An optional <see cref="CancellationToken"/> that can be used to cancel the request.
        /// </param>
        /// <returns>
        ///     A <see cref="ReplicaSetV1"/> representing the ReplicaSet's current state; <c>null</c>, if no corresponding ReplicaSet was found.
        /// </returns>
        public static async Task <ReplicaSetV1> FindReplicaSetForRevision(IKubeApiClient client, DeploymentV1 deployment, int targetRevision, CancellationToken cancellationToken = default)
        {
            if (client == null)
            {
                throw new ArgumentNullException(nameof(client));
            }

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

            string matchLabelSelector = deployment.GetLabelSelector();

            ReplicaSetListV1 replicaSets = await client.ReplicaSetsV1().List(matchLabelSelector, deployment.Metadata.Namespace, cancellationToken);

            ReplicaSetV1 targetRevisionReplicaSet = replicaSets.Items.FirstOrDefault(
                replicaSet => replicaSet.GetRevision() == targetRevision
                );

            return(targetRevisionReplicaSet);
        }
예제 #3
0
        /// <summary>
        ///     Determine whether a Deployment owns a ReplicaSet.
        /// </summary>
        /// <param name="deployment">
        ///     The Deployment to examine.
        /// </param>
        /// <param name="replicaSet">
        ///     The ReplicaSet to examine.
        /// </param>
        /// <returns>
        ///     <c>true</c>, if the ReplicaSet has an owner-reference to the Deployment; otherwise, <c>false</c>.
        /// </returns>
        static bool DoesDeploymentOwnReplicaSet(DeploymentV1 deployment, ReplicaSetV1 replicaSet)
        {
            if (deployment == null)
            {
                throw new ArgumentNullException(nameof(deployment));
            }

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

            // Sanity-check: does the target ReplicaSet actually represent a revision of the existing Deployment?
            bool isReplicaSetForDeployment = replicaSet.Metadata.OwnerReferences.Any(ownerReference =>
                                                                                     ownerReference.Kind == deployment.Kind
                                                                                     &&
                                                                                     ownerReference.ApiVersion == deployment.ApiVersion
                                                                                     &&
                                                                                     ownerReference.Name == deployment.Metadata.Name
                                                                                     );

            return(isReplicaSetForDeployment);
        }
예제 #4
0
        /// <summary>
        ///     Find the ReplicaSet that corresponds to the specified revision of the specified Deployment.
        /// </summary>
        /// <param name="client">
        ///     The Kubernetes API client.
        /// </param>
        /// <param name="deployment">
        ///     The target Deployment.
        /// </param>
        /// <param name="targetRevision">
        ///     The target revision.
        /// </param>
        /// <returns>
        ///     A <see cref="ReplicaSetV1"/> representing the ReplicaSet's current state; <c>null</c>, if no corresponding ReplicaSet was found.
        /// </returns>
        static async Task <ReplicaSetV1> FindReplicaSetForRevision(IKubeApiClient client, DeploymentV1 deployment, int targetRevision)
        {
            if (client == null)
            {
                throw new ArgumentNullException(nameof(client));
            }

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

            ReplicaSetListV1 replicaSets = await client.ReplicaSetsV1().List(
                labelSelector: $"app={deployment.Metadata.Name}",
                kubeNamespace: deployment.Metadata.Namespace
                );

            ReplicaSetV1 targetRevisionReplicaSet = replicaSets.Items.FirstOrDefault(
                replicaSet => replicaSet.GetRevision() == targetRevision
                );

            return(targetRevisionReplicaSet);
        }
예제 #5
0
        /// <summary>
        ///     Roll back a Deployment to the revision represented by the specified ReplicaSet.
        /// </summary>
        /// <param name="client">
        ///     The Kubernetes API client.
        /// </param>
        /// <param name="existingDeployment">
        ///     The target Deployment.
        /// </param>
        /// <param name="targetRevisionReplicaSet">
        ///     The ReplicaSet that represents the target revision.
        /// </param>
        /// <returns>
        ///     A <see cref="DeploymentV1"/> representing the Deployment's current state.
        /// </returns>
        static async Task <DeploymentV1> RollbackDeployment(IKubeApiClient client, DeploymentV1 existingDeployment, ReplicaSetV1 targetRevisionReplicaSet)
        {
            if (client == null)
            {
                throw new ArgumentNullException(nameof(client));
            }

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

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

            if (!DoesDeploymentOwnReplicaSet(existingDeployment, targetRevisionReplicaSet))
            {
                throw new InvalidOperationException($"ReplicaSet '{targetRevisionReplicaSet.Metadata.Name}' in namespace '{targetRevisionReplicaSet.Metadata.Namespace}' is not owned by Deployment '{existingDeployment.Metadata.Name}'.");
            }

            int?targetRevision = targetRevisionReplicaSet.GetRevision();

            if (targetRevision == null)
            {
                throw new InvalidOperationException($"Cannot determine Deployment revision represented by ReplicaSet '{targetRevisionReplicaSet.Metadata.Name}' in namespace '{targetRevisionReplicaSet.Metadata.Namespace}'.");
            }

            DeploymentV1 rolledBackDeployment = await client.DeploymentsV1().Update(existingDeployment.Metadata.Name, kubeNamespace: existingDeployment.Metadata.Namespace, patchAction: patch =>
            {
                patch.Replace(deployment =>
                              deployment.Spec.Template.Spec,
                              value: targetRevisionReplicaSet.Spec.Template.Spec
                              );

                // Since the Rollback API is now obsolete, we have to update the Deployment's revision by hand.
                Dictionary <string, string> annotationsWithModifiedRevision         = existingDeployment.Metadata.Annotations;
                annotationsWithModifiedRevision[K8sAnnotations.Deployment.Revision] = targetRevision.Value.ToString();
                patch.Replace(deployment =>
                              deployment.Metadata.Annotations,
                              value: annotationsWithModifiedRevision
                              );
            });

            // Re-fetch Deployment state so we pick up annotations added or updated by the controller.
            rolledBackDeployment = await client.DeploymentsV1().Get(rolledBackDeployment.Metadata.Name, rolledBackDeployment.Metadata.Namespace);

            return(rolledBackDeployment);
        }
예제 #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)
        {
            ProgramOptions options = ProgramOptions.Parse(commandLineArguments);

            if (options == null)
            {
                return(ExitCodes.InvalidArguments);
            }

            ILoggerFactory loggerFactory = ConfigureLogging(options);

            try
            {
                KubeClientOptions clientOptions = K8sConfig.Load().ToKubeClientOptions(defaultKubeNamespace: options.KubeNamespace);
                if (options.Verbose)
                {
                    clientOptions.LogPayloads = true;
                }

                KubeApiClient client = KubeApiClient.Create(clientOptions, loggerFactory);

                Log.Information("Looking for existing Deployment {DeploymentName} in namespace {KubeNamespace}...",
                                options.DeploymentName,
                                options.KubeNamespace
                                );

                DeploymentV1 existingDeployment = await client.DeploymentsV1().Get(options.DeploymentName, options.KubeNamespace);

                if (existingDeployment != null)
                {
                    Log.Error("Cannot continue - deployment  {DeploymentName} in namespace {KubeNamespace} already exists.",
                              options.DeploymentName,
                              options.KubeNamespace
                              );

                    return(ExitCodes.AlreadyExists);
                }

                Log.Information("Ok, Deployment does not exist yet - we're ready to go.");

                Log.Information("Creating Deployment {DeploymentName} in namespace {KubeNamespace}...",
                                options.DeploymentName,
                                options.KubeNamespace
                                );
                DeploymentV1 initialDeployment = await CreateInitialDeployment(client, options.DeploymentName, options.KubeNamespace);

                int?initialRevision = initialDeployment.GetRevision();
                if (initialRevision == null)
                {
                    Log.Error("Unable to determine initial revision of Deployment {DeploymentName} in namespace {KubeNamespace} (missing annotation).",
                              options.DeploymentName,
                              options.KubeNamespace
                              );

                    return(ExitCodes.UnexpectedError);
                }
                Log.Information("Created Deployment {DeploymentName} in namespace {KubeNamespace} (revision {DeploymentRevision}).",
                                options.DeploymentName,
                                options.KubeNamespace,
                                initialRevision
                                );

                Log.Information("Updating Deployment {DeploymentName} in namespace {KubeNamespace}...",
                                options.DeploymentName,
                                options.KubeNamespace
                                );
                DeploymentV1 updatedDeployment = await UpdateDeployment(client, initialDeployment);

                int?updatedRevision = updatedDeployment.GetRevision();
                if (updatedRevision == null)
                {
                    Log.Error("Unable to determine updated revision of Deployment {DeploymentName} in namespace {KubeNamespace} (missing annotation).",
                              options.DeploymentName,
                              options.KubeNamespace
                              );

                    return(ExitCodes.UnexpectedError);
                }
                Log.Information("Updated Deployment {DeploymentName} in namespace {KubeNamespace} (revision {DeploymentRevision}).",
                                options.DeploymentName,
                                options.KubeNamespace,
                                updatedRevision
                                );

                Log.Information("Searching for ReplicaSet that corresponds to revision {Revision} of {DeploymentName} in namespace {KubeNamespace}...",
                                options.DeploymentName,
                                options.KubeNamespace,
                                initialRevision
                                );
                ReplicaSetV1 targetReplicaSet = await FindReplicaSetForRevision(client, updatedDeployment, initialRevision.Value);

                if (targetReplicaSet == null)
                {
                    Log.Error("Cannot find ReplicaSet that corresponds to revision {Revision} of {DeploymentName} in namespace {KubeNamespace}...",
                              options.DeploymentName,
                              options.KubeNamespace,
                              initialRevision
                              );

                    return(ExitCodes.NotFound);
                }
                Log.Information("Found ReplicaSet {ReplicaSetName} in namespace {KubeNamespace}.",
                                targetReplicaSet.Metadata.Name,
                                targetReplicaSet.Metadata.Namespace
                                );

                Log.Information("Rolling Deployment {DeploymentName} in namespace {KubeNamespace} back to initial revision {DeploymentRevision}...",
                                options.DeploymentName,
                                options.KubeNamespace,
                                initialRevision
                                );
                DeploymentV1 rolledBackDeployment = await RollbackDeployment(client, updatedDeployment, targetReplicaSet);

                Log.Information("Rollback initiated for Deployment {DeploymentName} in namespace {KubeNamespace} from revision {FromRevision} to {ToRevision} (new revision will be {NewRevision})...",
                                options.DeploymentName,
                                options.KubeNamespace,
                                updatedRevision,
                                initialRevision,
                                rolledBackDeployment.GetRevision()
                                );

                return(ExitCodes.Success);
            }
            catch (HttpRequestException <StatusV1> kubeError)
            {
                Log.Error(kubeError, "Kubernetes API error: {@Status}", kubeError.Response);

                return(ExitCodes.UnexpectedError);
            }
            catch (Exception unexpectedError)
            {
                Log.Error(unexpectedError, "Unexpected error.");

                return(ExitCodes.UnexpectedError);
            }
        }
예제 #7
0
        /// <summary>
        ///     Roll back a Deployment to the revision represented by the specified ReplicaSet.
        /// </summary>
        /// <param name="client">
        ///     The Kubernetes API client.
        /// </param>
        /// <param name="existingDeployment">
        ///     The target Deployment.
        /// </param>
        /// <param name="targetRevisionReplicaSet">
        ///     The ReplicaSet that represents the target revision.
        /// </param>
        /// <param name="cancellationToken">
        ///     An optional <see cref="CancellationToken"/> that can be used to cancel the request.
        /// </param>
        /// <returns>
        ///     A <see cref="DeploymentV1"/> representing the Deployment's current state.
        /// </returns>
        public static async Task <DeploymentV1> RollbackDeployment(IKubeApiClient client, DeploymentV1 existingDeployment, ReplicaSetV1 targetRevisionReplicaSet, CancellationToken cancellationToken = default)
        {
            if (client == null)
            {
                throw new ArgumentNullException(nameof(client));
            }

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

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

            int?targetRevision = targetRevisionReplicaSet.GetRevision();

            if (targetRevision == null)
            {
                throw new InvalidOperationException($"Cannot determine Deployment revision represented by ReplicaSet '{targetRevisionReplicaSet.Metadata.Name}' in namespace '{targetRevisionReplicaSet.Metadata.Namespace}'.");
            }

            DeploymentV1 rolledBackDeployment = await client.DeploymentsV1().Update(existingDeployment.Metadata.Name, kubeNamespace: existingDeployment.Metadata.Namespace, cancellationToken: cancellationToken, patchAction: patch =>
            {
                // Restore Deployment's Pod-template specification to the one used by the target ReplicaSet.
                patch.Replace(deployment =>
                              deployment.Spec.Template.Spec,
                              value: targetRevisionReplicaSet.Spec.Template.Spec
                              );

                // Since the old Rollback API is obsolete (as of v1beta2), we have to update the Deployment's revision by hand.
                patch.Replace(deployment =>
                              deployment.Metadata.Annotations, // Due to JSON-PATCH limitations in the K8s API, we have to replace the entire Annotations property, not attempt to update individual items within the dictionary.
                              value: new Dictionary <string, string>(existingDeployment.Metadata.Annotations)
                {
                    [K8sAnnotations.Deployment.Revision] = targetRevision.Value.ToString()
                }
                              );
            });

            // Re-fetch Deployment state so we pick up annotations added or updated by the controller.
            rolledBackDeployment = await client.DeploymentsV1().Get(rolledBackDeployment.Metadata.Name, rolledBackDeployment.Metadata.Namespace, cancellationToken);

            return(rolledBackDeployment);
        }