Skip to content

fleet cli json output #3559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions cmd/fleetcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package main

import (
// Ensure GVKs are registered
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep this comment together with the blank imports.


"os"
"strings"

_ "github.com/rancher/fleet/pkg/generated/controllers/fleet.cattle.io"
_ "github.com/rancher/wrangler/v3/pkg/generated/controllers/apiextensions.k8s.io"
_ "github.com/rancher/wrangler/v3/pkg/generated/controllers/apps"
Expand All @@ -22,7 +26,16 @@ func main() {
ctx := signals.SetupSignalContext()
cmd := cmds.App()
if err := cmd.ExecuteContext(ctx); err != nil {
logrus.Fatal(err)
if strings.ToLower(os.Getenv("FLEET_JSON_OUTPUT")) == "true" {
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
// use a fleet specific field name so we are sure logs from other libraries
// are not considered.
log.WithFields(logrus.Fields{
"fleetErrorMessage": err.Error(),
}).Fatal("Fleet cli failed")
} else {
logrus.Fatal(err)
}
}

}
11 changes: 8 additions & 3 deletions integrationtests/gitjob/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,15 @@ var _ = Describe("GitJob controller", func() {
job.Status.StartTime = &metav1.Time{Time: time.Now().Add(-1 * time.Hour)}
job.Status.Conditions = []batchv1.JobCondition{
{
Type: "Failed",
// using Stalled because the Compute function uses Stalled
// for returning the condition message and it's simpler.
// For testing it in a different way we would need to setup a more complex
// scenario defining the job pods
// We are simulating job failures.
Type: "Stalled",
Status: "True",
Reason: "BackoffLimitExceeded",
Message: "Job has reached the specified backoff limit",
Message: `{"fleetErrorMessage":"fleet error message","level":"fatal","msg":"Fleet cli failed","time":"2025-04-15T14:53:15+02:00"}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beware that message in status conditions is by definition meant to be human-readable:

❯ kubectl explain job.status.conditions.message
GROUP:      batch
KIND:       Job
VERSION:    v1

FIELD: message <string>


DESCRIPTION:
    Human readable message indicating details about last transition.

},
{
Type: batchv1.JobFailureTarget,
Expand All @@ -566,7 +571,7 @@ var _ = Describe("GitJob controller", func() {
// check the conditions related to the job
// Failed.... Stalled=true and Reconcilling=false
g.Expect(checkCondition(&gitRepo, "Reconciling", corev1.ConditionFalse, "")).To(BeTrue())
g.Expect(checkCondition(&gitRepo, "Stalled", corev1.ConditionTrue, "Job Failed. failed: 1/1")).To(BeTrue())
g.Expect(checkCondition(&gitRepo, "Stalled", corev1.ConditionTrue, "fleet error message")).To(BeTrue())

// check the rest
g.Expect(checkCondition(&gitRepo, "GitPolling", corev1.ConditionTrue, "")).To(BeTrue())
Expand Down
68 changes: 66 additions & 2 deletions internal/cmd/controller/gitops/reconciler/gitjob_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,10 @@ func argsAndEnvs(
Name: "HOME",
Value: fleetHomeDir,
},
{
Name: "FLEET_JSON_OUTPUT",
Value: "true",
},
{
Name: fleetcli.FleetApplyConflictRetriesEnv,
Value: strconv.Itoa(fleetApplyRetries),
Expand Down Expand Up @@ -1278,7 +1282,13 @@ func (r *GitJobReconciler) newGitCloner(
args = append(args, "--ca-bundle-file", "/gitjob/cabundle/"+bundleCAFile)
}

env := proxyEnvVars()
env := []corev1.EnvVar{
{
Name: "FLEET_JSON_OUTPUT",
Value: "true",
},
}
env = append(env, proxyEnvVars()...)

// If strict host key checks are enabled but no entries are available, another error will be shown by the known
// hosts getter, as that means that the Fleet deployment is incomplete.
Expand Down Expand Up @@ -1495,7 +1505,7 @@ func setStatusFromGitjob(ctx context.Context, c client.Client, gitRepo *v1alpha1
// - Terminating
switch result.Status {
case status.FailedStatus:
kstatus.SetError(gitRepo, terminationMessage)
kstatus.SetError(gitRepo, filterFleetCLIJobOutput(terminationMessage))
case status.CurrentStatus:
if strings.Contains(result.Message, "Job Completed") {
gitRepo.Status.Commit = job.Annotations["commit"]
Expand Down Expand Up @@ -1632,3 +1642,57 @@ func separatorInBundleDefinitions(gitrepo v1alpha1.GitRepo, sep rune) bool {

return false
}

func filterFleetCLIJobOutput(output string) string {
// first split the output in lines
lines := strings.Split(output, "\n")
s := ""
for _, l := range lines {
s = s + getFleetCLIErrorsFromLine(l)
}

s = strings.Trim(s, "\n")
// in the case that all the messages from fleet apply are from libraries
// we just report an unknown error
if s == "" {
s = "Unknown error"
}

return s
}

func getFleetCLIErrorsFromLine(l string) string {
type LogEntry struct {
Level string `json:"level"`
FleetErrorMsg string `json:"fleetErrorMessage"`
Time string `json:"time"`
Msg string `json:"msg"`
}
s := ""
open := strings.IndexByte(l, '{')
if open == -1 {
// line does not contain a valid json string
return ""
}
close := strings.IndexByte(l, '}')
if close != -1 {
if close < open {
// looks like there is some garbage before a possible json string
// ignore everything up to that closing bracked and try again
return getFleetCLIErrorsFromLine(l[close+1:])
}
} else if close == -1 {
// line does not contain a valid json string
return ""
}
var entry LogEntry
if err := json.Unmarshal([]byte(l[open:close+1]), &entry); err == nil {
s = s + entry.FleetErrorMsg + "\n"
}
// check if there's more to parse
if close+1 < len(l) {
s = s + getFleetCLIErrorsFromLine(l[close+1:])
}

return s
}
Loading
Loading