Skip to content

Commit

Permalink
KUBESAW-187: Adjust ksctl adm restart command to use rollout-restart
Browse files Browse the repository at this point in the history
Signed-off-by: Feny Mehta <[email protected]>
  • Loading branch information
fbm3307 committed Sep 12, 2024
1 parent 43eb3db commit 2748b57
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 145 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lithammer/dedent v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
Expand Down
194 changes: 110 additions & 84 deletions pkg/cmd/adm/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,32 @@ package adm
import (
"context"
"fmt"
"time"
"os"

"github.com/kubesaw/ksctl/pkg/client"
"github.com/kubesaw/ksctl/pkg/cmd/flags"
"github.com/kubesaw/ksctl/pkg/configuration"
clicontext "github.com/kubesaw/ksctl/pkg/context"
"github.com/kubesaw/ksctl/pkg/ioutils"

"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
kubectlrollout "k8s.io/kubectl/pkg/cmd/rollout"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
)

// NewRestartCmd() is a function to restart the whole operator, it relies on the target cluster and fetches the cluster config
// 1. If the command is run for host operator, it restart the whole host operator.(it deletes olm based pods(host-operator pods),
// waits for the new deployment to come up, then uses rollout-restart command for non-olm based - registration-service)
// 2. If the command is run for member operator, it restart the whole member operator.(it deletes olm based pods(member-operator pods),
// waits for the new deployment to come up, then uses rollout-restart command for non-olm based deployments - webhooks)
func NewRestartCmd() *cobra.Command {
var targetCluster string
command := &cobra.Command{
Use: "restart -t <cluster-name> <deployment-name>",
Use: "restart -t <cluster-name> <host|member-1|member-2>",
Short: "Restarts a deployment",
Long: `Restarts the deployment with the given name in the operator namespace.
If no deployment name is provided, then it lists all existing deployments in the namespace.`,
Expand All @@ -38,120 +44,140 @@ If no deployment name is provided, then it lists all existing deployments in the
return command
}

func restart(ctx *clicontext.CommandContext, clusterName string, deployments ...string) error {
func restart(ctx *clicontext.CommandContext, clusterName string, operatorType ...string) error {
cfg, err := configuration.LoadClusterConfig(ctx, clusterName)
if err != nil {
return err
}
cl, err := ctx.NewClient(cfg.Token, cfg.ServerAPI)

if err != nil {
return err
}

if len(deployments) == 0 {
err := printExistingDeployments(ctx.Terminal, cl, cfg.OperatorNamespace)
if err != nil {
ctx.Terminal.Printlnf("\nERROR: Failed to list existing deployments\n :%s", err.Error())
}
return fmt.Errorf("at least one deployment name is required, include one or more of the above deployments to restart")
if len(operatorType) == 0 {
return fmt.Errorf("please mention one of the following operator names to restart: host | member-1 | member-2")
}
deploymentName := deployments[0]

if !ctx.AskForConfirmation(
ioutils.WithMessagef("restart the deployment '%s' in namespace '%s'", deploymentName, cfg.OperatorNamespace)) {
ioutils.WithMessagef("restart the '%s' operator in namespace '%s'", operatorType[0], cfg.OperatorNamespace)) {
return nil
}
return restartDeployment(ctx, cl, cfg.OperatorNamespace, deploymentName)

return restartDeployment(ctx, cl, cfg.OperatorNamespace)
}

func restartDeployment(ctx *clicontext.CommandContext, cl runtimeclient.Client, ns string, deploymentName string) error {
namespacedName := types.NamespacedName{
Namespace: ns,
Name: deploymentName,
func restartDeployment(ctx *clicontext.CommandContext, cl runtimeclient.Client, ns string) error {
olmDeploymentList, nonOlmDeploymentlist, err := getExistingDeployments(cl, ns)
if err != nil {
return err
}

originalReplicas, err := scaleToZero(cl, namespacedName)
if err != nil {
if apierrors.IsNotFound(err) {
ctx.Printlnf("\nERROR: The given deployment '%s' wasn't found.", deploymentName)
return printExistingDeployments(ctx, cl, ns)
if olmDeploymentList == nil {
return fmt.Errorf("OLM based deploymont not found in %s", ns)
}
for _, olmDeployment := range olmDeploymentList.Items {
if err := deletePods(ctx, cl, olmDeployment, ns); err != nil {
return err
}
return err
}
ctx.Println("The deployment was scaled to 0")
if err := scaleBack(ctx, cl, namespacedName, originalReplicas); err != nil {
ctx.Printlnf("Scaling the deployment '%s' in namespace '%s' back to '%d' replicas wasn't successful", originalReplicas)
ctx.Println("Please, try to contact administrators to scale the deployment back manually")
return err
if nonOlmDeploymentlist == nil {
return fmt.Errorf("non-OLM based deploymont not found in %s", ns)
}
for _, nonOlmDeployment := range nonOlmDeploymentlist.Items {
if err := restartNonOlmDeployments(ns, nonOlmDeployment); err != nil {
return err
}
//check the rollout status
if err := checkRolloutStatus(ns); err != nil {
return err
}
}

ctx.Printlnf("The deployment was scaled back to '%d'", originalReplicas)
return nil
}

func restartHostOperator(ctx *clicontext.CommandContext, hostClient runtimeclient.Client, hostNamespace string) error {
deployments := &appsv1.DeploymentList{}
if err := hostClient.List(context.TODO(), deployments,
runtimeclient.InNamespace(hostNamespace),
runtimeclient.MatchingLabels{"olm.owner.namespace": "toolchain-host-operator"}); err != nil {
func deletePods(ctx *clicontext.CommandContext, cl runtimeclient.Client, deployment appsv1.Deployment, ns string) error {
//get pods by label selector from the deployment
pods := corev1.PodList{}
selector, _ := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
if err := cl.List(ctx, &pods, runtimeclient.MatchingLabelsSelector{Selector: selector}); err != nil {
return err
}
if len(deployments.Items) != 1 {
return fmt.Errorf("there should be a single deployment matching the label olm.owner.namespace=toolchain-host-operator in %s ns, but %d was found. "+
"It's not possible to restart the Host Operator deployment", hostNamespace, len(deployments.Items))
//delete pods
for _, pod := range pods.Items {
if err := cl.Delete(ctx, &pod); err != nil {

Check failure on line 108 in pkg/cmd/adm/restart.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint

G601: Implicit memory aliasing in for loop. (gosec)
return err
}
}

//check the rollout status
if err := checkRolloutStatus(ns); err != nil {
return err
}
return nil

return restartDeployment(ctx, hostClient, hostNamespace, deployments.Items[0].Name)
}

func printExistingDeployments(term ioutils.Terminal, cl runtimeclient.Client, ns string) error {
deployments := &appsv1.DeploymentList{}
if err := cl.List(context.TODO(), deployments, runtimeclient.InNamespace(ns)); err != nil {
return err
func restartNonOlmDeployments(ns string, deployment appsv1.Deployment) error {
kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
hFactory := cmdutil.NewFactory(cmdutil.NewMatchVersionFlags(kubeConfigFlags))
ioStreams := genericclioptions.IOStreams{
In: nil, // Not to forward the Standard Input
Out: os.Stdout,
ErrOut: os.Stderr,
}
deploymentList := "\n"
for _, deployment := range deployments.Items {
deploymentList += fmt.Sprintf("%s\n", deployment.Name)

o := kubectlrollout.NewRolloutRestartOptions(ioStreams)

if err := o.Complete(hFactory, nil, []string{"deployments"}); err != nil {
panic(err)
}
term.PrintContextSeparatorWithBodyf(deploymentList, "Existing deployments in %s namespace", ns)
return nil
o.Namespace = ns
o.Resources = []string{"deployment/" + deployment.Name}

if err := o.Validate(); err != nil {
panic(err)
}
return o.RunRestart()
}

func scaleToZero(cl runtimeclient.Client, namespacedName types.NamespacedName) (int32, error) {
// get the deployment
deployment := &appsv1.Deployment{}
if err := cl.Get(context.TODO(), namespacedName, deployment); err != nil {
return 0, err
func checkRolloutStatus(ns string) error {
kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
Factory := cmdutil.NewFactory(cmdutil.NewMatchVersionFlags(kubeConfigFlags))
ioStreams := genericclioptions.IOStreams{
In: nil, // Not to forward the Standard Input
Out: os.Stdout,
ErrOut: os.Stderr,
}
// keep original number of replicas so we can bring it back
originalReplicas := *deployment.Spec.Replicas
zero := int32(0)
deployment.Spec.Replicas = &zero

// update the deployment so it scales to zero
return originalReplicas, cl.Update(context.TODO(), deployment)
cmd := kubectlrollout.NewRolloutStatusOptions(ioStreams)

if err := cmd.Complete(Factory, []string{"deployment"}); err != nil {
panic(err)
}
cmd.LabelSelector = "provider=codeready-toolchain"
cmd.Namespace = ns
if err := cmd.Validate(); err != nil {
panic(err)
}
return cmd.Run()
}

func scaleBack(term ioutils.Terminal, cl runtimeclient.Client, namespacedName types.NamespacedName, originalReplicas int32) error {
return wait.Poll(500*time.Millisecond, 10*time.Second, func() (done bool, err error) {
term.Println("")
term.Printlnf("Trying to scale the deployment back to '%d'", originalReplicas)
// get the updated
deployment := &appsv1.Deployment{}
if err := cl.Get(context.TODO(), namespacedName, deployment); err != nil {
return false, err
}
// check if the replicas number wasn't already reset by a controller
if *deployment.Spec.Replicas == originalReplicas {
return true, nil
}
// set the original
deployment.Spec.Replicas = &originalReplicas
// and update to scale back
if err := cl.Update(context.TODO(), deployment); err != nil {
term.Printlnf("error updating Deployment '%s': %s. Will retry again...", namespacedName.Name, err.Error())
return false, nil
}
return true, nil
})
func getExistingDeployments(cl runtimeclient.Client, ns string) (*appsv1.DeploymentList, *appsv1.DeploymentList, error) {

olmDeployments := &appsv1.DeploymentList{}
if err := cl.List(context.TODO(), olmDeployments,
runtimeclient.InNamespace(ns),
runtimeclient.MatchingLabels{"olm.owner.kind": "ClusterServiceVersion"}); err != nil {
return nil, nil, err
}

nonOlmDeployments := &appsv1.DeploymentList{}
if err := cl.List(context.TODO(), nonOlmDeployments,
runtimeclient.InNamespace(ns),
runtimeclient.MatchingLabels{"provider": "codeready-toolchain"}); err != nil {
return nil, nil, err
}

return olmDeployments, nonOlmDeployments, nil
}
Loading

0 comments on commit 2748b57

Please sign in to comment.