diff --git a/cmd/bb_runner/main.go b/cmd/bb_runner/main.go index 000ae003..6252e99b 100644 --- a/cmd/bb_runner/main.go +++ b/cmd/bb_runner/main.go @@ -68,6 +68,13 @@ func main() { buildDirectoryPath, commandCreator, configuration.SetTmpdirEnvironmentVariable) + for _, mountinfo := range configuration.InputRootMounts { + r = runner.NewMountingRunner( + r, + buildDirectory, + mountinfo, + ) + } // Let bb_runner replace temporary directories with symbolic // links pointing to the temporary directory set up by diff --git a/pkg/proto/configuration/bb_runner/bb_runner.pb.go b/pkg/proto/configuration/bb_runner/bb_runner.pb.go index 0b6159b7..aa7676c4 100644 --- a/pkg/proto/configuration/bb_runner/bb_runner.pb.go +++ b/pkg/proto/configuration/bb_runner/bb_runner.pb.go @@ -41,6 +41,7 @@ type ApplicationConfiguration struct { SymlinkTemporaryDirectories []string `protobuf:"bytes,12,rep,name=symlink_temporary_directories,json=symlinkTemporaryDirectories,proto3" json:"symlink_temporary_directories,omitempty"` RunCommandCleaner []string `protobuf:"bytes,13,rep,name=run_command_cleaner,json=runCommandCleaner,proto3" json:"run_command_cleaner,omitempty"` AppleXcodeDeveloperDirectories map[string]string `protobuf:"bytes,14,rep,name=apple_xcode_developer_directories,json=appleXcodeDeveloperDirectories,proto3" json:"apple_xcode_developer_directories,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + InputRootMounts []*InputMountOptions `protobuf:"bytes,15,rep,name=input_root_mounts,json=inputRootMounts,proto3" json:"input_root_mounts,omitempty"` } func (x *ApplicationConfiguration) Reset() { @@ -166,6 +167,76 @@ func (x *ApplicationConfiguration) GetAppleXcodeDeveloperDirectories() map[strin return nil } +func (x *ApplicationConfiguration) GetInputRootMounts() []*InputMountOptions { + if x != nil { + return x.InputRootMounts + } + return nil +} + +type InputMountOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mountpoint string `protobuf:"bytes,1,opt,name=mountpoint,proto3" json:"mountpoint,omitempty"` + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` + FilesystemType string `protobuf:"bytes,3,opt,name=filesystem_type,json=filesystemType,proto3" json:"filesystem_type,omitempty"` +} + +func (x *InputMountOptions) Reset() { + *x = InputMountOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_proto_configuration_bb_runner_bb_runner_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InputMountOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InputMountOptions) ProtoMessage() {} + +func (x *InputMountOptions) ProtoReflect() protoreflect.Message { + mi := &file_pkg_proto_configuration_bb_runner_bb_runner_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InputMountOptions.ProtoReflect.Descriptor instead. +func (*InputMountOptions) Descriptor() ([]byte, []int) { + return file_pkg_proto_configuration_bb_runner_bb_runner_proto_rawDescGZIP(), []int{1} +} + +func (x *InputMountOptions) GetMountpoint() string { + if x != nil { + return x.Mountpoint + } + return "" +} + +func (x *InputMountOptions) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *InputMountOptions) GetFilesystemType() string { + if x != nil { + return x.FilesystemType + } + return "" +} + var File_pkg_proto_configuration_bb_runner_bb_runner_proto protoreflect.FileDescriptor var file_pkg_proto_configuration_bb_runner_bb_runner_proto_rawDesc = []byte{ @@ -183,7 +254,7 @@ var file_pkg_proto_configuration_bb_runner_bb_runner_proto_rawDesc = []byte{ 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xf3, 0x08, 0x0a, 0x18, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x6f, 0x74, 0x6f, 0x22, 0xd5, 0x09, 0x0a, 0x18, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, @@ -249,17 +320,31 @@ var file_pkg_proto_configuration_bb_runner_bb_runner_proto_rawDesc = []byte{ 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x58, 0x63, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, - 0x1a, 0x51, 0x0a, 0x23, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x58, 0x63, 0x6f, 0x64, 0x65, 0x44, 0x65, - 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, - 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, - 0x6e, 0x2f, 0x62, 0x62, 0x2d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x62, - 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x60, 0x0a, 0x11, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x62, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, + 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x0f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x4d, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x1a, 0x51, 0x0a, 0x23, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x58, 0x63, 0x6f, 0x64, 0x65, + 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x74, 0x0a, 0x11, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, + 0x65, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x62, 0x61, 0x72, 0x6e, 0x2f, 0x62, 0x62, 0x2d, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x2d, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x6b, + 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x62, 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -274,26 +359,28 @@ func file_pkg_proto_configuration_bb_runner_bb_runner_proto_rawDescGZIP() []byte return file_pkg_proto_configuration_bb_runner_bb_runner_proto_rawDescData } -var file_pkg_proto_configuration_bb_runner_bb_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_pkg_proto_configuration_bb_runner_bb_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_pkg_proto_configuration_bb_runner_bb_runner_proto_goTypes = []interface{}{ (*ApplicationConfiguration)(nil), // 0: buildbarn.configuration.bb_runner.ApplicationConfiguration - nil, // 1: buildbarn.configuration.bb_runner.ApplicationConfiguration.AppleXcodeDeveloperDirectoriesEntry - (*grpc.ServerConfiguration)(nil), // 2: buildbarn.configuration.grpc.ServerConfiguration - (*global.Configuration)(nil), // 3: buildbarn.configuration.global.Configuration - (*grpc.ClientConfiguration)(nil), // 4: buildbarn.configuration.grpc.ClientConfiguration - (*credentials.UNIXCredentialsConfiguration)(nil), // 5: buildbarn.configuration.credentials.UNIXCredentialsConfiguration + (*InputMountOptions)(nil), // 1: buildbarn.configuration.bb_runner.InputMountOptions + nil, // 2: buildbarn.configuration.bb_runner.ApplicationConfiguration.AppleXcodeDeveloperDirectoriesEntry + (*grpc.ServerConfiguration)(nil), // 3: buildbarn.configuration.grpc.ServerConfiguration + (*global.Configuration)(nil), // 4: buildbarn.configuration.global.Configuration + (*grpc.ClientConfiguration)(nil), // 5: buildbarn.configuration.grpc.ClientConfiguration + (*credentials.UNIXCredentialsConfiguration)(nil), // 6: buildbarn.configuration.credentials.UNIXCredentialsConfiguration } var file_pkg_proto_configuration_bb_runner_bb_runner_proto_depIdxs = []int32{ - 2, // 0: buildbarn.configuration.bb_runner.ApplicationConfiguration.grpc_servers:type_name -> buildbarn.configuration.grpc.ServerConfiguration - 3, // 1: buildbarn.configuration.bb_runner.ApplicationConfiguration.global:type_name -> buildbarn.configuration.global.Configuration - 4, // 2: buildbarn.configuration.bb_runner.ApplicationConfiguration.temporary_directory_installer:type_name -> buildbarn.configuration.grpc.ClientConfiguration - 5, // 3: buildbarn.configuration.bb_runner.ApplicationConfiguration.run_commands_as:type_name -> buildbarn.configuration.credentials.UNIXCredentialsConfiguration - 1, // 4: buildbarn.configuration.bb_runner.ApplicationConfiguration.apple_xcode_developer_directories:type_name -> buildbarn.configuration.bb_runner.ApplicationConfiguration.AppleXcodeDeveloperDirectoriesEntry - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 3, // 0: buildbarn.configuration.bb_runner.ApplicationConfiguration.grpc_servers:type_name -> buildbarn.configuration.grpc.ServerConfiguration + 4, // 1: buildbarn.configuration.bb_runner.ApplicationConfiguration.global:type_name -> buildbarn.configuration.global.Configuration + 5, // 2: buildbarn.configuration.bb_runner.ApplicationConfiguration.temporary_directory_installer:type_name -> buildbarn.configuration.grpc.ClientConfiguration + 6, // 3: buildbarn.configuration.bb_runner.ApplicationConfiguration.run_commands_as:type_name -> buildbarn.configuration.credentials.UNIXCredentialsConfiguration + 2, // 4: buildbarn.configuration.bb_runner.ApplicationConfiguration.apple_xcode_developer_directories:type_name -> buildbarn.configuration.bb_runner.ApplicationConfiguration.AppleXcodeDeveloperDirectoriesEntry + 1, // 5: buildbarn.configuration.bb_runner.ApplicationConfiguration.input_root_mounts:type_name -> buildbarn.configuration.bb_runner.InputMountOptions + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_pkg_proto_configuration_bb_runner_bb_runner_proto_init() } @@ -314,6 +401,18 @@ func file_pkg_proto_configuration_bb_runner_bb_runner_proto_init() { return nil } } + file_pkg_proto_configuration_bb_runner_bb_runner_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InputMountOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -321,7 +420,7 @@ func file_pkg_proto_configuration_bb_runner_bb_runner_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_pkg_proto_configuration_bb_runner_bb_runner_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/proto/configuration/bb_runner/bb_runner.proto b/pkg/proto/configuration/bb_runner/bb_runner.proto index 94c52514..f1700512 100644 --- a/pkg/proto/configuration/bb_runner/bb_runner.proto +++ b/pkg/proto/configuration/bb_runner/bb_runner.proto @@ -131,4 +131,37 @@ message ApplicationConfiguration { // https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/exec/local/XcodeLocalEnvProvider.java // https://www.smileykeith.com/2021/03/08/locking-xcode-in-bazel/ map apple_xcode_developer_directories = 14; + + // Mount special filesystems in the input root. This is useful when + // running with `chroot_into_input_root`. Some tools require access to + // special filesystems that are created when the operating system + // boots. An input root with a full userland implementation may need + // these. + // + // The mount point directories must exist in the input root. + // + // Typical choices are: + // + // inputRootMounts: [ + // { + // mountpoint: 'proc', + // source: '/proc', + // filesystemType: 'proc', + // }, + // { + // mountpoint: 'sys', + // source: '/sys', + // filesystemType: 'sysfs', + // }, + // ], + repeated InputMountOptions input_root_mounts = 15; +} + +message InputMountOptions { + // Mount a filesystem in the input root, a relative path. + string mountpoint = 1; + // Source filesystem from the runner's operating system. + string source = 2; + // Type of filesystem, see the mount(8) man page. + string filesystem_type = 3; } diff --git a/pkg/runner/BUILD.bazel b/pkg/runner/BUILD.bazel index de0c7fe9..c5c37b9d 100644 --- a/pkg/runner/BUILD.bazel +++ b/pkg/runner/BUILD.bazel @@ -11,6 +11,7 @@ go_library( "local_runner_rss_kibibytes.go", "local_runner_unix.go", "local_runner_windows.go", + "mounting_runner.go", "path_existence_checking_runner.go", "temporary_directory_installing_runner.go", "temporary_directory_symlinking_runner.go", @@ -19,6 +20,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/cleaner", + "//pkg/proto/configuration/bb_runner", "//pkg/proto/runner", "//pkg/proto/tmp_installer", "@com_github_buildbarn_bb_storage//pkg/filesystem", @@ -70,6 +72,7 @@ go_test( "apple_xcode_resolving_runner_test.go", "clean_runner_test.go", "local_runner_test.go", + "mounting_runner_test.go", "path_existence_checking_runner_test.go", "temporary_directory_symlinking_runner_test.go", ], @@ -77,6 +80,7 @@ go_test( ":runner", "//internal/mock", "//pkg/cleaner", + "//pkg/proto/configuration/bb_runner", "//pkg/proto/resourceusage", "//pkg/proto/runner", "@com_github_buildbarn_bb_storage//pkg/filesystem", diff --git a/pkg/runner/mounting_runner.go b/pkg/runner/mounting_runner.go new file mode 100644 index 00000000..45bfe64c --- /dev/null +++ b/pkg/runner/mounting_runner.go @@ -0,0 +1,103 @@ +package runner + +import ( + "context" + + "github.com/buildbarn/bb-remote-execution/pkg/proto/configuration/bb_runner" + runner_pb "github.com/buildbarn/bb-remote-execution/pkg/proto/runner" + "github.com/buildbarn/bb-storage/pkg/filesystem" + "github.com/buildbarn/bb-storage/pkg/filesystem/path" + "github.com/buildbarn/bb-storage/pkg/util" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type mountingRunner struct { + base runner_pb.RunnerServer + buildDirectory filesystem.Directory + mount *bb_runner.InputMountOptions +} + +// NewMountingRunner is a decorator for Runner +// that mounts `mount` before running a build action. +// +// This decorator can be used for chroot runners +// that must mount special filesystems into the input root. +func NewMountingRunner(base runner_pb.RunnerServer, buildDirectory filesystem.Directory, mount *bb_runner.InputMountOptions) runner_pb.RunnerServer { + return &mountingRunner{ + buildDirectory: buildDirectory, + mount: mount, + base: base, + } +} + +func (r *mountingRunner) Run(ctx context.Context, request *runner_pb.RunRequest) (response *runner_pb.RunResponse, err error) { + rootResolver := buildDirectoryPathResolver{ + stack: util.NewNonEmptyStack(filesystem.NopDirectoryCloser(r.buildDirectory)), + } + defer rootResolver.closeAll() + if err := path.Resolve(request.InputRootDirectory, path.NewRelativeScopeWalker(&rootResolver)); err != nil { + return nil, util.StatusWrap(err, "Invalid input root.") + } + + inputRoot := rootResolver.stack.Peek() + terminal := rootResolver.TerminalName + if terminal != nil { + inputRoot, err = inputRoot.EnterDirectory(*terminal) + if err != nil { + return nil, util.StatusWrap(err, "Invalid input root.") + } + } + + defer inputRoot.Close() + + mountResolver := buildDirectoryPathResolver{ + stack: util.NewNonEmptyStack(filesystem.NopDirectoryCloser(inputRoot)), + } + defer mountResolver.closeAll() + if err := path.Resolve(r.mount.Mountpoint, path.NewRelativeScopeWalker(&mountResolver)); err != nil { + return nil, util.StatusWrap(err, "Invalid mountpoint directory path.") + } + + mountDir := mountResolver.stack.Peek() + defer mountDir.Close() + if mountResolver.TerminalName == nil { + return nil, status.Errorf(codes.InvalidArgument, "Could not resolve mountpoint basename: %#v", r.mount.Mountpoint) + } + + mountname := *mountResolver.TerminalName + if err := mountDir.Mount(mountname, r.mount.Source, r.mount.FilesystemType); err != nil { + return nil, util.StatusWrapf(err, "Failed to mount %#v in the input root", r.mount.Mountpoint) + } + + // We only need the mounting directory file descriptor open. + rootResolver.closeAll() + // Already peeked the mounting directory + // we must now pop it to save it from the close iteration. + maybe, ok := mountResolver.stack.PopSingle() + if ok { + // PopSingle does not return anything if it is the initial element in the stack, + // which we got from Peek earlier. + defer maybe.Close() + mountDir = maybe + } + mountResolver.closeAll() + + response, err = r.base.Run(ctx, request) + if err != nil { + return nil, err + } + if err2 := mountDir.Unmount(mountname); err2 != nil { + err = util.StatusFromMultiple([]error{ + err, + util.StatusWrapf(err2, "Failed to unmount %#v in the input root", r.mount.Mountpoint), + }) + } + + return response, nil +} + +func (r *mountingRunner) CheckReadiness(ctx context.Context, request *runner_pb.CheckReadinessRequest) (*emptypb.Empty, error) { + return r.base.CheckReadiness(ctx, request) +} diff --git a/pkg/runner/mounting_runner_test.go b/pkg/runner/mounting_runner_test.go new file mode 100644 index 00000000..bafc8c4c --- /dev/null +++ b/pkg/runner/mounting_runner_test.go @@ -0,0 +1,98 @@ +package runner_test + +import ( + "context" + "path/filepath" + "testing" + + "github.com/buildbarn/bb-remote-execution/internal/mock" + "github.com/buildbarn/bb-remote-execution/pkg/proto/configuration/bb_runner" + runner_pb "github.com/buildbarn/bb-remote-execution/pkg/proto/runner" + "github.com/buildbarn/bb-remote-execution/pkg/runner" + "github.com/buildbarn/bb-storage/pkg/filesystem/path" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func TestMountingRunner(t *testing.T) { + ctrl, ctx := gomock.WithContext(context.Background(), t) + + mountpoint := "proc_inside" + mountpointComponent := path.MustNewComponent(mountpoint) + source := "/proc" + fstype := "proc" + + runRequest := &runner_pb.RunRequest{ + Arguments: []string{"ls", "-l", mountpoint}, + } + + t.Run("Run", func(t *testing.T) { + mockRunner := mock.NewMockRunnerServer(ctrl) + mockDirectory := mock.NewMockDirectory(ctrl) + + runnerServer := runner.NewMountingRunner(mockRunner, mockDirectory, &bb_runner.InputMountOptions{ + Mountpoint: mountpoint, + Source: source, + FilesystemType: fstype, + }) + mockDirectory.EXPECT().Mount(mountpointComponent, source, fstype) + mockDirectory.EXPECT().Unmount(mountpointComponent) + + mockRunner.EXPECT().Run(ctx, runRequest).Return(&runner_pb.RunResponse{}, nil) + _, err := runnerServer.Run(ctx, runRequest) + require.NoError(t, err) + }) + + t.Run("RunChained", func(t *testing.T) { + mockRunner := mock.NewMockRunnerServer(ctrl) + mockDirectory := mock.NewMockDirectory(ctrl) + + innerDecorator := runner.NewMountingRunner(mockRunner, mockDirectory, &bb_runner.InputMountOptions{ + Mountpoint: mountpoint, + Source: source, + FilesystemType: fstype, + }) + outer := []string{"sys_inside", "/sys", "sysfs"} + outerDecorator := runner.NewMountingRunner(innerDecorator, mockDirectory, &bb_runner.InputMountOptions{ + Mountpoint: outer[0], + Source: outer[1], + FilesystemType: outer[2], + }) + + gomock.InOrder( + mockDirectory.EXPECT().Mount(path.MustNewComponent(outer[0]), outer[1], outer[2]), + mockDirectory.EXPECT().Mount(mountpointComponent, source, fstype), + mockDirectory.EXPECT().Unmount(mountpointComponent), + mockDirectory.EXPECT().Unmount(path.MustNewComponent(outer[0])), + ) + + mockRunner.EXPECT().Run(ctx, runRequest).Return(&runner_pb.RunResponse{}, nil) + _, err := outerDecorator.Run(ctx, runRequest) + require.NoError(t, err) + }) + + t.Run("RunNestedMount", func(t *testing.T) { + mockRunner := mock.NewMockRunnerServer(ctrl) + mockDirectory := mock.NewMockDirectory(ctrl) + mockDirectoryCloser := mock.NewMockDirectoryCloser(ctrl) + + nestedMountpoint := path.MustNewComponent("nested") + parent := path.MustNewComponent("outer") + fullPath := filepath.Join(parent.String(), nestedMountpoint.String()) + runnerServer := runner.NewMountingRunner(mockRunner, mockDirectory, &bb_runner.InputMountOptions{ + Mountpoint: fullPath, + Source: source, + FilesystemType: fstype, + }) + + // The mount calls are called in a subdirectory + mockDirectory.EXPECT().EnterDirectory(parent).Return(mockDirectoryCloser, nil) + mockDirectoryCloser.EXPECT().Mount(nestedMountpoint, source, fstype) + mockDirectoryCloser.EXPECT().Unmount(nestedMountpoint) + mockDirectoryCloser.EXPECT().Close().Times(2) // Why twice ??? + + mockRunner.EXPECT().Run(ctx, runRequest).Return(&runner_pb.RunResponse{}, nil) + _, err := runnerServer.Run(ctx, runRequest) + require.NoError(t, err) + }) +}