diff --git a/cmd/crictl/events.go b/cmd/crictl/events.go new file mode 100644 index 0000000000..d7c5d7c0b2 --- /dev/null +++ b/cmd/crictl/events.go @@ -0,0 +1,89 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io" + + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + internalapi "k8s.io/cri-api/pkg/apis" + pb "k8s.io/cri-api/pkg/apis/runtime/v1" +) + +var eventsCommand = &cli.Command{ + Name: "events", + Usage: "Fetch the events of containers", + Aliases: []string{"event"}, + UseShortOptionHandling: true, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Value: "json", + Usage: "Output format, One of: json|yaml|go-template", + }, + &cli.StringFlag{ + Name: "template", + Usage: "The template string is only used when output is go-template; The Template format is golang template", + }, + }, + Action: func(c *cli.Context) error { + if c.NArg() != 0 { + return cli.ShowSubcommandHelp(c) + } + + runtimeClient, err := getRuntimeService(c, 0) + if err != nil { + return err + } + + if err = Events(c, runtimeClient); err != nil { + return fmt.Errorf("getting container events: %w", err) + } + + return nil + }, +} + +func Events(cliContext *cli.Context, client internalapi.RuntimeService) error { + errCh := make(chan error, 1) + + containerEventsCh := make(chan *pb.ContainerEventResponse) + go func() { + logrus.Debug("getting container events") + err := client.GetContainerEvents(containerEventsCh) + if err == io.EOF { + errCh <- nil + return + } + errCh <- err + }() + + for { + select { + case err := <-errCh: + return err + case e := <-containerEventsCh: + err := outputEvent(e, cliContext.String("output"), cliContext.String("template")) + if err != nil { + return err + } + } + } +} diff --git a/cmd/crictl/main.go b/cmd/crictl/main.go index 14ea45a0b8..7836a6b369 100644 --- a/cmd/crictl/main.go +++ b/cmd/crictl/main.go @@ -179,6 +179,7 @@ func main() { completionCommand, checkpointContainerCommand, runtimeConfigCommand, + eventsCommand, } runtimeEndpointUsage := fmt.Sprintf("Endpoint of CRI container runtime "+ diff --git a/cmd/crictl/util.go b/cmd/crictl/util.go index c3970bd8bb..48f638e305 100644 --- a/cmd/crictl/util.go +++ b/cmd/crictl/util.go @@ -260,6 +260,34 @@ func outputStatusInfo(status string, info map[string]string, format string, tmpl return nil } +func outputEvent(event proto.Message, format string, tmplStr string) error { + switch format { + case "yaml": + err := outputProtobufObjAsYAML(event) + if err != nil { + return err + } + case "json": + err := outputProtobufObjAsJSON(event) + if err != nil { + return err + } + case "go-template": + jsonEvent, err := protobufObjectToJSON(event) + if err != nil { + return err + } + output, err := tmplExecuteRawJSON(tmplStr, jsonEvent) + if err != nil { + return err + } + fmt.Println(output) + default: + fmt.Printf("Don't support %q format\n", format) + } + return nil +} + func parseLabelStringSlice(ss []string) (map[string]string, error) { labels := make(map[string]string) for _, s := range ss { diff --git a/docs/crictl.md b/docs/crictl.md index da4a843e6c..a2134f6e63 100644 --- a/docs/crictl.md +++ b/docs/crictl.md @@ -65,6 +65,7 @@ COMMANDS: - `statsp`: List pod(s) resource usage statistics - `completion`: Output bash shell completion code - `checkpoint`: Checkpoint one or more running containers +- `events, event`: Fetch the events of containers - `help, h`: Shows a list of commands or help for one command `crictl` by default connects on Unix to: diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go new file mode 100644 index 0000000000..cee7a4a502 --- /dev/null +++ b/test/e2e/events_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +// The actual test suite +var _ = t.Describe("events", func() { + It("should succeed", func() { + // Given + endpoint, testDir, crio := t.StartCrio() + + // When + session := t.CrictlWithEndpoint(endpoint, "events") + Expect(session.Out).ToNot(Say("unknown method GetContainerEvents")) // no errors + + // Then + session.Terminate() + t.StopCrio(testDir, crio) + }) +}) diff --git a/test/framework/framework.go b/test/framework/framework.go index 02ae9a19c3..ab4f95a6ba 100644 --- a/test/framework/framework.go +++ b/test/framework/framework.go @@ -136,9 +136,9 @@ func (t *TestFramework) CrictlExpectFailureWithEndpoint( func SetupCrio() string { const ( crioURL = "https://github.com/cri-o/cri-o" - crioVersion = "v1.23.1" + crioVersion = "v1.26.4" conmonURL = "https://github.com/containers/conmon" - conmonVersion = "v2.0.32" + conmonVersion = "v2.1.7" ) tmpDir := filepath.Join(os.TempDir(), "crio-tmp") @@ -202,7 +202,8 @@ func (t *TestFramework) StartCrio() (string, string, *Session) { " --cni-config-dir=%s"+ " --root=%s"+ " --runroot=%s"+ - " --pinns-path=%s", + " --pinns-path=%s"+ + " --enable-pod-events", filepath.Join(tmpDir, "bin", "crio"), filepath.Join(t.crioDir, "crio.conf"), endpoint,