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

@@ -2,10 +2,14 @@ package releasekit
import (
"context"
"errors"
"fmt"
"io"
"strings"
internalbuild "go.maximumdirect.net/releasekit/internal/build"
internaldelivery "go.maximumdirect.net/releasekit/internal/delivery"
"go.maximumdirect.net/releasekit/internal/forge"
"go.maximumdirect.net/releasekit/internal/gitea"
)
@@ -25,21 +29,57 @@ const (
DeliveryUnknown DeliveryState = "unknown"
)
// GiteaDestination supplies the hosted repository and explicit access token.
type GiteaDestination struct {
// Forge selects the hosted delivery backend.
type Forge string
const (
// ForgeGitea selects Gitea for hosted delivery.
ForgeGitea Forge = "gitea"
)
// DeliveryDestination supplies an explicitly selected hosted destination.
type DeliveryDestination struct {
Forge Forge
BaseURL string
Owner string
Repository string
Token string
}
// DeliveryPreflightFailureKind classifies a failure while checking a delivery
// destination's credentials and repository access.
type DeliveryPreflightFailureKind string
const (
DeliveryPreflightMissingCredential DeliveryPreflightFailureKind = "missing_credential"
DeliveryPreflightInvalidCredential DeliveryPreflightFailureKind = "invalid_credential"
DeliveryPreflightInsufficientPermissions DeliveryPreflightFailureKind = "insufficient_permissions"
DeliveryPreflightInaccessibleDestination DeliveryPreflightFailureKind = "inaccessible_destination"
DeliveryPreflightUnavailable DeliveryPreflightFailureKind = "unavailable"
)
// DeliveryPreflightError reports a classified failure to verify a destination.
type DeliveryPreflightError struct {
Kind DeliveryPreflightFailureKind
Err error
}
func (err *DeliveryPreflightError) Error() string {
if err.Err == nil {
return fmt.Sprintf("delivery preflight %s", err.Kind)
}
return fmt.Sprintf("delivery preflight %s: %v", err.Kind, err.Err)
}
func (err *DeliveryPreflightError) Unwrap() error { return err.Err }
// DeliveryAsset identifies a hosted asset by filename and SHA-256 digest.
type DeliveryAsset struct {
Filename string
SHA256 string
}
// DeliveryRequest identifies a local tagged checkout and its explicit Gitea
// DeliveryRequest identifies a local tagged checkout and its explicit
// destination. Build selects a managed build; Prebuilt supplies caller-owned
// build output instead. They cannot both be set. Either may be nil for
// page-only delivery when both are nil. Build with no targets, or an empty
@@ -54,7 +94,7 @@ type DeliveryRequest struct {
// ExpectedTag optionally requires the full CI event tag to match the
// selected module's derived tag before building or making hosted requests.
ExpectedTag string
Destination GiteaDestination
Destination DeliveryDestination
// Build selects binaries to build in temporary storage outside the checkout.
Build *BuildSpec
// Prebuilt supplies an existing build whose files remain caller-owned.
@@ -98,9 +138,9 @@ type DeliveryResult struct {
// managed builds require their selected source files in the working tree.
func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, error) {
result := DeliveryResult{Version: request.Version, State: DeliveryNotAttempted}
destination := gitea.Destination{
BaseURL: request.Destination.BaseURL, Owner: request.Destination.Owner,
Repository: request.Destination.Repository, Token: request.Destination.Token,
backend, err := deliveryBackend(request.Destination)
if err != nil {
return result, err
}
var prebuilt *internalbuild.Result
if request.Prebuilt != nil {
@@ -112,7 +152,7 @@ func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, erro
Product: request.Product,
Version: request.Version,
ExpectedTag: request.ExpectedTag,
Destination: destination,
Backend: backend,
Build: internalBuildSpec(request.Build),
Prebuilt: prebuilt,
Output: request.Output,
@@ -129,10 +169,59 @@ func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, erro
result.Pending = deliveryAssets(internalResult.Pending)
result.Conflicting = deliveryAssets(internalResult.Conflicting)
result.Uncertain = deliveryAssets(internalResult.Uncertain)
return result, err
return result, publicPreflightError(err)
}
func deliveryAssets(assets []gitea.AssetIdentity) []DeliveryAsset {
// PreflightDelivery checks destination credentials and repository access using
// read-only backend operations. It does not require a checkout or published tag.
func PreflightDelivery(ctx context.Context, destination DeliveryDestination) error {
backend, err := deliveryBackend(destination)
if err != nil {
return err
}
return publicPreflightError(backend.Preflight(ctx))
}
func deliveryBackend(destination DeliveryDestination) (forge.Backend, error) {
selection := Forge(strings.TrimSpace(string(destination.Forge)))
switch selection {
case ForgeGitea:
return gitea.NewBackend(gitea.Destination{
BaseURL: destination.BaseURL, Owner: destination.Owner,
Repository: destination.Repository, Token: destination.Token,
}, nil), nil
case "":
return nil, errors.New("delivery forge must be selected explicitly")
default:
return nil, fmt.Errorf("unsupported delivery forge %q", destination.Forge)
}
}
func publicPreflightError(err error) error {
if err == nil {
return nil
}
var failure *forge.PreflightFailure
if !errors.As(err, &failure) {
return err
}
kind := DeliveryPreflightUnavailable
switch failure.Kind {
case forge.PreflightMissingCredential:
kind = DeliveryPreflightMissingCredential
case forge.PreflightInvalidCredential:
kind = DeliveryPreflightInvalidCredential
case forge.PreflightInsufficientPermissions:
kind = DeliveryPreflightInsufficientPermissions
case forge.PreflightInaccessibleDestination:
kind = DeliveryPreflightInaccessibleDestination
case forge.PreflightUnavailable:
kind = DeliveryPreflightUnavailable
}
return &DeliveryPreflightError{Kind: kind, Err: failure.Err}
}
func deliveryAssets(assets []internaldelivery.AssetIdentity) []DeliveryAsset {
result := make([]DeliveryAsset, 0, len(assets))
for _, asset := range assets {
result = append(result, DeliveryAsset{Filename: asset.Filename, SHA256: asset.SHA256})