add --since and --since-time

Signed-off-by: Tomas Tulka <tomas.tulka@gmail.com>
pull/1754/head
Tomas Tulka 3 years ago
parent 375e00c79c
commit 54758b1692

@ -26,12 +26,14 @@ import (
"os" "os"
"strings" "strings"
"sync" "sync"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/util"
"github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/flags"
"github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/internal/utils"
@ -42,16 +44,19 @@ var logsCmd = &cobra.Command{
Short: "Display formatted logs for Flux components", Short: "Display formatted logs for Flux components",
Long: "The logs command displays formatted logs from various Flux components.", Long: "The logs command displays formatted logs from various Flux components.",
Example: ` # Print the reconciliation logs of all Flux custom resources in your cluster Example: ` # Print the reconciliation logs of all Flux custom resources in your cluster
flux logs --all-namespaces flux logs --all-namespaces
# Print all logs of all Flux custom resources newer than 2 minutes
flux logs --all-namespaces --since=2m
# Stream logs for a particular log level # Stream logs for a particular log level
flux logs --follow --level=error --all-namespaces flux logs --follow --level=error --all-namespaces
# Filter logs by kind, name and namespace # Filter logs by kind, name and namespace
flux logs --kind=Kustomization --name=podinfo --namespace=default flux logs --kind=Kustomization --name=podinfo --namespace=default
# Print logs when Flux is installed in a different namespace than flux-system # Print logs when Flux is installed in a different namespace than flux-system
flux logs --flux-namespace=my-namespace flux logs --flux-namespace=my-namespace
`, `,
RunE: logsCmdRun, RunE: logsCmdRun,
} }
@ -64,6 +69,8 @@ type logsFlags struct {
name string name string
fluxNamespace string fluxNamespace string
allNamespaces bool allNamespaces bool
sinceTime string
sinceSeconds time.Duration
} }
var logsArgs = &logsFlags{ var logsArgs = &logsFlags{
@ -78,6 +85,8 @@ func init() {
logsCmd.Flags().Int64VarP(&logsArgs.tail, "tail", "", logsArgs.tail, "lines of recent log file to display") logsCmd.Flags().Int64VarP(&logsArgs.tail, "tail", "", logsArgs.tail, "lines of recent log file to display")
logsCmd.Flags().StringVarP(&logsArgs.fluxNamespace, "flux-namespace", "", rootArgs.defaults.Namespace, "the namespace where the Flux components are running") logsCmd.Flags().StringVarP(&logsArgs.fluxNamespace, "flux-namespace", "", rootArgs.defaults.Namespace, "the namespace where the Flux components are running")
logsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces") logsCmd.Flags().BoolVarP(&logsArgs.allNamespaces, "all-namespaces", "A", false, "displays logs for objects across all namespaces")
logsCmd.Flags().DurationVar(&logsArgs.sinceSeconds, "since", logsArgs.sinceSeconds, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
logsCmd.Flags().StringVar(&logsArgs.sinceTime, "since-time", logsArgs.sinceTime, "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
rootCmd.AddCommand(logsCmd) rootCmd.AddCommand(logsCmd)
} }
@ -87,7 +96,6 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel() defer cancel()
var pods []corev1.Pod
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext) cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil { if err != nil {
return err return err
@ -102,7 +110,7 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("no argument required") return fmt.Errorf("no argument required")
} }
pods, err = getPods(ctx, clientset, fluxSelector) pods, err := getPods(ctx, clientset, fluxSelector)
if err != nil { if err != nil {
return err return err
} }
@ -115,6 +123,24 @@ func logsCmdRun(cmd *cobra.Command, args []string) error {
logOpts.TailLines = &logsArgs.tail logOpts.TailLines = &logsArgs.tail
} }
if len(logsArgs.sinceTime) > 0 && logsArgs.sinceSeconds != 0 {
return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
}
if len(logsArgs.sinceTime) > 0 {
t, err := util.ParseRFC3339(logsArgs.sinceTime, metav1.Now)
if err != nil {
return fmt.Errorf("%s is not a valid (RFC3339) time", logsArgs.sinceTime)
}
logOpts.SinceTime = &t
}
if logsArgs.sinceSeconds != 0 {
// round up to the nearest second
sec := int64(logsArgs.sinceSeconds.Round(time.Second).Seconds())
logOpts.SinceSeconds = &sec
}
var requests []rest.ResponseWrapper var requests []rest.ResponseWrapper
for _, pod := range pods { for _, pod := range pods {
req := clientset.CoreV1().Pods(logsArgs.fluxNamespace).GetLogs(pod.Name, logOpts) req := clientset.CoreV1().Pods(logsArgs.fluxNamespace).GetLogs(pod.Name, logOpts)

@ -0,0 +1,63 @@
// +build unit
package main
import (
"testing"
)
func TestLogsNoArgs(t *testing.T) {
cmd := cmdTestCase{
args: "logs",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsAllNamespaces(t *testing.T) {
cmd := cmdTestCase{
args: "logs --all-namespaces",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsSince(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since=2m",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsSinceInvalid(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since=XXX",
assert: assertError(`invalid argument "XXX" for "--since" flag: time: invalid duration "XXX"`),
}
cmd.runTestCmd(t)
}
func TestLogsSinceTime(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since-time=2021-08-06T14:26:25.546Z",
assert: assertSuccess(),
}
cmd.runTestCmd(t)
}
func TestLogsSinceTimeInvalid(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since-time=XXX",
assert: assertError("XXX is not a valid (RFC3339) time"),
}
cmd.runTestCmd(t)
}
func TestLogsSinceOnlyOneAllowed(t *testing.T) {
cmd := cmdTestCase{
args: "logs --since=2m --since-time=2021-08-06T14:26:25.546Z",
assert: assertError("at most one of `sinceTime` or `sinceSeconds` may be specified"),
}
cmd.runTestCmd(t)
}
Loading…
Cancel
Save