package befehle import ( "context" "encoding/json" "errors" "fmt" "strings" "time" "github.com/spf13/cobra" "git.wilde-server.de/florian.wilde/proxdeploy/cli/internal/api" ) func eineID(befehl string) cobra.PositionalArgs { return func(_ *cobra.Command, argumente []string) error { if len(argumente) != 1 { return bedienfehler("Auftrags-ID fehlt: proxdeploy %s ", befehl) } return nil } } func (u *umgebung) statusBefehl() *cobra.Command { var folgen bool befehl := &cobra.Command{ Use: "status ", Short: "Stand eines Auftrags (mit --follow live bis zum Ende)", Args: eineID("status"), RunE: func(cmd *cobra.Command, argumente []string) error { c, _, err := u.client() if err != nil { return err } if folgen { _, err := u.folgen(cmd.Context(), c, argumente[0]) return err } a, err := c.Auftrag(cmd.Context(), argumente[0]) if err != nil { return err } fertig := endzustaende[a.Status] if u.opt.json { if err := u.jsonAusgeben(a); err != nil { return err } } else { d := u.neuerDarsteller() d.live = true // einmal vollständig zeichnen, ohne Neuzeichnen d.zeichnen(a, fertig) } if fertig && a.Status != api.AuftragStatusERFOLGREICH { return &exitFehler{code: ExitFehlschlag, gemeldet: true} } return nil }, } befehl.Flags().BoolVarP(&folgen, "follow", "f", false, "live verfolgen bis zum Ende") return befehl } func (u *umgebung) logsBefehl() *cobra.Command { var folgen bool befehl := &cobra.Command{ Use: "logs ", Short: "Detail-Log eines Auftrags (mit --follow live)", Args: eineID("logs"), RunE: func(cmd *cobra.Command, argumente []string) error { c, _, err := u.client() if err != nil { return err } id := argumente[0] zeilen, err := c.Log(cmd.Context(), id, "") if err != nil { return err } for _, z := range zeilen { u.logZeile(z) } if !folgen { return nil } letzte := "" if len(zeilen) > 0 { letzte = zeilen[len(zeilen)-1].Id } return u.logFolgen(cmd.Context(), c, id, letzte) }, } befehl.Flags().BoolVarP(&folgen, "follow", "f", false, "neue Zeilen live anhängen, bis der Auftrag fertig ist") return befehl } func (u *umgebung) logZeile(z api.LogZeile) { if u.opt.json { daten, _ := json.Marshal(z) fmt.Fprintln(u.aus, string(daten)) return } s := u.stil() zeit := z.Zeit if t, err := time.Parse(time.RFC3339Nano, z.Zeit); err == nil { zeit = t.Local().Format("15:04:05") } phase := "" if z.Phase != nil { phase = *z.Phase } nachricht := z.Nachricht switch z.Stufe { case "error": nachricht = s.Rot(nachricht) case "warn": nachricht = s.Amber(nachricht) default: nachricht = s.Paper(nachricht) } fmt.Fprintf(u.aus, "%s %s %s\n", s.Muted(zeit), s.Muted(fmt.Sprintf("%-16s", phase)), nachricht) } // logFolgen hängt neue Zeilen an (SSE-Ereignis `log`), bis der Auftrag fertig ist. func (u *umgebung) logFolgen(ctx context.Context, c *api.Client, id, ab string) error { for versuch := 0; versuch < 5; versuch++ { err := c.Ereignisse(ctx, id, ab, func(e api.Ereignis) error { switch e.Art { case "log": var zeilen []api.LogZeile if err := json.Unmarshal(e.Daten, &zeilen); err != nil { return err } for _, z := range zeilen { u.logZeile(z) ab = z.Id } case "ende": return api.Ende } return nil }) if errors.Is(err, api.Ende) || ctx.Err() != nil { return nil } var apiFehler *api.Fehler if errors.As(err, &apiFehler) { return err } time.Sleep(time.Duration(versuch+1) * time.Second) } return &api.VerbindungsFehler{Ursache: errors.New("Ereignisstrom bricht wiederholt ab")} } func (u *umgebung) cancelBefehl() *cobra.Command { return &cobra.Command{ Use: "cancel ", Short: "Auftrag abbrechen (hält an der nächsten Phasengrenze)", Args: eineID("cancel"), RunE: func(cmd *cobra.Command, argumente []string) error { c, _, err := u.client() if err != nil { return err } if err := c.Abbrechen(cmd.Context(), argumente[0]); err != nil { return err } if !u.opt.json { fmt.Fprintln(u.aus, u.stil().Amber("Abbruch angefordert – "+strings.TrimSpace(argumente[0])+" hält an der nächsten Phasengrenze.")) } return nil }, } }