Skip to content

Commit

Permalink
feat: Add events for deploy upgrade and access tokens (#34)
Browse files Browse the repository at this point in the history
- Up tests to check events
- Add appId and InstallId to CRD print collumns in kubectl get
  • Loading branch information
samirtahir91 authored Apr 8, 2024
1 parent 31bbe6b commit dbe08c7
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 21 deletions.
2 changes: 2 additions & 0 deletions api/v1/githubapp_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type GithubAppStatus struct {
//+kubebuilder:subresource:status

// GithubApp is the Schema for the githubapps API
// +kubebuilder:printcolumn:name="App ID",type=string,JSONPath=`.spec.appId`
// +kubebuilder:printcolumn:name="Install ID",type=string,JSONPath=`.spec.installId`
// +kubebuilder:printcolumn:name="Expires At",type=string,JSONPath=`.status.expiresAt`
// +kubebuilder:printcolumn:name="Error",type=string,JSONPath=`.status.error`
type GithubApp struct {
Expand Down
5 changes: 3 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ func main() {
}

if err = (&controller.GithubAppReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("githubapp-controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "GithubApp")
os.Exit(1)
Expand Down
6 changes: 6 additions & 0 deletions config/crd/bases/githubapp.samir.io_githubapps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.appId
name: App ID
type: string
- jsonPath: .spec.installId
name: Install ID
type: string
- jsonPath: .status.expiresAt
name: Expires At
type: string
Expand Down
7 changes: 7 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- githubapp.samir.io
resources:
Expand Down
55 changes: 50 additions & 5 deletions internal/controller/githubapp_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder" // Required for Watching
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -45,8 +46,9 @@ import (
// GithubAppReconciler reconciles a GithubApp object
type GithubAppReconciler struct {
client.Client
Scheme *runtime.Scheme
lock sync.Mutex
Scheme *runtime.Scheme
Recorder record.EventRecorder
lock sync.Mutex
}

var (
Expand All @@ -65,6 +67,7 @@ const (
//+kubebuilder:rbac:groups=githubapp.samir.io,resources=githubapps/finalizers,verbs=update
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;update;create;delete;watch;patch
//+kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;update;watch;patch
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch

// Reconcile function
func (r *GithubAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
Expand Down Expand Up @@ -114,6 +117,13 @@ func (r *GithubAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
if updateErr := r.updateStatusWithError(ctx, githubApp, err.Error()); updateErr != nil {
l.Error(updateErr, "failed to update status field 'Error'")
}
// Raise event
r.Recorder.Event(
githubApp,
"Warning",
"FailedRenewal",
fmt.Sprintf("Error: %s", err),
)
return ctrl.Result{}, err
}

Expand Down Expand Up @@ -389,12 +399,26 @@ func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, g
"Secret created for access token",
"Secret", accessTokenSecret,
)
// Raise event
r.Recorder.Event(
githubApp,
"Normal",
"Created",
fmt.Sprintf("Created access token secret %s/%s", githubApp.Namespace, accessTokenSecret),
)
// Update the status with the new expiresAt time
if err := updateGithubAppStatusWithRetry(ctx, r, githubApp, expiresAt, 10); err != nil {
return fmt.Errorf("failed after creating secret: %v", err)
}
// Restart the pods is required
// Rollout deployments if required
if err := r.rolloutDeployment(ctx, githubApp); err != nil {
// Raise event
r.Recorder.Event(
githubApp,
"Warning",
"FailedDeploymentUpgrade",
fmt.Sprintf("Error: %s", err),
)
return fmt.Errorf("failed to rollout deployment after after creating secret: %v", err)
}
return nil
Expand Down Expand Up @@ -433,10 +457,24 @@ func (r *GithubAppReconciler) generateOrUpdateAccessToken(ctx context.Context, g
}
// Restart the pods is required
if err := r.rolloutDeployment(ctx, githubApp); err != nil {
// Raise event
r.Recorder.Event(
githubApp,
"Warning",
"FailedDeploymentUpgrade",
fmt.Sprintf("Error: %s", err),
)
return fmt.Errorf("failed to rollout deployment after updating secret: %v", err)
}

l.Info("Access token updated in the existing Secret successfully")
// Raise event
r.Recorder.Event(
githubApp,
"Normal",
"Updated",
fmt.Sprintf("Updated access token secret %s/%s", githubApp.Namespace, accessTokenSecret),
)
return nil
}

Expand Down Expand Up @@ -534,7 +572,7 @@ func generateAccessToken(ctx context.Context, appID int, installationID int, pri
return accessToken, metav1.NewTime(expiresAt), nil
}

// Function to bounce pods as per `spec.rolloutDeployment.labels` in GithubApp (in the same namespace)
// Function to upgrade deployments as per `spec.rolloutDeployment.labels` in GithubApp (in the same namespace)
func (r *GithubAppReconciler) rolloutDeployment(ctx context.Context, githubApp *githubappv1.GithubApp) error {
l := log.FromContext(ctx)

Expand All @@ -544,7 +582,7 @@ func (r *GithubAppReconciler) rolloutDeployment(ctx context.Context, githubApp *
return nil
}

// Loop through each label specified in rolloutDeployment.labels and restart pods matching each label
// Loop through each label specified in rolloutDeployment.labels and update deployments matching each label
for key, value := range githubApp.Spec.RolloutDeployment.Labels {
// Create a list options with label selector
listOptions := &client.ListOptions{
Expand Down Expand Up @@ -582,6 +620,13 @@ func (r *GithubAppReconciler) rolloutDeployment(ctx context.Context, githubApp *
"Namespace",
deployment.Namespace,
)
// Raise event
r.Recorder.Event(
githubApp,
"Normal",
"Updated",
fmt.Sprintf("Updated deployment %s/%s", deployment.Namespace, deployment.Name),
)
}
}
return nil
Expand Down
40 changes: 40 additions & 0 deletions internal/controller/githubapp_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ var _ = Describe("GithubApp controller", func() {

By("Waiting for the access token secret to be created")
test_helpers.WaitForAccessTokenSecret(ctx, k8sClient, namespace1)

By("Waiting for the correct event to be recorded")
test_helpers.CheckEvent(
ctx,
k8sClient,
githubAppName,
namespace1,
"Normal",
"Created",
fmt.Sprintf("Created access token secret %s/github-app-access-token-", namespace1))
})
})

Expand Down Expand Up @@ -101,6 +111,16 @@ var _ = Describe("GithubApp controller", func() {
Expect(err).To(Succeed())
return string(updatedSecret.Data["token"])
}, "60s", "5s").ShouldNot(Equal(dummyAccessToken))

By("Waiting for the correct event to be recorded")
test_helpers.CheckEvent(
ctx,
k8sClient,
githubAppName,
namespace1,
"Normal",
"Updated",
fmt.Sprintf("Updated access token secret %s/github-app-access-token-", namespace1))
})
})

Expand Down Expand Up @@ -237,6 +257,16 @@ var _ = Describe("GithubApp controller", func() {
namespace4,
"privateKey not found in Secret",
)
By("Waiting for the correct event to be recorded")
test_helpers.CheckEvent(
ctx,
k8sClient,
githubAppName4,
namespace4,
"Warning",
"FailedRenewal",
"Error: privateKey not found in Secret",
)

// Delete the GitHubApp after reconciliation
test_helpers.DeleteGitHubAppAndWait(ctx, k8sClient, namespace4, githubAppName4)
Expand All @@ -261,6 +291,16 @@ var _ = Describe("GithubApp controller", func() {
namespace3,
"Secret \"gh-app-key-test\" not found",
)
By("Waiting for the correct event to be recorded")
test_helpers.CheckEvent(
ctx,
k8sClient,
githubAppName3,
namespace3,
"Warning",
"FailedRenewal",
"Error: Secret \"gh-app-key-test\" not found",
)
})
})

Expand Down
7 changes: 4 additions & 3 deletions internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ var _ = BeforeSuite(func() {
}

// set check interval and expiry threshold for test env
osEnvErr := os.Setenv("CHECK_INTERVAL", "5s")
osEnvErr := os.Setenv("CHECK_INTERVAL", "15s")
Expect(osEnvErr).NotTo(HaveOccurred())
osEnvErr = os.Setenv("EXPIRY_THRESHOLD", "15m")
Expect(osEnvErr).NotTo(HaveOccurred())
Expand All @@ -103,8 +103,9 @@ var _ = BeforeSuite(func() {
Expect(err).ToNot(HaveOccurred())

err = (&GithubAppReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
Recorder: k8sManager.GetEventRecorderFor("githubapp-controller"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())

Expand Down
60 changes: 49 additions & 11 deletions internal/controller/test_helpers/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"os"
"strconv"
"strings"

gomega "github.com/onsi/gomega"

Expand Down Expand Up @@ -35,16 +36,53 @@ var (

// Function to initialise vars for github app
func init() {
var err error
appId, err = strconv.Atoi(os.Getenv("GH_APP_ID"))
if err != nil {
panic(err)
}
installId, err = strconv.Atoi(os.Getenv("GH_INSTALL_ID"))
if err != nil {
panic(err)
}
acessTokenSecretName = fmt.Sprintf("github-app-access-token-%s", strconv.Itoa(appId))
var err error
appId, err = strconv.Atoi(os.Getenv("GH_APP_ID"))
if err != nil {
panic(err)
}
installId, err = strconv.Atoi(os.Getenv("GH_INSTALL_ID"))
if err != nil {
panic(err)
}
acessTokenSecretName = fmt.Sprintf("github-app-access-token-%s", strconv.Itoa(appId))
}

// Function to check and wait for an event on a GithubApp object
func CheckEvent(
ctx context.Context,
k8sClient client.Client,
githubAppName string,
namespace string,
eventType string,
reason string,
message string,
) {
listOptions := &client.ListOptions{
Namespace: namespace,
}

// Event not found, wait for it
gomega.Eventually(func() error {
// list events
eventList := &corev1.EventList{}
err := k8sClient.List(ctx, eventList, listOptions)
if err != nil {
return fmt.Errorf("failed to list events: %v", err)
}
// Check the event exists
for _, evt := range eventList.Items {
if evt.InvolvedObject.Name == githubAppName &&
evt.Type == eventType &&
evt.Reason == reason &&
strings.Contains(evt.Message, message) {
return nil // Event found
}
}

// Event not found yet
return fmt.Errorf("matching event not found")
}, "20s", "5s").Should(gomega.Succeed())
}

// Function to delete accessToken Secret
Expand Down Expand Up @@ -83,7 +121,7 @@ func DeleteGitHubAppAndWait(ctx context.Context, k8sClient client.Client, namesp
Name: name,
}, &githubappv1.GithubApp{})
return apierrors.IsNotFound(err) // GitHubApp is deleted
}, "60s", "5s").Should(gomega.BeTrue(), "Failed to delete GitHubApp within timeout")
}, "20s", "5s").Should(gomega.BeTrue(), "Failed to delete GitHubApp within timeout")
}

// Function to create a GitHubApp and wait for its creation
Expand Down

0 comments on commit dbe08c7

Please sign in to comment.