diff --git a/internal/controllers/app_controller.go b/internal/controllers/app_controller.go index f95ecbdf..8f89434f 100644 --- a/internal/controllers/app_controller.go +++ b/internal/controllers/app_controller.go @@ -243,6 +243,12 @@ type condition struct { Reason string } +type eventCondition struct { + Type string + Reason string + Message string +} + // workload contains the needed information for watchDeployEvents logic // deployments and statefulsets are both supported so it became necessary // to abstract their common properties into a separate type @@ -254,6 +260,7 @@ type workload struct { Generation int ObservedGeneration int Conditions []condition + Events []eventCondition } type workloadClient struct { @@ -284,6 +291,15 @@ func (cli workloadClient) Get(ctx context.Context) (*workload, error) { for _, c := range o.Status.Conditions { w.Conditions = append(w.Conditions, condition{Type: string(c.Type), Reason: c.Reason}) } + e, err := cli.k8sClient.CoreV1().Events(cli.workloadNamespace).List(ctx, metav1.ListOptions{FieldSelector: "involvedObject.name=" + o.Name, TypeMeta: metav1.TypeMeta{Kind: "Pod"}}) + if err != nil { + return nil, err + } + for _, e := range e.Items { + if e.FirstTimestamp == o.ObjectMeta.CreationTimestamp { + w.Events = append(w.Events, eventCondition{Type: e.Type, Reason: e.Reason, Message: e.Message}) + } + } return &w, nil case ketchv1.StatefulSetAppType: o, err := cli.k8sClient.AppsV1().StatefulSets(cli.workloadNamespace).Get(ctx, cli.workloadName, metav1.GetOptions{}) @@ -303,6 +319,15 @@ func (cli workloadClient) Get(ctx context.Context) (*workload, error) { for _, c := range o.Status.Conditions { w.Conditions = append(w.Conditions, condition{Type: string(c.Type), Reason: c.Reason}) } + e, err := cli.k8sClient.CoreV1().Events(cli.workloadNamespace).List(ctx, metav1.ListOptions{FieldSelector: "involvedObject.name=" + o.Name, TypeMeta: metav1.TypeMeta{Kind: "StatefulSet"}}) + if err != nil { + return nil, err + } + for _, e := range e.Items { + if e.FirstTimestamp == o.ObjectMeta.CreationTimestamp { + w.Events = append(w.Events, eventCondition{Type: e.Type, Reason: e.Reason, Message: e.Message}) + } + } return &w, nil } return nil, fmt.Errorf("unknown workload type") @@ -495,6 +520,9 @@ func (r *AppReconciler) watchDeployEvents(ctx context.Context, app *ketchv1.App, recorder.Eventf(app, v1.EventTypeWarning, ketchv1.AppReconcileError, "error getting deployments: %s", err.Error()) return err } + if err := checkWorkloadEvent(wl); err != nil { + return err + } select { case <-time.After(100 * time.Millisecond): case <-timeout: @@ -681,6 +709,15 @@ func isDeploymentEvent(msg watch.Event, name string) bool { return ok && strings.HasPrefix(evt.Name, name) } +func checkWorkloadEvent(wl *workload) error { + for _, e := range wl.Events { + if e.Type == "Warning" && e.Reason == "FailedCreate" { + return errors.New(e.Message) + } + } + return nil +} + // createDeployTimeoutError gets pods that are not status == ready aggregates and returns the pod phase errors func createDeployTimeoutError(ctx context.Context, cli kubernetes.Interface, app *ketchv1.App, timeout time.Duration, namespace, group, label string) error { var deploymentVersion int