Skip to content

Commit

Permalink
Feat/optional pod restart (#16)
Browse files Browse the repository at this point in the history
* feat: Add optional pod restart if specified in CR
- Pods now restart if spec.restartPods exists in the CR
- Specify k/v labels under spec.restartPods.labels to trigger a pod delete -
after a new access token is created
  • Loading branch information
samirtahir91 authored Mar 31, 2024
1 parent 2c4875a commit eed3f40
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 18 deletions.
12 changes: 9 additions & 3 deletions api/v1/githubapp_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import (

// GithubAppSpec defines the desired state of GithubApp
type GithubAppSpec struct {
AppId int `json:"appId"`
InstallId int `json:"installId"`
PrivateKeySecret string `json:"privateKeySecret"`
AppId int `json:"appId"`
InstallId int `json:"installId"`
PrivateKeySecret string `json:"privateKeySecret"`
RestartPods *RestartPodsSpec `json:"restartPods,omitempty"`
}

// GithubAppStatus defines the observed state of GithubApp
Expand All @@ -45,6 +46,11 @@ type GithubApp struct {
Status GithubAppStatus `json:"status,omitempty"`
}

// RestartPodsSpec defines the specification for restarting pods
type RestartPodsSpec struct {
Labels map[string]string `json:"labels,omitempty"`
}

//+kubebuilder:object:root=true

// GithubAppList contains a list of GithubApp
Expand Down
29 changes: 28 additions & 1 deletion api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions config/crd/bases/githubapp.samir.io_githubapps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ spec:
type: integer
privateKeySecret:
type: string
restartPods:
description: RestartPodsSpec defines the specification for restarting
pods
properties:
labels:
additionalProperties:
type: string
type: object
type: object
required:
- appId
- installId
Expand Down
77 changes: 63 additions & 14 deletions internal/controller/githubapp_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder" // Required for Watching
Expand All @@ -50,7 +51,7 @@ var (
defaultTimeBeforeExpiry = 15 * time.Minute // Default time before expiry
reconcileInterval time.Duration // Requeue interval (from env var)
timeBeforeExpiry time.Duration // Expiry threshold (from env var)
gitUsername = "not-used"
gitUsername = "not-used"
)

//+kubebuilder:rbac:groups=githubapp.samir.io,resources=githubapps,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -104,7 +105,7 @@ func (r *GithubAppReconciler) checkExpiryAndUpdateAccessToken(ctx context.Contex

// If expiresAt status field is not present or expiry time has already passed, generate or renew access token
if expiresAt.IsZero() || expiresAt.Before(time.Now()) {
return r.generateOrUpdateAccessToken(ctx, githubApp)
return r.generateOrUpdateAccessToken(ctx, githubApp, req)
}

// Check if the access token secret exists if not reconcile immediately
Expand All @@ -116,7 +117,7 @@ func (r *GithubAppReconciler) checkExpiryAndUpdateAccessToken(ctx context.Contex
if err := r.Get(ctx, accessTokenSecretKey, accessTokenSecret); err != nil {
if apierrors.IsNotFound(err) {
// Secret doesn't exist, reconcile straight away
return r.generateOrUpdateAccessToken(ctx, githubApp)
return r.generateOrUpdateAccessToken(ctx, githubApp, req)
}
// Error other than NotFound, return error
return err
Expand All @@ -125,7 +126,7 @@ func (r *GithubAppReconciler) checkExpiryAndUpdateAccessToken(ctx context.Contex
for key := range accessTokenSecret.Data {
if key != "token" && key != "username" {
log.Log.Info("Removing invalid key in access token secret", "Key", key)
return r.generateOrUpdateAccessToken(ctx, githubApp)
return r.generateOrUpdateAccessToken(ctx, githubApp, req)
}
}

Expand All @@ -136,7 +137,7 @@ func (r *GithubAppReconciler) checkExpiryAndUpdateAccessToken(ctx context.Contex
// Check if the access token is a valid github token via gh api auth
if !isAccessTokenValid(ctx, username, accessToken, req) {
// If accessToken is invalid, generate or update access token
return r.generateOrUpdateAccessToken(ctx, githubApp)
return r.generateOrUpdateAccessToken(ctx, githubApp, req)
}

// Access token exists, calculate the duration until expiry
Expand All @@ -149,7 +150,7 @@ func (r *GithubAppReconciler) checkExpiryAndUpdateAccessToken(ctx context.Contex
"GithubApp", req.Name,
"Namespace", req.Namespace,
)
err := r.generateOrUpdateAccessToken(ctx, githubApp)
err := r.generateOrUpdateAccessToken(ctx, githubApp, req)
return err
}

Expand Down Expand Up @@ -245,7 +246,7 @@ func (r *GithubAppReconciler) checkExpiryAndRequeue(ctx context.Context, githubA
}

// Function to generate or update access token
func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, githubApp *githubappv1.GithubApp) error {
func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, githubApp *githubappv1.GithubApp, req ctrl.Request) error {
l := log.FromContext(ctx)

// Get the private key from the Secret
Expand Down Expand Up @@ -283,8 +284,8 @@ func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, g
Namespace: githubApp.Namespace,
},
StringData: map[string]string{
"token": accessToken,
"username": gitUsername, // username is ignored in github auth but required
"token": accessToken,
"username": gitUsername, // username is ignored in github auth but required
},
}
accessTokenSecretKey := client.ObjectKey{
Expand Down Expand Up @@ -316,6 +317,10 @@ func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, g
if err := updateGithubAppStatusWithRetry(ctx, r, githubApp, expiresAt, 10); err != nil {
return fmt.Errorf("Failed after creating secret: %v", err)
}
// Restart the pods is required
if err := r.restartPods(ctx, githubApp, req); err != nil {
return fmt.Errorf("Failed to restart pods after after creating secret: %v", err)
}
return nil
}
l.Error(
Expand All @@ -338,7 +343,7 @@ func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, g
delete(existingSecret.Data, k)
}
existingSecret.StringData = map[string]string{
"token": accessToken,
"token": accessToken,
"username": gitUsername,
}
if err := r.Update(ctx, existingSecret); err != nil {
Expand All @@ -350,6 +355,10 @@ func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, g
if err := updateGithubAppStatusWithRetry(ctx, r, githubApp, expiresAt, 10); err != nil {
return fmt.Errorf("Failed after updating secret: %v", err)
}
// Restart the pods is required
if err := r.restartPods(ctx, githubApp, req); err != nil {
return fmt.Errorf("Failed to restart pods after updating secret: %v", err)
}

log.Log.Info("Access token updated in the existing Secret successfully")
return nil
Expand Down Expand Up @@ -440,6 +449,46 @@ func generateAccessToken(appID int, installationID int, privateKey []byte) (stri
return accessToken, metav1.NewTime(expiresAt), nil
}

// Function to bounce pods in the with matching labels if restartPods in GithubApp (in the same namespace)
func (r *GithubAppReconciler) restartPods(ctx context.Context, githubApp *githubappv1.GithubApp, req ctrl.Request) error {
// Check if restartPods field is defined
if githubApp.Spec.RestartPods == nil || len(githubApp.Spec.RestartPods.Labels) == 0 {
// No action needed if restartPods is not defined or no labels are specified
return nil
}

// Loop through each label specified in restartPods.labels and restart pods matching each label
for key, value := range githubApp.Spec.RestartPods.Labels {
// Create a list options with label selector
listOptions := &client.ListOptions{
Namespace: githubApp.Namespace,
LabelSelector: labels.SelectorFromSet(map[string]string{key: value}),
}

// List pods with the label selector
podList := &corev1.PodList{}
if err := r.List(ctx, podList, listOptions); err != nil {
return fmt.Errorf("failed to list pods with label %s=%s: %v", key, value, err)
}

// Restart each pod by deleting it
for _, pod := range podList.Items {
// Set deletion timestamp on the pod
if err := r.Delete(ctx, &pod); err != nil {
return fmt.Errorf("failed to delete pod %s/%s: %v", pod.Namespace, pod.Name, err)
}
// Log pod deletion
log.Log.Info(
"Pod marked for deletion to refresh secret",
"GithubApp", req.Name,
"Namespace", pod.Namespace,
"Name", pod.Name,
)
}
}
return nil
}

// Define a predicate function to filter create events for access token secrets
func accessTokenSecretPredicate() predicate.Predicate {
return predicate.Funcs{
Expand All @@ -451,10 +500,10 @@ func accessTokenSecretPredicate() predicate.Predicate {
}

/*
Define a predicate function to filter events for GithubApp objects
Check if the status field in ObjectOld is unset
Check if ExpiresAt is valid in the new GithubApp
Ignore status update event for GithubApp
Define a predicate function to filter events for GithubApp objects
Check if the status field in ObjectOld is unset
Check if ExpiresAt is valid in the new GithubApp
Ignore status update event for GithubApp
*/
func githubAppPredicate() predicate.Predicate {
return predicate.Funcs{
Expand Down

0 comments on commit eed3f40

Please sign in to comment.