diff --git a/Makefile b/Makefile index d7b758b015..b22f715645 100644 --- a/Makefile +++ b/Makefile @@ -239,11 +239,11 @@ all: binaries docs .PHONY: binaries ifeq ($(GOOS),freebsd) -binaries: podman podman-remote ## (FreeBSD) Build podman and podman-remote binaries +binaries: podman podman-remote podman-testing ## (FreeBSD) Build podman, podman-remote, and podman-testing binaries else ifneq (, $(findstring $(GOOS),darwin windows)) binaries: podman-remote ## (macOS/Windows) Build podman-remote (client) only binaries else -binaries: podman podman-remote podman-testing podmansh rootlessport quadlet ## (Linux) Build podman, podman-remote and rootlessport binaries quadlet +binaries: podman podman-remote podman-testing podmansh rootlessport quadlet ## (Linux) Build podman, podman-remote, podmansh, rootlessport, and quadlet binaries endif # Extract text following double-# for targets, as their description for @@ -478,7 +478,7 @@ $(SRCBINDIR)/podman-testing: $(SOURCES) go.mod go.sum -o $@ ./cmd/podman-testing .PHONY: podman-testing -podman-testing: bin/podman-testing +podman-testing: $(SRCBINDIR)/podman-testing ### ### Secondary binary-build targets diff --git a/cmd/podman-testing/call.go b/cmd/podman-testing/call.go new file mode 100644 index 0000000000..c9d65bdf11 --- /dev/null +++ b/cmd/podman-testing/call.go @@ -0,0 +1,152 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "strings" + + "github.com/containers/podman/v6/pkg/api/grpcpb" + "github.com/containers/podman/v6/pkg/bindings" + "github.com/containers/podman/v6/pkg/domain/entities" + "github.com/spf13/cobra" + "go.podman.io/common/pkg/completion" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + reflectionv1 "google.golang.org/grpc/reflection/grpc_reflection_v1" +) + +var ( + noopDescription = `Call the no-op GRPC endpoint.` + noopCmd = &cobra.Command{ + Use: "noop [arg]", + Args: cobra.MaximumNArgs(1), + Short: "Call the no-op GRPC endpoint", + Long: noopDescription, + RunE: noop, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman-testing noop`, + } + lsDescription = `List GRPC endpoints.` + lsCmd = &cobra.Command{ + Use: "ls [arg]", + Args: cobra.MaximumNArgs(1), + Short: "Call an RPC endpoint", + Long: lsDescription, + RunE: ls, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman-testing ls`, + } +) + +func init() { + mainCmd.AddCommand(noopCmd) + mainCmd.AddCommand(lsCmd) +} + +func ls(_ *cobra.Command, args []string) error { + if podmanConfig.EngineMode != entities.TunnelMode { + return errors.New("only available in remote mode") + } + ctx, grpcClient, err := getGrpcClient() + if err != nil { + return fmt.Errorf("setting up grpc client for podman service: %w", err) + } + if err != nil { + return fmt.Errorf("setting up grpc client for podman service: %w", err) + } + reflectionClient := reflectionv1.NewServerReflectionClient(grpcClient) + if reflectionClient == nil { + return fmt.Errorf("setting up client for reflection grpc service: %w", err) + } + info, err := reflectionClient.ServerReflectionInfo(ctx) + if err != nil { + return fmt.Errorf("reflection grpc service: %w", err) + } + ls := "" + if len(args) > 1 { + ls = args[1] + } + err = info.Send(&reflectionv1.ServerReflectionRequest{ + MessageRequest: &reflectionv1.ServerReflectionRequest_ListServices{ + ListServices: ls, + }, + }) + if err != nil { + return fmt.Errorf("reflection grpc service: %w", err) + } + err = info.CloseSend() + if err != nil { + return fmt.Errorf("reflection grpc service: %w", err) + } + var response reflectionv1.ServerReflectionResponse + err = info.RecvMsg(&response) + for err == nil { + var out []byte + out, err = json.Marshal(response.GetListServicesResponse()) + if err != nil { + return fmt.Errorf("encoding response from grpc service: %w", err) + } + fmt.Println(string(out)) + response.Reset() + err = info.RecvMsg(&response) + } + if !errors.Is(err, io.EOF) { + return fmt.Errorf("unexpected grpc protocol error: %w", err) + } + return nil +} + +func noop(_ *cobra.Command, args []string) error { + var out []byte + switch podmanConfig.EngineMode { + case entities.TunnelMode: + ctx, grpcClient, err := getGrpcClient() + if err != nil { + return fmt.Errorf("setting up grpc client for podman service: %w", err) + } + noopClient := grpcpb.NewNoopClient(grpcClient) + if noopClient == nil { + return fmt.Errorf("setting up client for noop grpc service: %w", err) + } + var request grpcpb.NoopRequest + if encoded := strings.Join(args, ""); len(encoded) > 0 { + if err := json.Unmarshal([]byte(encoded), &request); err != nil { + return fmt.Errorf("parsing client request contents for noop grpc service: %w", err) + } + } + response, err := noopClient.Noop(ctx, &request) + if err != nil { + return fmt.Errorf("noop grpc service: %w", err) + } + out, err = json.Marshal(response) + if err != nil { + return fmt.Errorf("encoding response from grpc service: %w", err) + } + default: + return errors.New("only available in remote mode") + } + fmt.Println(string(out)) + return nil +} + +func getGrpcClient() (context.Context, *grpc.ClientConn, error) { + ctx, err := bindings.NewConnection(mainContext, podmanConfig.URI) + if err != nil { + return nil, nil, fmt.Errorf("connecting to podman service: %w", err) + } + client, err := bindings.GetClient(ctx) + if err != nil { + return nil, nil, fmt.Errorf("obtaining client handle for podman service: %w", err) + } + onlyPodmanSystemServiceDialer := grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { return client.GetDialer(ctx) }) + withoutEncryption := grpc.WithTransportCredentials(insecure.NewCredentials()) + grpcClient, err := grpc.NewClient(podmanConfig.URI, onlyPodmanSystemServiceDialer, withoutEncryption) + if err != nil { + return nil, nil, fmt.Errorf("setting up grpc client for podman service: %w", err) + } + return ctx, grpcClient, err +} diff --git a/cmd/podman-testing/create.go b/cmd/podman-testing/create.go index 654e5af041..993ecb05b5 100644 --- a/cmd/podman-testing/create.go +++ b/cmd/podman-testing/create.go @@ -1,4 +1,4 @@ -//go:build !remote +//go:build (linux || freebsd) && !remote package main @@ -20,7 +20,7 @@ var ( Long: createStorageLayerDescription, RunE: createStorageLayer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing create-storage-layer`, + Example: `podman-testing create-storage-layer`, } createStorageLayerOpts entities.CreateStorageLayerOptions @@ -33,7 +33,7 @@ var ( Long: createLayerDescription, RunE: createLayer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing create-layer`, + Example: `podman-testing create-layer`, } createLayerOpts entities.CreateLayerOptions @@ -46,7 +46,7 @@ var ( Long: createImageDescription, RunE: createImage, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing create-image`, + Example: `podman-testing create-image`, } createImageOpts entities.CreateImageOptions @@ -59,7 +59,7 @@ var ( Long: createContainerDescription, RunE: createContainer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing create-container`, + Example: `podman-testing create-container`, } createContainerOpts entities.CreateContainerOptions diff --git a/cmd/podman-testing/data.go b/cmd/podman-testing/data.go index bdac66c609..c1feffe0b1 100644 --- a/cmd/podman-testing/data.go +++ b/cmd/podman-testing/data.go @@ -1,4 +1,4 @@ -//go:build !remote +//go:build (linux || freebsd) && !remote package main @@ -21,7 +21,7 @@ var ( Long: createLayerDataDescription, RunE: createLayerData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing create-layer-data`, + Example: `podman-testing create-layer-data`, } createLayerDataOpts entities.CreateLayerDataOptions @@ -37,7 +37,7 @@ var ( Long: createImageDataDescription, RunE: createImageData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing create-image-data`, + Example: `podman-testing create-image-data`, } createImageDataOpts entities.CreateImageDataOptions @@ -53,7 +53,7 @@ var ( Long: createContainerDataDescription, RunE: createContainerData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing create-container-data`, + Example: `podman-testing create-container-data`, } createContainerDataOpts entities.CreateContainerDataOptions @@ -69,7 +69,7 @@ var ( Long: modifyLayerDataDescription, RunE: modifyLayerData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing modify-layer-data`, + Example: `podman-testing modify-layer-data`, } modifyLayerDataOpts entities.ModifyLayerDataOptions @@ -84,7 +84,7 @@ var ( Long: modifyImageDataDescription, RunE: modifyImageData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing modify-image-data`, + Example: `podman-testing modify-image-data`, } modifyImageDataOpts entities.ModifyImageDataOptions @@ -99,7 +99,7 @@ var ( Long: modifyContainerDataDescription, RunE: modifyContainerData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing modify-container-data`, + Example: `podman-testing modify-container-data`, } modifyContainerDataOpts entities.ModifyContainerDataOptions @@ -114,7 +114,7 @@ var ( Long: removeLayerDataDescription, RunE: removeLayerData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing remove-layer-data`, + Example: `podman-testing remove-layer-data`, } removeLayerDataOpts entities.RemoveLayerDataOptions @@ -127,7 +127,7 @@ var ( Long: removeImageDataDescription, RunE: removeImageData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing remove-image-data`, + Example: `podman-testing remove-image-data`, } removeImageDataOpts entities.RemoveImageDataOptions @@ -140,7 +140,7 @@ var ( Long: removeContainerDataDescription, RunE: removeContainerData, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing remove-container-data`, + Example: `podman-testing remove-container-data`, } removeContainerDataOpts entities.RemoveContainerDataOptions diff --git a/cmd/podman-testing/layer.go b/cmd/podman-testing/layer.go index 6146846801..f098d4ef6f 100644 --- a/cmd/podman-testing/layer.go +++ b/cmd/podman-testing/layer.go @@ -1,4 +1,4 @@ -//go:build !remote +//go:build (linux || freebsd) && !remote package main @@ -21,7 +21,7 @@ var ( Long: populateLayerDescription, RunE: populateLayer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing populate-layer`, + Example: `podman-testing populate-layer`, } populateLayerOpts entities.PopulateLayerOptions @@ -35,7 +35,7 @@ var ( Long: modifyLayerDescription, RunE: modifyLayer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing modify-layer`, + Example: `podman-testing modify-layer`, } modifyLayerOpts entities.ModifyLayerOptions diff --git a/cmd/podman-testing/main.go b/cmd/podman-testing/main.go index 958fc7150f..b83d8c4de6 100644 --- a/cmd/podman-testing/main.go +++ b/cmd/podman-testing/main.go @@ -1,5 +1,3 @@ -//go:build !remote - package main import ( @@ -11,13 +9,10 @@ import ( "syscall" _ "github.com/containers/podman/v6/cmd/podman/completion" - ientities "github.com/containers/podman/v6/internal/domain/entities" - "github.com/containers/podman/v6/internal/domain/infra" "github.com/containers/podman/v6/pkg/domain/entities" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "go.podman.io/common/pkg/config" - "go.podman.io/storage" "go.podman.io/storage/pkg/reexec" "go.podman.io/storage/pkg/unshare" ) @@ -38,11 +33,9 @@ var ( SilenceUsage: true, SilenceErrors: true, } - mainContext = context.Background() - podmanConfig entities.PodmanConfig - globalStorageOptions storage.StoreOptions - globalLogLevel string - testingEngine ientities.TestingEngine + mainContext = context.Background() + podmanConfig entities.PodmanConfig + globalLogLevel string ) func init() { @@ -80,18 +73,9 @@ func before() error { } podmanConfig.ContainersConf = containersConf - podmanConfig.StorageDriver = globalStorageOptions.GraphDriverName - podmanConfig.GraphRoot = globalStorageOptions.GraphRoot - podmanConfig.Runroot = globalStorageOptions.RunRoot - podmanConfig.ImageStore = globalStorageOptions.ImageStore - podmanConfig.StorageOpts = globalStorageOptions.GraphDriverOptions - podmanConfig.TransientStore = globalStorageOptions.TransientStore - - te, err := infra.NewTestingEngine(&podmanConfig) - if err != nil { - return fmt.Errorf("initializing libpod: %w", err) + if err := testingEngineBefore(&podmanConfig); err != nil { + return fmt.Errorf("setting up testing engine: %w", err) } - testingEngine = te return nil } diff --git a/cmd/podman-testing/remove.go b/cmd/podman-testing/remove.go index e3c30ee5f1..6d90ffecae 100644 --- a/cmd/podman-testing/remove.go +++ b/cmd/podman-testing/remove.go @@ -1,4 +1,4 @@ -//go:build !remote +//go:build (linux || freebsd) && !remote package main @@ -20,7 +20,7 @@ var ( Long: removeStorageLayerDescription, RunE: removeStorageLayer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing remove-storage-layer`, + Example: `podman-testing remove-storage-layer`, } removeStorageLayerOpts entities.RemoveStorageLayerOptions @@ -33,7 +33,7 @@ var ( Long: removeLayerDescription, RunE: removeLayer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing remove-layer`, + Example: `podman-testing remove-layer`, } removeLayerOpts entities.RemoveLayerOptions @@ -46,7 +46,7 @@ var ( Long: removeImageDescription, RunE: removeImage, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing remove-image`, + Example: `podman-testing remove-image`, } removeImageOpts entities.RemoveImageOptions @@ -59,7 +59,7 @@ var ( Long: removeContainerDescription, RunE: removeContainer, ValidArgsFunction: completion.AutocompleteNone, - Example: `podman testing remove-container`, + Example: `podman-testing remove-container`, } removeContainerOpts entities.RemoveContainerOptions diff --git a/cmd/podman-testing/store_supported.go b/cmd/podman-testing/store_supported.go index 3955ae0ad0..6855645968 100644 --- a/cmd/podman-testing/store_supported.go +++ b/cmd/podman-testing/store_supported.go @@ -6,14 +6,18 @@ import ( "fmt" "os" + ientities "github.com/containers/podman/v6/internal/domain/entities" + "github.com/containers/podman/v6/internal/domain/infra" "github.com/containers/podman/v6/pkg/domain/entities" "go.podman.io/storage" "go.podman.io/storage/types" ) var ( - globalStore storage.Store - engineMode = entities.ABIMode + globalStorageOptions storage.StoreOptions + globalStore storage.Store + engineMode = entities.ABIMode + testingEngine ientities.TestingEngine ) func init() { @@ -52,6 +56,7 @@ func storeBefore() error { } else { engineMode = entities.ABIMode } + podmanConfig.EngineMode = engineMode return nil } @@ -62,3 +67,14 @@ func storeAfter() error { } return nil } + +func testingEngineBefore(podmanConfig *entities.PodmanConfig) (err error) { + podmanConfig.StorageDriver = globalStorageOptions.GraphDriverName + podmanConfig.GraphRoot = globalStorageOptions.GraphRoot + podmanConfig.Runroot = globalStorageOptions.RunRoot + podmanConfig.ImageStore = globalStorageOptions.ImageStore + podmanConfig.StorageOpts = globalStorageOptions.GraphDriverOptions + podmanConfig.TransientStore = globalStorageOptions.TransientStore + testingEngine, err = infra.NewTestingEngine(podmanConfig) + return err +} diff --git a/cmd/podman-testing/store_unsupported.go b/cmd/podman-testing/store_unsupported.go new file mode 100644 index 0000000000..d17334dd20 --- /dev/null +++ b/cmd/podman-testing/store_unsupported.go @@ -0,0 +1,23 @@ +//go:build !(linux || freebsd) || remote + +package main + +import ( + "github.com/containers/podman/v6/pkg/domain/entities" +) + +var ( + engineMode = entities.TunnelMode +) + +func storeBefore() error { + return nil +} + +func storeAfter() error { + return nil +} + +func testingEngineBefore(_ *entities.PodmanConfig) (err error) { + return nil +} diff --git a/pkg/api/grpcpb/build.sh b/pkg/api/grpcpb/build.sh new file mode 100755 index 0000000000..f3e7a09077 --- /dev/null +++ b/pkg/api/grpcpb/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e +cd $(dirname ${BASH_SOURCE[0]}) +TOP=../../.. +PATH=${TOP}/test/tools/build:${PATH} +set -x +for proto in *.proto ; do + protoc \ + --go_opt=paths=source_relative --go_out . \ + --go-grpc_opt=paths=source_relative --go-grpc_out . \ + ${proto} +done diff --git a/pkg/api/grpcpb/noop.pb.go b/pkg/api/grpcpb/noop.pb.go new file mode 100644 index 0000000000..fb7c967816 --- /dev/null +++ b/pkg/api/grpcpb/noop.pb.go @@ -0,0 +1,174 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v3.19.6 +// source: noop.proto + +package grpcpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type NoopRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ignored string `protobuf:"bytes,1,opt,name=ignored,proto3" json:"ignored,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NoopRequest) Reset() { + *x = NoopRequest{} + mi := &file_noop_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NoopRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NoopRequest) ProtoMessage() {} + +func (x *NoopRequest) ProtoReflect() protoreflect.Message { + mi := &file_noop_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NoopRequest.ProtoReflect.Descriptor instead. +func (*NoopRequest) Descriptor() ([]byte, []int) { + return file_noop_proto_rawDescGZIP(), []int{0} +} + +func (x *NoopRequest) GetIgnored() string { + if x != nil { + return x.Ignored + } + return "" +} + +type NoopResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ignored string `protobuf:"bytes,1,opt,name=ignored,proto3" json:"ignored,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NoopResponse) Reset() { + *x = NoopResponse{} + mi := &file_noop_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NoopResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NoopResponse) ProtoMessage() {} + +func (x *NoopResponse) ProtoReflect() protoreflect.Message { + mi := &file_noop_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NoopResponse.ProtoReflect.Descriptor instead. +func (*NoopResponse) Descriptor() ([]byte, []int) { + return file_noop_proto_rawDescGZIP(), []int{1} +} + +func (x *NoopResponse) GetIgnored() string { + if x != nil { + return x.Ignored + } + return "" +} + +var File_noop_proto protoreflect.FileDescriptor + +const file_noop_proto_rawDesc = "" + + "\n" + + "\n" + + "noop.proto\x12\fio.podman.v1\"'\n" + + "\vNoopRequest\x12\x18\n" + + "\aignored\x18\x01 \x01(\tR\aignored\"(\n" + + "\fNoopResponse\x12\x18\n" + + "\aignored\x18\x01 \x01(\tR\aignored2E\n" + + "\x04Noop\x12=\n" + + "\x04Noop\x12\x19.io.podman.v1.NoopRequest\x1a\x1a.io.podman.v1.NoopResponseB0Z.github.com/containers/podman/v6/pkg/api/grpcpbb\x06proto3" + +var ( + file_noop_proto_rawDescOnce sync.Once + file_noop_proto_rawDescData []byte +) + +func file_noop_proto_rawDescGZIP() []byte { + file_noop_proto_rawDescOnce.Do(func() { + file_noop_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_noop_proto_rawDesc), len(file_noop_proto_rawDesc))) + }) + return file_noop_proto_rawDescData +} + +var file_noop_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_noop_proto_goTypes = []any{ + (*NoopRequest)(nil), // 0: io.podman.v1.NoopRequest + (*NoopResponse)(nil), // 1: io.podman.v1.NoopResponse +} +var file_noop_proto_depIdxs = []int32{ + 0, // 0: io.podman.v1.Noop.Noop:input_type -> io.podman.v1.NoopRequest + 1, // 1: io.podman.v1.Noop.Noop:output_type -> io.podman.v1.NoopResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_noop_proto_init() } +func file_noop_proto_init() { + if File_noop_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_noop_proto_rawDesc), len(file_noop_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_noop_proto_goTypes, + DependencyIndexes: file_noop_proto_depIdxs, + MessageInfos: file_noop_proto_msgTypes, + }.Build() + File_noop_proto = out.File + file_noop_proto_goTypes = nil + file_noop_proto_depIdxs = nil +} diff --git a/pkg/api/grpcpb/noop.proto b/pkg/api/grpcpb/noop.proto new file mode 100644 index 0000000000..13829c23ea --- /dev/null +++ b/pkg/api/grpcpb/noop.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package io.podman.v1; + +option go_package = "github.com/containers/podman/v6/pkg/api/grpcpb"; + +service Noop { + rpc Noop(NoopRequest) returns (NoopResponse); +} + +message NoopRequest { + string ignored = 1; +} + +message NoopResponse { + string ignored = 1; +} diff --git a/pkg/api/grpcpb/noop_grpc.pb.go b/pkg/api/grpcpb/noop_grpc.pb.go new file mode 100644 index 0000000000..2782a920b4 --- /dev/null +++ b/pkg/api/grpcpb/noop_grpc.pb.go @@ -0,0 +1,121 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.19.6 +// source: noop.proto + +package grpcpb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Noop_Noop_FullMethodName = "/io.podman.v1.Noop/Noop" +) + +// NoopClient is the client API for Noop service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type NoopClient interface { + Noop(ctx context.Context, in *NoopRequest, opts ...grpc.CallOption) (*NoopResponse, error) +} + +type noopClient struct { + cc grpc.ClientConnInterface +} + +func NewNoopClient(cc grpc.ClientConnInterface) NoopClient { + return &noopClient{cc} +} + +func (c *noopClient) Noop(ctx context.Context, in *NoopRequest, opts ...grpc.CallOption) (*NoopResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(NoopResponse) + err := c.cc.Invoke(ctx, Noop_Noop_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NoopServer is the server API for Noop service. +// All implementations must embed UnimplementedNoopServer +// for forward compatibility. +type NoopServer interface { + Noop(context.Context, *NoopRequest) (*NoopResponse, error) + mustEmbedUnimplementedNoopServer() +} + +// UnimplementedNoopServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedNoopServer struct{} + +func (UnimplementedNoopServer) Noop(context.Context, *NoopRequest) (*NoopResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Noop not implemented") +} +func (UnimplementedNoopServer) mustEmbedUnimplementedNoopServer() {} +func (UnimplementedNoopServer) testEmbeddedByValue() {} + +// UnsafeNoopServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NoopServer will +// result in compilation errors. +type UnsafeNoopServer interface { + mustEmbedUnimplementedNoopServer() +} + +func RegisterNoopServer(s grpc.ServiceRegistrar, srv NoopServer) { + // If the following call pancis, it indicates UnimplementedNoopServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Noop_ServiceDesc, srv) +} + +func _Noop_Noop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NoopRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NoopServer).Noop(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Noop_Noop_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NoopServer).Noop(ctx, req.(*NoopRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Noop_ServiceDesc is the grpc.ServiceDesc for Noop service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Noop_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "io.podman.v1.Noop", + HandlerType: (*NoopServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Noop", + Handler: _Noop_Noop_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "noop.proto", +} diff --git a/pkg/api/handlers/grpc/noop.go b/pkg/api/handlers/grpc/noop.go new file mode 100644 index 0000000000..74f0c06089 --- /dev/null +++ b/pkg/api/handlers/grpc/noop.go @@ -0,0 +1,26 @@ +//go:build !remote + +package grpc + +import ( + "context" + + "github.com/containers/podman/v6/libpod" + "github.com/containers/podman/v6/pkg/api/grpcpb" +) + +type noopServer struct { + grpcpb.UnimplementedNoopServer + runtime *libpod.Runtime +} + +func (noopServer) Noop(_ context.Context, req *grpcpb.NoopRequest) (*grpcpb.NoopResponse, error) { + resp := &grpcpb.NoopResponse{ + Ignored: req.GetIgnored(), + } + return resp, nil +} + +func NewNoopServer(runtime *libpod.Runtime) grpcpb.NoopServer { + return &noopServer{runtime: runtime} +} diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index d9d21083d3..d7cfcab1d4 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -19,7 +19,9 @@ import ( "github.com/containers/podman/v6/libpod" "github.com/containers/podman/v6/libpod/shutdown" + "github.com/containers/podman/v6/pkg/api/grpcpb" "github.com/containers/podman/v6/pkg/api/handlers" + grpchandlers "github.com/containers/podman/v6/pkg/api/handlers/grpc" "github.com/containers/podman/v6/pkg/api/server/idle" "github.com/containers/podman/v6/pkg/api/types" "github.com/containers/podman/v6/pkg/domain/entities" @@ -28,12 +30,13 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/schema" "github.com/sirupsen/logrus" - _ "google.golang.org/grpc" - _ "google.golang.org/grpc/reflection" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" ) type APIServer struct { - http.Server // The HTTP work happens here + http.Server // The HTTP work happens here + grpc *grpc.Server // GRPC stuff happens here net.Listener // mux for routing HTTP API calls to libpod routines *libpod.Runtime // Where the real work happens *schema.Decoder // Decoder for Query parameters to structs @@ -73,6 +76,10 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser router := mux.NewRouter().UseEncodedPath() tracker := idle.NewTracker(opts.Timeout) + serverProtocols := &http.Protocols{} + serverProtocols.SetHTTP1(true) + serverProtocols.SetHTTP2(true) + server := APIServer{ Server: http.Server{ ConnContext: func(ctx context.Context, c net.Conn) context.Context { @@ -82,7 +89,9 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser ErrorLog: log.New(logrus.StandardLogger().Out, "", 0), Handler: router, IdleTimeout: opts.Timeout * 2, + Protocols: serverProtocols, }, + grpc: grpc.NewServer(), CorsHeaders: opts.CorsHeaders, Listener: listener, PProfAddr: opts.PProfAddr, @@ -92,6 +101,9 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser tlsClientCAFile: opts.TLSClientCAFile, } + router.NewRoute().HeadersRegexp("Content-Type", "application/grpc(\\+.*)?").Handler(server.grpc) + reflection.Register(server.grpc) + server.BaseContext = func(_ net.Listener) context.Context { ctx := context.WithValue(context.Background(), types.DecoderKey, handlers.NewAPIDecoder()) ctx = context.WithValue(ctx, types.CompatDecoderKey, handlers.NewCompatAPIDecoder()) @@ -163,6 +175,8 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser } } + grpcpb.RegisterNoopServer(server.grpc, grpchandlers.NewNoopServer(runtime)) // TODO: make this table-driven instead of a one-off? + if logrus.IsLevelEnabled(logrus.TraceLevel) { // If in trace mode log request and response bodies router.Use(loggingHandler()) @@ -213,6 +227,7 @@ func (s *APIServer) Serve() error { s.setupPprof() if err := shutdown.Register("service", func(_ os.Signal) error { + s.grpc.GracefulStop() err := s.Shutdown(true) if err == nil { // For `systemctl stop podman.service` support, exit code should be 0 @@ -247,6 +262,7 @@ func (s *APIServer) Serve() error { } err = s.Server.ServeTLS(s.Listener, s.tlsCertFile, s.tlsKeyFile) } else { + s.Server.Protocols.SetUnencryptedHTTP2(true) err = s.Server.Serve(s.Listener) } if err != nil && err != http.ErrServerClosed { diff --git a/test/system/251-system-service.bats b/test/system/251-system-service.bats index 1113bed7e0..c7c5fccd73 100644 --- a/test/system/251-system-service.bats +++ b/test/system/251-system-service.bats @@ -46,6 +46,24 @@ function _podman_system_service { rm -f $PODMAN_TMPDIR/myunix.sock } +@test "podman system service: grpc listener" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + URL=unix://$PODMAN_TMPDIR/myunix.sock + + _podman_system_service $URL --time=0 + wait_for_file $PODMAN_TMPDIR/myunix.sock + + nonce=$RANDOM + run_podman_testing --url "$URL" noop '{"ignored":"'$nonce'"}' + assert $status -eq 0 + is $(jq -c <<< "$output") '{"ignored":"'$nonce'"}' "noop responder" + + systemctl stop $SERVICE_NAME + rm -f $PODMAN_TMPDIR/myunix.sock +} + @test "podman-system-service containers survive service stop" { unset REMOTESYSTEM_TRANSPORT