diff --git a/entity/BUILD.bazel b/entity/BUILD.bazel index ccd288d0..344cc689 100644 --- a/entity/BUILD.bazel +++ b/entity/BUILD.bazel @@ -7,6 +7,7 @@ go_library( "batch_dependent.go", "build.go", "change_provider.go", + "land_entry.go", "queue_config.go", "request.go", "speculation_tree.go", diff --git a/entity/land_entry.go b/entity/land_entry.go new file mode 100644 index 00000000..22b97a2a --- /dev/null +++ b/entity/land_entry.go @@ -0,0 +1,10 @@ +package entity + +// LandEntry pairs a land strategy with the change to land. +// Each entry represents one request's contribution to a batch land operation. +type LandEntry struct { + // Strategy is the source control integration method for this change. + Strategy RequestLandStrategy + // Change is the code change to land. + Change Change +} diff --git a/extension/landprovider/BUILD.bazel b/extension/landprovider/BUILD.bazel new file mode 100644 index 00000000..34adfa64 --- /dev/null +++ b/extension/landprovider/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "landprovider", + srcs = [ + "errors.go", + "land_provider.go", + ], + importpath = "github.com/uber/submitqueue/extension/landprovider", + visibility = ["//visibility:public"], + deps = ["//entity"], +) + +exports_files( + ["land_provider.go"], + visibility = ["//extension/landprovider/mock:__pkg__"], +) diff --git a/extension/landprovider/errors.go b/extension/landprovider/errors.go new file mode 100644 index 00000000..5c7c95d0 --- /dev/null +++ b/extension/landprovider/errors.go @@ -0,0 +1,33 @@ +package landprovider + +import ( + "errors" + "fmt" +) + +// ErrLandRejected is returned by LandProvider implementations when the land operation +// was attempted but rejected due to the changes themselves (e.g., merge conflict, policy +// violation). This is a terminal failure — retrying will not help. +// Infrastructure errors (network timeout, API unavailable) should be returned as plain +// errors so the consumer can retry. +var ErrLandRejected = errors.New("land rejected") + +// ErrAlreadyLanded is returned by LandProvider implementations when the changes have +// already been landed (e.g., PR already merged). The extension reports this as a domain +// fact; the controller decides whether to treat it as success. +var ErrAlreadyLanded = errors.New("already landed") + +// IsLandRejected returns true if any error in the error chain is an ErrLandRejected. +func IsLandRejected(err error) bool { + return errors.Is(err, ErrLandRejected) +} + +// IsAlreadyLanded returns true if any error in the error chain is an ErrAlreadyLanded. +func IsAlreadyLanded(err error) bool { + return errors.Is(err, ErrAlreadyLanded) +} + +// WrapLandRejected wraps ErrLandRejected with a descriptive reason from the land provider. +func WrapLandRejected(err error) error { + return fmt.Errorf("%w: %w", ErrLandRejected, err) +} diff --git a/extension/landprovider/land_provider.go b/extension/landprovider/land_provider.go new file mode 100644 index 00000000..a83aaf2d --- /dev/null +++ b/extension/landprovider/land_provider.go @@ -0,0 +1,21 @@ +package landprovider + +//go:generate mockgen -source=land_provider.go -destination=mock/land_provider_mock.go -package=mock + +import ( + "context" + + "github.com/uber/submitqueue/entity" +) + +// LandProvider lands (merges) code changes into the target branch of a source +// control repository. Each implementation is configured for a specific provider +// (GitHub, GitLab, Phabricator). +type LandProvider interface { + // Land merges the provided changes into the target branch of the given queue. + // The queue identifies the repository and target branch. + // Each entry contains a change and the strategy to use for landing it. + // Returns ErrLandRejected if the land was rejected due to the changes themselves. + // Returns ErrAlreadyLanded if the changes have already been landed. + Land(ctx context.Context, queue string, entries []entity.LandEntry) error +} diff --git a/extension/landprovider/mock/BUILD.bazel b/extension/landprovider/mock/BUILD.bazel new file mode 100644 index 00000000..2e01efbb --- /dev/null +++ b/extension/landprovider/mock/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_go//extras:gomock.bzl", "gomock") +load("@rules_go//go:def.bzl", "go_library") + +_MOCKGEN = "@org_uber_go_mock//mockgen" + +gomock( + name = "mock_land_provider_src", + out = "land_provider_mock.go", + mockgen_tool = _MOCKGEN, + package = "mock", + source = "//extension/landprovider:land_provider.go", + source_importpath = "github.com/uber/submitqueue/extension/landprovider", +) + +# gazelle:ignore +go_library( + name = "mock", + srcs = [":mock_land_provider_src"], + importpath = "github.com/uber/submitqueue/extension/landprovider/mock", + visibility = ["//visibility:public"], + deps = [ + "//entity", + "//extension/landprovider", + "@org_uber_go_mock//gomock", + ], +)