Connect neutral delivery to the Gitea CLI

This commit is contained in:
2026-11-01 18:20:01 +00:00
parent b350c71246
commit 2e617db23c
21 changed files with 275 additions and 97 deletions

View File

@@ -12,8 +12,8 @@ import (
"go.maximumdirect.net/releasekit/internal/artifact"
internalbuild "go.maximumdirect.net/releasekit/internal/build"
"go.maximumdirect.net/releasekit/internal/candidate"
"go.maximumdirect.net/releasekit/internal/forge"
"go.maximumdirect.net/releasekit/internal/git"
"go.maximumdirect.net/releasekit/internal/gitea"
"go.maximumdirect.net/releasekit/internal/gotool"
"go.maximumdirect.net/releasekit/internal/releaseidentity"
"go.maximumdirect.net/releasekit/internal/tempdir"
@@ -36,7 +36,7 @@ type Request struct {
Product string
Version string
ExpectedTag string
Destination gitea.Destination
Backend forge.Backend
Build *internalbuild.Spec
Prebuilt *internalbuild.Result
Output io.Writer
@@ -50,11 +50,11 @@ type Result struct {
CandidateCommit string
ReleaseURL string
State State
Matching []gitea.AssetIdentity
Uploaded []gitea.AssetIdentity
Pending []gitea.AssetIdentity
Conflicting []gitea.AssetIdentity
Uncertain []gitea.AssetIdentity
Matching []AssetIdentity
Uploaded []AssetIdentity
Pending []AssetIdentity
Conflicting []AssetIdentity
Uncertain []AssetIdentity
}
func Deliver(ctx context.Context, request Request) (Result, error) {
@@ -68,6 +68,9 @@ func Deliver(ctx context.Context, request Request) (Result, error) {
if request.Build != nil && request.Prebuilt != nil {
return result, errors.New("delivery cannot use both a managed build and prebuilt artifacts")
}
if request.Backend == nil {
return result, errors.New("delivery backend is required")
}
repositoryDir, err := git.RepositoryRoot(ctx, request.RepositoryDir)
if err != nil {
return result, fmt.Errorf("resolve repository directory: %w", err)
@@ -112,6 +115,14 @@ func Deliver(ctx context.Context, request Request) (Result, error) {
if err := candidate.ValidateReleaseNote(note, request.Product, request.Version); err != nil {
return result, fmt.Errorf("release note %q: %w", identity.NotePath, err)
}
if request.Prebuilt != nil {
if err := validateBuildIdentity(identity, commit, request.Prebuilt); err != nil {
return result, err
}
}
if err := request.Backend.Preflight(ctx); err != nil {
return result, err
}
build := request.Prebuilt
if request.Build != nil && len(request.Build.Targets) != 0 {
@@ -144,11 +155,11 @@ func Deliver(ctx context.Context, request Request) (Result, error) {
}
if len(assets) > 0 {
if err := artifact.VerifyLocalAssets(assets); err != nil {
kind := gitea.FailureInvalid
kind := forge.FailureInvalid
if errors.Is(err, artifact.ErrAssetConflict) {
kind = gitea.FailureConflict
kind = forge.FailureConflict
}
return result, &gitea.Failure{Kind: kind, Operation: "verify build assets", Err: err}
return result, &forge.Failure{Kind: kind, Err: fmt.Errorf("verify build assets: %w", err)}
}
}
if build != nil && len(build.Settings.Targets) != 0 {
@@ -164,38 +175,33 @@ func Deliver(ctx context.Context, request Request) (Result, error) {
}
}
releaseResult, err := (gitea.Client{}).EnsureRelease(ctx, gitea.Request{
Destination: request.Destination,
Tag: identity.Tag,
Commit: commit,
Note: string(note),
})
release, pageEvidence, err := coordinateRelease(ctx, request.Backend, identity.Tag, commit, string(note))
if err != nil {
result.State = stateForError(err, false)
return result, err
}
result.ReleaseURL = releaseResult.Release.URL
result.ReleaseURL = release.URL
if len(assets) == 0 {
result.State = Complete
return result, nil
}
inspection, err := (gitea.Client{}).InspectAssets(ctx, request.Destination, releaseResult.Release.ID, assets)
inspection, err := inspectRemoteAssets(ctx, request.Backend, release.Handle, assets)
if err != nil {
result.State = NotAttempted
if releaseResult.WriteAttempted {
if pageEvidence.Attempted {
result.State = Partial
}
result.Uncertain = assetIdentities(assets)
return result, err
}
uploadResult, uploadErr := (gitea.Client{}).UploadVerifiedMissing(ctx, request.Destination, releaseResult.Release.ID, inspection)
uploadResult, uploadErr := uploadMissingAssets(ctx, request.Backend, release.Handle, inspection)
result.Matching = uploadResult.Matching
result.Uploaded = uploadResult.Uploaded
result.Pending = uploadResult.Pending
result.Conflicting = uploadResult.Conflicting
result.Uncertain = uploadResult.Uncertain
result.State = State(uploadResult.State)
if result.State == NotAttempted && releaseResult.WriteAttempted {
if result.State == NotAttempted && pageEvidence.Attempted {
result.State = Partial
}
return result, uploadErr
@@ -208,6 +214,22 @@ func validateBuild(identity releaseidentity.Identity, commit string, build *inte
}
return nil
}
if err := validateBuildIdentity(identity, commit, build); err != nil {
return err
}
if len(build.Settings.Targets) == 0 {
if len(assets) != 0 {
return errors.New("page-only delivery cannot include binary assets")
}
return nil
}
if len(assets) != len(build.Settings.Targets)+1 || assets[len(assets)-1].Filename != artifact.ManifestFilename {
return errors.New("build result does not contain the selected artifacts and complete checksum manifest")
}
return nil
}
func validateBuildIdentity(identity releaseidentity.Identity, commit string, build *internalbuild.Result) error {
buildRepository, err := filepath.Abs(build.RepositoryDir)
if err != nil {
return fmt.Errorf("resolve build repository: %w", err)
@@ -220,27 +242,24 @@ func validateBuild(identity releaseidentity.Identity, commit string, build *inte
return errors.New("build result does not identify the selected tagged module")
}
if len(build.Settings.Targets) == 0 {
if len(assets) != 0 {
return errors.New("page-only delivery cannot include binary assets")
}
return nil
}
if !build.SourceVerified {
return errors.New("build source was not verified as clean and unchanged during compilation")
}
if strings.TrimSpace(build.Settings.MainPackage) == "" || strings.TrimSpace(build.Settings.BinaryName) == "" || len(assets) != len(build.Settings.Targets)+1 || assets[len(assets)-1].Filename != artifact.ManifestFilename {
if strings.TrimSpace(build.Settings.MainPackage) == "" || strings.TrimSpace(build.Settings.BinaryName) == "" {
return errors.New("build result does not contain the selected artifacts and complete checksum manifest")
}
return nil
}
func stateForError(err error, pageCreated bool) State {
var failure *gitea.Failure
var failure *forge.Failure
if errors.As(err, &failure) {
switch failure.Kind {
case gitea.FailureConflict:
case forge.FailureConflict:
return Conflict
case gitea.FailureUnknown:
case forge.FailureUnknown:
return Unknown
}
}
@@ -250,10 +269,10 @@ func stateForError(err error, pageCreated bool) State {
return NotAttempted
}
func assetIdentities(assets []artifact.ExpectedAsset) []gitea.AssetIdentity {
result := make([]gitea.AssetIdentity, 0, len(assets))
func assetIdentities(assets []artifact.ExpectedAsset) []AssetIdentity {
result := make([]AssetIdentity, 0, len(assets))
for _, asset := range assets {
result = append(result, gitea.AssetIdentity{Filename: asset.Filename, SHA256: asset.SHA256})
result = append(result, AssetIdentity{Filename: asset.Filename, SHA256: asset.SHA256})
}
return result
}