Connect neutral delivery to the Gitea CLI
This commit is contained in:
@@ -815,7 +815,7 @@ func main(){ if len(os.Args)>1 && os.Args[1]=="version" {fmt.Println("fixture",b
|
|||||||
}
|
}
|
||||||
buildGit(t, repository, "restore", "cmd/app/main.go")
|
buildGit(t, repository, "restore", "cmd/app/main.go")
|
||||||
buildGit(t, repository, "checkout", "--detach", "v1.2.3")
|
buildGit(t, repository, "checkout", "--detach", "v1.2.3")
|
||||||
delivered, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Prebuilt: &result})
|
delivered, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3", Destination: releasekit.DeliveryDestination{Forge: releasekit.ForgeGitea}, Prebuilt: &result})
|
||||||
if err == nil || !strings.Contains(err.Error(), "build source") || delivered.State != releasekit.DeliveryNotAttempted {
|
if err == nil || !strings.Contains(err.Error(), "build source") || delivered.State != releasekit.DeliveryNotAttempted {
|
||||||
t.Fatalf("dirty source delivered: %#v %v", delivered, err)
|
t.Fatalf("dirty source delivered: %#v %v", delivered, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func TestBuildAndPrebuiltDeliveryUseSameDirectoryBase(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
settings := cli.Settings{Product: "Example Product", ModuleDir: tt.module,
|
settings := cli.Settings{Product: "Example Product", ModuleDir: tt.module,
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
}
|
}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
cliGitRun(t, repository, "add", ".")
|
cliGitRun(t, repository, "add", ".")
|
||||||
@@ -94,9 +94,9 @@ func TestBuildAndPrebuiltDeliveryUseSameDirectoryBase(t *testing.T) {
|
|||||||
if err := os.WriteFile(receipt.Artifacts[0].Path, []byte("changed"), 0o600); err != nil {
|
if err := os.WriteFile(receipt.Artifacts[0].Path, []byte("changed"), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
requestsBefore := state.snapshot().apiRequests
|
writesBefore := state.snapshot().writes
|
||||||
if status := run(context.Background(), args, &stdout, &stderr); status != 1 || state.snapshot().apiRequests != requestsBefore {
|
if status := run(context.Background(), args, &stdout, &stderr); status != 1 || state.snapshot().writes != writesBefore {
|
||||||
t.Fatalf("changed content status=%d, requests=%d->%d, stderr=%s", status, requestsBefore, state.snapshot().apiRequests, stderr.String())
|
t.Fatalf("changed content status=%d, writes=%d->%d, stderr=%s", status, writesBefore, state.snapshot().writes, stderr.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func TestPageOnlyDeliveryUsesCommittedModuleInSparseCheckout(t *testing.T) {
|
|||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", ModuleDir: module,
|
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", ModuleDir: module,
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", state.snapshot().tag)
|
cliGitRun(t, repository, "tag", state.snapshot().tag)
|
||||||
|
|||||||
@@ -196,8 +196,9 @@ type GoValidationSettings struct {
|
|||||||
CompileTargets []string `json:"compile_targets,omitempty"`
|
CompileTargets []string `json:"compile_targets,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverySettings selects the hosted Gitea release destination.
|
// DeliverySettings selects the hosted forge release destination.
|
||||||
type DeliverySettings struct {
|
type DeliverySettings struct {
|
||||||
|
Forge string `json:"forge,omitempty"`
|
||||||
BaseURL string `json:"base_url"`
|
BaseURL string `json:"base_url"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Repository string `json:"repository"`
|
Repository string `json:"repository"`
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestMainModuleWithoutDomainSupportsCheckBuildAndPageDelivery(t *testing.T)
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
settings := cli.Settings{Product: "Example Product",
|
settings := cli.Settings{Product: "Example Product",
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
}
|
}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
for _, args := range [][]string{
|
for _, args := range [][]string{
|
||||||
|
|||||||
@@ -296,6 +296,10 @@ func runCIDelivery(ctx context.Context, invocation cli.Invocation, stdout, stder
|
|||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
if settings.Build != nil && len(settings.Build.Targets) != 0 {
|
if settings.Build != nil && len(settings.Build.Targets) != 0 {
|
||||||
|
if err := releasekit.PreflightDelivery(ctx, destination); err != nil {
|
||||||
|
fmt.Fprintf(stderr, "delivery credential preflight: %v\n", err)
|
||||||
|
return deliveryPreflightExitStatus(err)
|
||||||
|
}
|
||||||
if err := releasekit.PrepareBuildDependencies(ctx, releasekit.DependencyPreparationRequest{
|
if err := releasekit.PrepareBuildDependencies(ctx, releasekit.DependencyPreparationRequest{
|
||||||
RepositoryDir: invocation.Repository,
|
RepositoryDir: invocation.Repository,
|
||||||
ModuleDir: settings.ModuleDir,
|
ModuleDir: settings.ModuleDir,
|
||||||
@@ -317,11 +321,24 @@ func deliverWithSettings(ctx context.Context, invocation cli.Invocation, setting
|
|||||||
return deliverWithDestination(ctx, invocation, settings, destination, stdout, stderr)
|
return deliverWithDestination(ctx, invocation, settings, destination, stdout, stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deliveryDestination(repositoryDir string, settings cli.Settings, stderr io.Writer) (releasekit.GiteaDestination, int) {
|
func deliveryDestination(repositoryDir string, settings cli.Settings, stderr io.Writer) (releasekit.DeliveryDestination, int) {
|
||||||
delivery := settings.Delivery
|
delivery := settings.Delivery
|
||||||
if delivery == nil || strings.TrimSpace(delivery.BaseURL) == "" || strings.TrimSpace(delivery.Owner) == "" || strings.TrimSpace(delivery.Repository) == "" {
|
if delivery == nil {
|
||||||
|
fmt.Fprintln(stderr, "deliver requires an explicit delivery.forge selection in the profile")
|
||||||
|
return releasekit.DeliveryDestination{}, 2
|
||||||
|
}
|
||||||
|
switch strings.TrimSpace(delivery.Forge) {
|
||||||
|
case "":
|
||||||
|
fmt.Fprintln(stderr, "deliver requires delivery.forge in the profile")
|
||||||
|
return releasekit.DeliveryDestination{}, 2
|
||||||
|
case string(releasekit.ForgeGitea):
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(stderr, "unsupported delivery forge %q\n", delivery.Forge)
|
||||||
|
return releasekit.DeliveryDestination{}, 2
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(delivery.BaseURL) == "" || strings.TrimSpace(delivery.Owner) == "" || strings.TrimSpace(delivery.Repository) == "" {
|
||||||
fmt.Fprintln(stderr, "deliver requires base_url, owner, and repository in the profile delivery settings")
|
fmt.Fprintln(stderr, "deliver requires base_url, owner, and repository in the profile delivery settings")
|
||||||
return releasekit.GiteaDestination{}, 2
|
return releasekit.DeliveryDestination{}, 2
|
||||||
}
|
}
|
||||||
tokenEnv := delivery.TokenEnv
|
tokenEnv := delivery.TokenEnv
|
||||||
if strings.TrimSpace(tokenEnv) == "" {
|
if strings.TrimSpace(tokenEnv) == "" {
|
||||||
@@ -336,22 +353,22 @@ func deliveryDestination(repositoryDir string, settings cli.Settings, stderr io.
|
|||||||
contents, err := os.ReadFile(name)
|
contents, err := os.ReadFile(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "read delivery token file: %v\n", err)
|
fmt.Fprintf(stderr, "read delivery token file: %v\n", err)
|
||||||
return releasekit.GiteaDestination{}, 2
|
return releasekit.DeliveryDestination{}, 2
|
||||||
}
|
}
|
||||||
token = strings.TrimSpace(string(contents))
|
token = strings.TrimSpace(string(contents))
|
||||||
if token == "" {
|
if token == "" {
|
||||||
fmt.Fprintln(stderr, "delivery token file is empty or blank")
|
fmt.Fprintln(stderr, "delivery token file is empty or blank")
|
||||||
return releasekit.GiteaDestination{}, 2
|
return releasekit.DeliveryDestination{}, 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(token) == "" {
|
if strings.TrimSpace(token) == "" {
|
||||||
fmt.Fprintf(stderr, "deliver requires a non-empty token in environment variable %s\n", tokenEnv)
|
fmt.Fprintf(stderr, "deliver requires a non-empty token in environment variable %s\n", tokenEnv)
|
||||||
return releasekit.GiteaDestination{}, 2
|
return releasekit.DeliveryDestination{}, 2
|
||||||
}
|
}
|
||||||
return releasekit.GiteaDestination{BaseURL: delivery.BaseURL, Owner: delivery.Owner, Repository: delivery.Repository, Token: token}, 0
|
return releasekit.DeliveryDestination{Forge: releasekit.ForgeGitea, BaseURL: delivery.BaseURL, Owner: delivery.Owner, Repository: delivery.Repository, Token: token}, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func deliverWithDestination(ctx context.Context, invocation cli.Invocation, settings cli.Settings, destination releasekit.GiteaDestination, stdout, stderr io.Writer) int {
|
func deliverWithDestination(ctx context.Context, invocation cli.Invocation, settings cli.Settings, destination releasekit.DeliveryDestination, stdout, stderr io.Writer) int {
|
||||||
hasTargets := settings.Build != nil && len(settings.Build.Targets) != 0
|
hasTargets := settings.Build != nil && len(settings.Build.Targets) != 0
|
||||||
if invocation.AssetsDir != "" && !hasTargets {
|
if invocation.AssetsDir != "" && !hasTargets {
|
||||||
fmt.Fprintln(stderr, "--assets requires build targets in the profile")
|
fmt.Fprintln(stderr, "--assets requires build targets in the profile")
|
||||||
@@ -406,6 +423,10 @@ func deliverWithDestination(ctx context.Context, invocation cli.Invocation, sett
|
|||||||
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
var preflightErr *releasekit.DeliveryPreflightError
|
||||||
|
if errors.As(err, &preflightErr) {
|
||||||
|
return deliveryPreflightExitStatus(preflightErr)
|
||||||
|
}
|
||||||
if result.State == releasekit.DeliveryUnknown {
|
if result.State == releasekit.DeliveryUnknown {
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
@@ -419,6 +440,22 @@ func deliverWithDestination(ctx context.Context, invocation cli.Invocation, sett
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deliveryPreflightExitStatus(err error) int {
|
||||||
|
var preflightErr *releasekit.DeliveryPreflightError
|
||||||
|
if !errors.As(err, &preflightErr) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
switch preflightErr.Kind {
|
||||||
|
case releasekit.DeliveryPreflightMissingCredential,
|
||||||
|
releasekit.DeliveryPreflightInvalidCredential,
|
||||||
|
releasekit.DeliveryPreflightInsufficientPermissions,
|
||||||
|
releasekit.DeliveryPreflightInaccessibleDestination:
|
||||||
|
return 2
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func writeDeliveryAssets(output io.Writer, result releasekit.DeliveryResult) {
|
func writeDeliveryAssets(output io.Writer, result releasekit.DeliveryResult) {
|
||||||
for _, group := range []struct {
|
for _, group := range []struct {
|
||||||
label string
|
label string
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ func TestDeliverPageOnlyCreatesAndReusesRelease(t *testing.T) {
|
|||||||
settings := cli.Settings{
|
settings := cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Validation: &cli.ValidationSettings{Go: cli.GoValidationSettings{CompileTargets: []string{"linux/amd64"}}},
|
Validation: &cli.ValidationSettings{Go: cli.GoValidationSettings{CompileTargets: []string{"linux/amd64"}}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
}
|
}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
@@ -673,7 +673,7 @@ func TestDeliverBuildsTaggedArtifactsAndReusesAssets(t *testing.T) {
|
|||||||
settings := cli.Settings{
|
settings := cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
}
|
}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
@@ -716,7 +716,7 @@ func TestCIDeliverLoadsTaggedProfileAndSkipsUnavailablePageOnlyDependencies(t *t
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfileAt(t, repository, "profiles/ci-delivery.json", cli.Settings{
|
writeDeliveryProfileAt(t, repository, "profiles/ci-delivery.json", cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
expectedCommit := pushTaggedCLIRepository(t, repository)
|
expectedCommit := pushTaggedCLIRepository(t, repository)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
||||||
@@ -751,7 +751,7 @@ func TestCIDeliverRejectsWrongCommitBeforeGoOrHostedRequests(t *testing.T) {
|
|||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
expectedCommit := pushTaggedCLIRepository(t, repository)
|
expectedCommit := pushTaggedCLIRepository(t, repository)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
||||||
@@ -802,7 +802,7 @@ func TestCIDeliverRejectsExpectedModuleMismatchBeforeDependencyPreparation(t *te
|
|||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
ModuleDir: "integrations/other",
|
ModuleDir: "integrations/other",
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
expectedCommit := pushTaggedCLIRepository(t, repository)
|
expectedCommit := pushTaggedCLIRepository(t, repository)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
||||||
@@ -839,7 +839,7 @@ func TestCIDeliverPreparesDependenciesAndBuildsConfiguredTargets(t *testing.T) {
|
|||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
expectedCommit := pushTaggedCLIRepository(t, repository)
|
expectedCommit := pushTaggedCLIRepository(t, repository)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
||||||
@@ -865,7 +865,7 @@ func TestCIDeliverValidatesCredentialsBeforePreparingDependencies(t *testing.T)
|
|||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
expectedCommit := pushTaggedCLIRepository(t, repository)
|
expectedCommit := pushTaggedCLIRepository(t, repository)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
state.update(func(state *deliveryServerData) { state.commit = expectedCommit })
|
||||||
@@ -903,7 +903,7 @@ func TestDeliverRejectsInvalidTaggedSourceBeforeManagedBuild(t *testing.T) {
|
|||||||
settings := cli.Settings{
|
settings := cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
}
|
}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
@@ -958,7 +958,7 @@ func TestDeliverFailureReportsRecoveryForPartialConflictAndUnknownStates(t *test
|
|||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Build: &build,
|
Build: &build,
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
@@ -1002,7 +1002,7 @@ func TestDeliverRejectsWrongHostedTagBeforeReleaseWrite(t *testing.T) {
|
|||||||
state := &deliveryServerState{data: deliveryServerData{remoteCommit: strings.Repeat("0", 40)}}
|
state := &deliveryServerState{data: deliveryServerData{remoteCommit: strings.Repeat("0", 40)}}
|
||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
t.Setenv("RELEASEKIT_GITEA_TOKEN", "fixture-token")
|
t.Setenv("RELEASEKIT_GITEA_TOKEN", "fixture-token")
|
||||||
@@ -1018,7 +1018,7 @@ func TestDeliverRequiresCredentialBeforeRemoteRequests(t *testing.T) {
|
|||||||
state := &deliveryServerState{}
|
state := &deliveryServerState{}
|
||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example", TokenEnv: "RELEASEKIT_TEST_DELIVERY_TOKEN"}})
|
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example", TokenEnv: "RELEASEKIT_TEST_DELIVERY_TOKEN"}})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
t.Setenv("RELEASEKIT_TEST_DELIVERY_TOKEN", "")
|
t.Setenv("RELEASEKIT_TEST_DELIVERY_TOKEN", "")
|
||||||
@@ -1034,7 +1034,7 @@ func TestDeliverRejectsCheckoutThatDiffersFromLocalTag(t *testing.T) {
|
|||||||
state := &deliveryServerState{}
|
state := &deliveryServerState{}
|
||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
if err := os.WriteFile(filepath.Join(repository, "later.txt"), []byte("later commit"), 0o600); err != nil {
|
if err := os.WriteFile(filepath.Join(repository, "later.txt"), []byte("later commit"), 0o600); err != nil {
|
||||||
@@ -1061,7 +1061,7 @@ func TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites(t *testin
|
|||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
current := cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}
|
current := cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}
|
||||||
settings := cli.Settings{Product: "Example Product", Build: ¤t, Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}}
|
settings := cli.Settings{Product: "Example Product", Build: ¤t, Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"}}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
@@ -1097,7 +1097,7 @@ func TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites(t *testin
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status != 1 || state.snapshot().apiRequests != 0 || state.snapshot().releaseCreates != 0 || state.snapshot().assetUploads != 0 {
|
if status != 1 || state.snapshot().writes != 0 {
|
||||||
t.Fatalf("%s receipt status=%d requests=%d stdout=%q stderr=%q", mode, status, state.snapshot().apiRequests, stdout.String(), stderr.String())
|
t.Fatalf("%s receipt status=%d requests=%d stdout=%q stderr=%q", mode, status, state.snapshot().apiRequests, stdout.String(), stderr.String())
|
||||||
}
|
}
|
||||||
if mode == "tampered" || mode == "missing artifact" {
|
if mode == "tampered" || mode == "missing artifact" {
|
||||||
@@ -1174,6 +1174,7 @@ type deliveryServerData struct {
|
|||||||
product string
|
product string
|
||||||
remoteCommit string
|
remoteCommit string
|
||||||
apiRequests int
|
apiRequests int
|
||||||
|
writes int
|
||||||
pageExists bool
|
pageExists bool
|
||||||
releaseCreates int
|
releaseCreates int
|
||||||
assetUploads int
|
assetUploads int
|
||||||
@@ -1196,11 +1197,19 @@ func newDeliveryServer(t *testing.T, state *deliveryServerState) *httptest.Serve
|
|||||||
defer state.mu.Unlock()
|
defer state.mu.Unlock()
|
||||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||||
data.apiRequests++
|
data.apiRequests++
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
data.writes++
|
||||||
|
}
|
||||||
if r.Header.Get("Authorization") != "token fixture-token" {
|
if r.Header.Get("Authorization") != "token fixture-token" {
|
||||||
t.Errorf("authorization = %q", r.Header.Get("Authorization"))
|
t.Errorf("authorization = %q", r.Header.Get("Authorization"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
|
case "/api/v1/repos/owner/example", "/api/v1/repositories/1":
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"id": 1, "name": "example", "full_name": "owner/example",
|
||||||
|
"owner": map[string]string{"login": "owner"},
|
||||||
|
})
|
||||||
case "/api/v1/repos/owner/example/tags/" + data.tag:
|
case "/api/v1/repos/owner/example/tags/" + data.tag:
|
||||||
if data.commit == "" {
|
if data.commit == "" {
|
||||||
t.Error("tag source commit was not initialized")
|
t.Error("tag source commit was not initialized")
|
||||||
@@ -1445,7 +1454,7 @@ func TestDeliverRejectsReceiptBuiltFromDirtySourceAfterCleanup(t *testing.T) {
|
|||||||
state := &deliveryServerState{}
|
state := &deliveryServerState{}
|
||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
settings := cli.Settings{Product: "Example Product", Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}, Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}}
|
settings := cli.Settings{Product: "Example Product", Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}, Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"}}
|
||||||
writeDeliveryProfile(t, repository, settings)
|
writeDeliveryProfile(t, repository, settings)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
@@ -1485,7 +1494,7 @@ func TestRecoveredPageCreatePreservesPartialDeliveryOnInspectionFailure(t *testi
|
|||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
build := cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}
|
build := cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Build: &build, Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Build: &build, Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
assets, _ := writeDeliveryReceipt(t, repository, build)
|
assets, _ := writeDeliveryReceipt(t, repository, build)
|
||||||
@@ -1505,7 +1514,7 @@ func TestUnreadableAssetsDoNotClaimAnUnknownWrite(t *testing.T) {
|
|||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
build := cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}
|
build := cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Build: &build, Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Build: &build, Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
assets, _ := writeDeliveryReceipt(t, repository, build)
|
assets, _ := writeDeliveryReceipt(t, repository, build)
|
||||||
@@ -1561,7 +1570,7 @@ func TestTemporaryDeliveryBuildDoesNotRequireWritableRepositoryParent(t *testing
|
|||||||
state := &deliveryServerState{}
|
state := &deliveryServerState{}
|
||||||
server := newDeliveryServer(t, state)
|
server := newDeliveryServer(t, state)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}, Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
writeDeliveryProfile(t, repository, cli.Settings{Product: "Example Product", Build: &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}, Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"}})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "v1.2.3")
|
cliGitRun(t, repository, "tag", "v1.2.3")
|
||||||
parent := t.TempDir()
|
parent := t.TempDir()
|
||||||
|
|||||||
@@ -209,6 +209,11 @@ func newMappedDeliveryFixture(t *testing.T, tag string) *mappedDeliveryFixture {
|
|||||||
apiRoot := "/api/v1/repos/owner/example/"
|
apiRoot := "/api/v1/repos/owner/example/"
|
||||||
escapedTag := url.PathEscape(tag)
|
escapedTag := url.PathEscape(tag)
|
||||||
switch r.URL.EscapedPath() {
|
switch r.URL.EscapedPath() {
|
||||||
|
case "/api/v1/repos/owner/example", "/api/v1/repositories/1":
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"id": 1, "name": "example", "full_name": "owner/example",
|
||||||
|
"owner": map[string]string{"login": "owner"},
|
||||||
|
})
|
||||||
case apiRoot + "tags/" + escapedTag:
|
case apiRoot + "tags/" + escapedTag:
|
||||||
_ = json.NewEncoder(w).Encode(map[string]any{"name": tag, "commit": map[string]string{"sha": data.commit}})
|
_ = json.NewEncoder(w).Encode(map[string]any{"name": tag, "commit": map[string]string{"sha": data.commit}})
|
||||||
case apiRoot + "releases/tags/" + escapedTag:
|
case apiRoot + "releases/tags/" + escapedTag:
|
||||||
@@ -366,7 +371,7 @@ func workflowModuleFile(module, version string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func workflowDeliverySettings(baseURL string) *cli.DeliverySettings {
|
func workflowDeliverySettings(baseURL string) *cli.DeliverySettings {
|
||||||
return &cli.DeliverySettings{BaseURL: baseURL, Owner: "owner", Repository: "example", TokenEnv: "RELEASEKIT_GITEA_TOKEN"}
|
return &cli.DeliverySettings{Forge: "gitea", BaseURL: baseURL, Owner: "owner", Repository: "example", TokenEnv: "RELEASEKIT_GITEA_TOKEN"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeMappedProfile(t *testing.T, repository, name string, settings cli.Settings) {
|
func writeMappedProfile(t *testing.T, repository, name string, settings cli.Settings) {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ func TestReleaseCandidate(t *testing.T) {
|
|||||||
"product": "ReleaseKit",
|
"product": "ReleaseKit",
|
||||||
"checks": []map[string]any{{"args": []string{"sh", "-c", "if [ -n \"$RELEASEKIT_TEST_ARM_GIT_SHIM\" ]; then : > \"$RELEASEKIT_TEST_ARM_GIT_SHIM\"; fi"}}},
|
"checks": []map[string]any{{"args": []string{"sh", "-c", "if [ -n \"$RELEASEKIT_TEST_ARM_GIT_SHIM\" ]; then : > \"$RELEASEKIT_TEST_ARM_GIT_SHIM\"; fi"}}},
|
||||||
"delivery": map[string]any{
|
"delivery": map[string]any{
|
||||||
|
"forge": "gitea",
|
||||||
"base_url": "https://forge.maximumdirect.net",
|
"base_url": "https://forge.maximumdirect.net",
|
||||||
"owner": "base-layer",
|
"owner": "base-layer",
|
||||||
"repository": "releasekit",
|
"repository": "releasekit",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "ReleaseKit",
|
Product: "ReleaseKit",
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
state.update(func(state *deliveryServerData) { state.commit = cliGitOutput(t, repository, "rev-parse", "HEAD") })
|
||||||
cliGitRun(t, repository, "tag", "-a", version, "-m", "ReleaseKit v1.2.3")
|
cliGitRun(t, repository, "tag", "-a", version, "-m", "ReleaseKit v1.2.3")
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func TestDeliveryTokenFileSelection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Setenv("RELEASEKIT_TEST_TOKEN", test.env)
|
t.Setenv("RELEASEKIT_TEST_TOKEN", test.env)
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
destination, status := deliveryDestination(repository, cli.Settings{Delivery: &cli.DeliverySettings{
|
destination, status := deliveryDestination(repository, cli.Settings{Delivery: &cli.DeliverySettings{Forge: "gitea",
|
||||||
BaseURL: "https://gitea.example", Owner: "owner", Repository: "example",
|
BaseURL: "https://gitea.example", Owner: "owner", Repository: "example",
|
||||||
TokenEnv: "RELEASEKIT_TEST_TOKEN", TokenFile: name,
|
TokenEnv: "RELEASEKIT_TEST_TOKEN", TokenFile: name,
|
||||||
}}, &stderr)
|
}}, &stderr)
|
||||||
@@ -58,7 +58,7 @@ func TestCheckReadsForbiddenFilesSelectionFromProfile(t *testing.T) {
|
|||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Validation: &cli.ValidationSettings{ForbiddenFiles: []string{"token-file"}},
|
Validation: &cli.ValidationSettings{ForbiddenFiles: []string{"token-file"}},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: "https://gitea.example", Owner: "owner", Repository: "example", TokenFile: "missing-token-file"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: "https://gitea.example", Owner: "owner", Repository: "example", TokenFile: "missing-token-file"},
|
||||||
})
|
})
|
||||||
writeRepositoryFile(t, repository, "token-file", "fixture-secret\n")
|
writeRepositoryFile(t, repository, "token-file", "fixture-secret\n")
|
||||||
cliGitRun(t, repository, "add", "token-file")
|
cliGitRun(t, repository, "add", "token-file")
|
||||||
@@ -82,7 +82,7 @@ func TestDeliverAndCIDeliverReadTokenFileFromRepositoryRoot(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example", TokenFile: ".releasekit/token"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example", TokenFile: ".releasekit/token"},
|
||||||
})
|
})
|
||||||
writeRepositoryFile(t, repository, ".gitignore", ".releasekit/\n")
|
writeRepositoryFile(t, repository, ".gitignore", ".releasekit/\n")
|
||||||
cliGitRun(t, repository, "add", ".gitignore")
|
cliGitRun(t, repository, "add", ".gitignore")
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
writeDeliveryProfile(t, repository, cli.Settings{
|
writeDeliveryProfile(t, repository, cli.Settings{
|
||||||
Product: "Example Product",
|
Product: "Example Product",
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
commit := pushTaggedCLIRepository(t, repository)
|
commit := pushTaggedCLIRepository(t, repository)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = commit })
|
state.update(func(state *deliveryServerData) { state.commit = commit })
|
||||||
@@ -70,7 +70,7 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
|
|||||||
BinaryName: "helper",
|
BinaryName: "helper",
|
||||||
Targets: []string{fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)},
|
Targets: []string{fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)},
|
||||||
},
|
},
|
||||||
Delivery: &cli.DeliverySettings{BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||||
})
|
})
|
||||||
commit := pushTaggedCLIRepository(t, repository)
|
commit := pushTaggedCLIRepository(t, repository)
|
||||||
state.update(func(state *deliveryServerData) { state.commit = commit })
|
state.update(func(state *deliveryServerData) { state.commit = commit })
|
||||||
|
|||||||
109
delivery.go
109
delivery.go
@@ -2,10 +2,14 @@ package releasekit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
internalbuild "go.maximumdirect.net/releasekit/internal/build"
|
internalbuild "go.maximumdirect.net/releasekit/internal/build"
|
||||||
internaldelivery "go.maximumdirect.net/releasekit/internal/delivery"
|
internaldelivery "go.maximumdirect.net/releasekit/internal/delivery"
|
||||||
|
"go.maximumdirect.net/releasekit/internal/forge"
|
||||||
"go.maximumdirect.net/releasekit/internal/gitea"
|
"go.maximumdirect.net/releasekit/internal/gitea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,21 +29,57 @@ const (
|
|||||||
DeliveryUnknown DeliveryState = "unknown"
|
DeliveryUnknown DeliveryState = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GiteaDestination supplies the hosted repository and explicit access token.
|
// Forge selects the hosted delivery backend.
|
||||||
type GiteaDestination struct {
|
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
|
BaseURL string
|
||||||
Owner string
|
Owner string
|
||||||
Repository string
|
Repository string
|
||||||
Token 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.
|
// DeliveryAsset identifies a hosted asset by filename and SHA-256 digest.
|
||||||
type DeliveryAsset struct {
|
type DeliveryAsset struct {
|
||||||
Filename string
|
Filename string
|
||||||
SHA256 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
|
// destination. Build selects a managed build; Prebuilt supplies caller-owned
|
||||||
// build output instead. They cannot both be set. Either may be nil for
|
// 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
|
// 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
|
// ExpectedTag optionally requires the full CI event tag to match the
|
||||||
// selected module's derived tag before building or making hosted requests.
|
// selected module's derived tag before building or making hosted requests.
|
||||||
ExpectedTag string
|
ExpectedTag string
|
||||||
Destination GiteaDestination
|
Destination DeliveryDestination
|
||||||
// Build selects binaries to build in temporary storage outside the checkout.
|
// Build selects binaries to build in temporary storage outside the checkout.
|
||||||
Build *BuildSpec
|
Build *BuildSpec
|
||||||
// Prebuilt supplies an existing build whose files remain caller-owned.
|
// 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.
|
// managed builds require their selected source files in the working tree.
|
||||||
func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, error) {
|
func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, error) {
|
||||||
result := DeliveryResult{Version: request.Version, State: DeliveryNotAttempted}
|
result := DeliveryResult{Version: request.Version, State: DeliveryNotAttempted}
|
||||||
destination := gitea.Destination{
|
backend, err := deliveryBackend(request.Destination)
|
||||||
BaseURL: request.Destination.BaseURL, Owner: request.Destination.Owner,
|
if err != nil {
|
||||||
Repository: request.Destination.Repository, Token: request.Destination.Token,
|
return result, err
|
||||||
}
|
}
|
||||||
var prebuilt *internalbuild.Result
|
var prebuilt *internalbuild.Result
|
||||||
if request.Prebuilt != nil {
|
if request.Prebuilt != nil {
|
||||||
@@ -112,7 +152,7 @@ func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, erro
|
|||||||
Product: request.Product,
|
Product: request.Product,
|
||||||
Version: request.Version,
|
Version: request.Version,
|
||||||
ExpectedTag: request.ExpectedTag,
|
ExpectedTag: request.ExpectedTag,
|
||||||
Destination: destination,
|
Backend: backend,
|
||||||
Build: internalBuildSpec(request.Build),
|
Build: internalBuildSpec(request.Build),
|
||||||
Prebuilt: prebuilt,
|
Prebuilt: prebuilt,
|
||||||
Output: request.Output,
|
Output: request.Output,
|
||||||
@@ -129,10 +169,59 @@ func Deliver(ctx context.Context, request DeliveryRequest) (DeliveryResult, erro
|
|||||||
result.Pending = deliveryAssets(internalResult.Pending)
|
result.Pending = deliveryAssets(internalResult.Pending)
|
||||||
result.Conflicting = deliveryAssets(internalResult.Conflicting)
|
result.Conflicting = deliveryAssets(internalResult.Conflicting)
|
||||||
result.Uncertain = deliveryAssets(internalResult.Uncertain)
|
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))
|
result := make([]DeliveryAsset, 0, len(assets))
|
||||||
for _, asset := range assets {
|
for _, asset := range assets {
|
||||||
result = append(result, DeliveryAsset{Filename: asset.Filename, SHA256: asset.SHA256})
|
result = append(result, DeliveryAsset{Filename: asset.Filename, SHA256: asset.SHA256})
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ func TestDeliverRejectsSameSizePrebuiltTamperingBeforeHostedWrites(t *testing.T)
|
|||||||
if err == nil || !strings.Contains(err.Error(), "verify build assets") || result.State != releasekit.DeliveryNotAttempted {
|
if err == nil || !strings.Contains(err.Error(), "verify build assets") || result.State != releasekit.DeliveryNotAttempted {
|
||||||
t.Fatalf("Deliver() = %#v, %v; want local asset verification failure", result, err)
|
t.Fatalf("Deliver() = %#v, %v; want local asset verification failure", result, err)
|
||||||
}
|
}
|
||||||
if remote.snapshot().requests != 0 {
|
if remote.snapshot().writes != 0 {
|
||||||
t.Fatalf("hosted requests = %d, want none before local verification", remote.snapshot().requests)
|
t.Fatalf("hosted writes = %d, want none before local verification", remote.snapshot().writes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,8 +463,8 @@ func TestDeliverManagedBuildRejectsCheckoutMutationBeforeHostedWrites(t *testing
|
|||||||
if err == nil || !strings.Contains(err.Error(), "build source") || result.State != releasekit.DeliveryNotAttempted {
|
if err == nil || !strings.Contains(err.Error(), "build source") || result.State != releasekit.DeliveryNotAttempted {
|
||||||
t.Fatalf("Deliver() = %#v, %v; want build-source eligibility failure", result, err)
|
t.Fatalf("Deliver() = %#v, %v; want build-source eligibility failure", result, err)
|
||||||
}
|
}
|
||||||
if remote.snapshot().requests != 0 {
|
if remote.snapshot().writes != 0 {
|
||||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
t.Fatalf("hosted writes = %d, want none after preflight", remote.snapshot().writes)
|
||||||
}
|
}
|
||||||
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
||||||
t.Fatalf("ineligible managed build output remains: entries=%v error=%v", entries, err)
|
t.Fatalf("ineligible managed build output remains: entries=%v error=%v", entries, err)
|
||||||
@@ -492,8 +492,8 @@ func TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites(t *te
|
|||||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.snapshot().commit {
|
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.snapshot().commit {
|
||||||
t.Fatalf("Deliver() = %#v, %v; want managed build failure before hosted writes", result, err)
|
t.Fatalf("Deliver() = %#v, %v; want managed build failure before hosted writes", result, err)
|
||||||
}
|
}
|
||||||
if remote.snapshot().requests != 0 {
|
if remote.snapshot().writes != 0 {
|
||||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
t.Fatalf("hosted writes = %d, want none after preflight", remote.snapshot().writes)
|
||||||
}
|
}
|
||||||
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
||||||
t.Fatalf("failed managed build output remains: entries=%v error=%v", entries, err)
|
t.Fatalf("failed managed build output remains: entries=%v error=%v", entries, err)
|
||||||
@@ -525,8 +525,8 @@ func TestDeliverManagedBuildCancellationCleansTemporaryOutput(t *testing.T) {
|
|||||||
if !errors.Is(err, context.Canceled) || result.State != releasekit.DeliveryNotAttempted {
|
if !errors.Is(err, context.Canceled) || result.State != releasekit.DeliveryNotAttempted {
|
||||||
t.Fatalf("Deliver() = %#v, %v; want cancellation before hosted writes", result, err)
|
t.Fatalf("Deliver() = %#v, %v; want cancellation before hosted writes", result, err)
|
||||||
}
|
}
|
||||||
if remote.snapshot().requests != 0 {
|
if remote.snapshot().writes != 0 {
|
||||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
t.Fatalf("hosted writes = %d, want none after preflight", remote.snapshot().writes)
|
||||||
}
|
}
|
||||||
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
||||||
t.Fatalf("canceled managed build output remains: entries=%v error=%v", entries, err)
|
t.Fatalf("canceled managed build output remains: entries=%v error=%v", entries, err)
|
||||||
@@ -575,8 +575,8 @@ func main() { if len(os.Args) == 2 && os.Args[1] == "version" { fmt.Println("fix
|
|||||||
return repository
|
return repository
|
||||||
}
|
}
|
||||||
|
|
||||||
func facadeDeliveryDestination(baseURL string) releasekit.GiteaDestination {
|
func facadeDeliveryDestination(baseURL string) releasekit.DeliveryDestination {
|
||||||
return releasekit.GiteaDestination{BaseURL: baseURL, Owner: "owner", Repository: "example", Token: "fixture-token"}
|
return releasekit.DeliveryDestination{Forge: releasekit.ForgeGitea, BaseURL: baseURL, Owner: "owner", Repository: "example", Token: "fixture-token"}
|
||||||
}
|
}
|
||||||
|
|
||||||
type deliveryCancelWriter struct{ cancel context.CancelFunc }
|
type deliveryCancelWriter struct{ cancel context.CancelFunc }
|
||||||
@@ -623,6 +623,7 @@ type facadeDeliveryData struct {
|
|||||||
noteBody string
|
noteBody string
|
||||||
pages map[string]bool
|
pages map[string]bool
|
||||||
requests int
|
requests int
|
||||||
|
writes int
|
||||||
assets map[string][]byte
|
assets map[string][]byte
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
@@ -642,11 +643,19 @@ func (state *facadeDeliveryServer) serveHTTP(w http.ResponseWriter, r *http.Requ
|
|||||||
data := &state.data
|
data := &state.data
|
||||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||||
data.requests++
|
data.requests++
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
data.writes++
|
||||||
|
}
|
||||||
if r.Header.Get("Authorization") != "token fixture-token" {
|
if r.Header.Get("Authorization") != "token fixture-token" {
|
||||||
data.t.Errorf("authorization = %q", r.Header.Get("Authorization"))
|
data.t.Errorf("authorization = %q", r.Header.Get("Authorization"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch r.URL.EscapedPath() {
|
switch r.URL.EscapedPath() {
|
||||||
|
case "/api/v1/repos/owner/example", "/api/v1/repositories/1":
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"id": 1, "name": "example", "full_name": "owner/example",
|
||||||
|
"owner": map[string]string{"login": "owner"},
|
||||||
|
})
|
||||||
case "/api/v1/repos/owner/example/tags/" + url.PathEscape(data.tag):
|
case "/api/v1/repos/owner/example/tags/" + url.PathEscape(data.tag):
|
||||||
_, _ = fmt.Fprintf(w, `{"name":%q,"commit":{"sha":%q}}`, data.tag, data.commit)
|
_, _ = fmt.Fprintf(w, `{"name":%q,"commit":{"sha":%q}}`, data.tag, data.commit)
|
||||||
case "/api/v1/repos/owner/example/releases/tags/" + url.PathEscape(data.tag):
|
case "/api/v1/repos/owner/example/releases/tags/" + url.PathEscape(data.tag):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Implementation plan: neutral forge delivery and credential preflight
|
# Implementation plan: neutral forge delivery and credential preflight
|
||||||
|
|
||||||
Status: implementation in progress; Stages 1–7 are complete and Stages 8–10
|
Status: implementation in progress; Stages 1–8 are complete and Stages 9–10
|
||||||
are pending.
|
are pending.
|
||||||
|
|
||||||
## Purpose and execution
|
## Purpose and execution
|
||||||
@@ -434,7 +434,7 @@ internal entry points have no public compatibility obligation.
|
|||||||
|
|
||||||
## Stage 8: Connect the neutral facade and CLI
|
## Stage 8: Connect the neutral facade and CLI
|
||||||
|
|
||||||
Status: pending.
|
Status: complete.
|
||||||
|
|
||||||
1. Implement the public declarations/conversions in Selected design. Replace
|
1. Implement the public declarations/conversions in Selected design. Replace
|
||||||
current `GiteaDestination` uses. The private facade factory switches on the
|
current `GiteaDestination` uses. The private facade factory switches on the
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"targets": ["linux/amd64", "linux/arm64", "darwin/arm64"]
|
"targets": ["linux/amd64", "linux/arm64", "darwin/arm64"]
|
||||||
},
|
},
|
||||||
"delivery": {
|
"delivery": {
|
||||||
|
"forge": "gitea",
|
||||||
"base_url": "https://gitea.example",
|
"base_url": "https://gitea.example",
|
||||||
"owner": "example",
|
"owner": "example",
|
||||||
"repository": "example",
|
"repository": "example",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"product": "Example",
|
"product": "Example",
|
||||||
"remote": "origin",
|
"remote": "origin",
|
||||||
"delivery": {
|
"delivery": {
|
||||||
|
"forge": "gitea",
|
||||||
"base_url": "https://gitea.example",
|
"base_url": "https://gitea.example",
|
||||||
"owner": "example",
|
"owner": "example",
|
||||||
"repository": "example",
|
"repository": "example",
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"go.maximumdirect.net/releasekit/internal/artifact"
|
"go.maximumdirect.net/releasekit/internal/artifact"
|
||||||
internalbuild "go.maximumdirect.net/releasekit/internal/build"
|
internalbuild "go.maximumdirect.net/releasekit/internal/build"
|
||||||
"go.maximumdirect.net/releasekit/internal/candidate"
|
"go.maximumdirect.net/releasekit/internal/candidate"
|
||||||
|
"go.maximumdirect.net/releasekit/internal/forge"
|
||||||
"go.maximumdirect.net/releasekit/internal/git"
|
"go.maximumdirect.net/releasekit/internal/git"
|
||||||
"go.maximumdirect.net/releasekit/internal/gitea"
|
|
||||||
"go.maximumdirect.net/releasekit/internal/gotool"
|
"go.maximumdirect.net/releasekit/internal/gotool"
|
||||||
"go.maximumdirect.net/releasekit/internal/releaseidentity"
|
"go.maximumdirect.net/releasekit/internal/releaseidentity"
|
||||||
"go.maximumdirect.net/releasekit/internal/tempdir"
|
"go.maximumdirect.net/releasekit/internal/tempdir"
|
||||||
@@ -36,7 +36,7 @@ type Request struct {
|
|||||||
Product string
|
Product string
|
||||||
Version string
|
Version string
|
||||||
ExpectedTag string
|
ExpectedTag string
|
||||||
Destination gitea.Destination
|
Backend forge.Backend
|
||||||
Build *internalbuild.Spec
|
Build *internalbuild.Spec
|
||||||
Prebuilt *internalbuild.Result
|
Prebuilt *internalbuild.Result
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
@@ -50,11 +50,11 @@ type Result struct {
|
|||||||
CandidateCommit string
|
CandidateCommit string
|
||||||
ReleaseURL string
|
ReleaseURL string
|
||||||
State State
|
State State
|
||||||
Matching []gitea.AssetIdentity
|
Matching []AssetIdentity
|
||||||
Uploaded []gitea.AssetIdentity
|
Uploaded []AssetIdentity
|
||||||
Pending []gitea.AssetIdentity
|
Pending []AssetIdentity
|
||||||
Conflicting []gitea.AssetIdentity
|
Conflicting []AssetIdentity
|
||||||
Uncertain []gitea.AssetIdentity
|
Uncertain []AssetIdentity
|
||||||
}
|
}
|
||||||
|
|
||||||
func Deliver(ctx context.Context, request Request) (Result, error) {
|
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 {
|
if request.Build != nil && request.Prebuilt != nil {
|
||||||
return result, errors.New("delivery cannot use both a managed build and prebuilt artifacts")
|
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)
|
repositoryDir, err := git.RepositoryRoot(ctx, request.RepositoryDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("resolve repository directory: %w", err)
|
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 {
|
if err := candidate.ValidateReleaseNote(note, request.Product, request.Version); err != nil {
|
||||||
return result, fmt.Errorf("release note %q: %w", identity.NotePath, err)
|
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
|
build := request.Prebuilt
|
||||||
if request.Build != nil && len(request.Build.Targets) != 0 {
|
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 len(assets) > 0 {
|
||||||
if err := artifact.VerifyLocalAssets(assets); err != nil {
|
if err := artifact.VerifyLocalAssets(assets); err != nil {
|
||||||
kind := gitea.FailureInvalid
|
kind := forge.FailureInvalid
|
||||||
if errors.Is(err, artifact.ErrAssetConflict) {
|
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 {
|
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{
|
release, pageEvidence, err := coordinateRelease(ctx, request.Backend, identity.Tag, commit, string(note))
|
||||||
Destination: request.Destination,
|
|
||||||
Tag: identity.Tag,
|
|
||||||
Commit: commit,
|
|
||||||
Note: string(note),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.State = stateForError(err, false)
|
result.State = stateForError(err, false)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
result.ReleaseURL = releaseResult.Release.URL
|
result.ReleaseURL = release.URL
|
||||||
if len(assets) == 0 {
|
if len(assets) == 0 {
|
||||||
result.State = Complete
|
result.State = Complete
|
||||||
return result, nil
|
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 {
|
if err != nil {
|
||||||
result.State = NotAttempted
|
result.State = NotAttempted
|
||||||
if releaseResult.WriteAttempted {
|
if pageEvidence.Attempted {
|
||||||
result.State = Partial
|
result.State = Partial
|
||||||
}
|
}
|
||||||
result.Uncertain = assetIdentities(assets)
|
result.Uncertain = assetIdentities(assets)
|
||||||
return result, err
|
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.Matching = uploadResult.Matching
|
||||||
result.Uploaded = uploadResult.Uploaded
|
result.Uploaded = uploadResult.Uploaded
|
||||||
result.Pending = uploadResult.Pending
|
result.Pending = uploadResult.Pending
|
||||||
result.Conflicting = uploadResult.Conflicting
|
result.Conflicting = uploadResult.Conflicting
|
||||||
result.Uncertain = uploadResult.Uncertain
|
result.Uncertain = uploadResult.Uncertain
|
||||||
result.State = State(uploadResult.State)
|
result.State = State(uploadResult.State)
|
||||||
if result.State == NotAttempted && releaseResult.WriteAttempted {
|
if result.State == NotAttempted && pageEvidence.Attempted {
|
||||||
result.State = Partial
|
result.State = Partial
|
||||||
}
|
}
|
||||||
return result, uploadErr
|
return result, uploadErr
|
||||||
@@ -208,6 +214,22 @@ func validateBuild(identity releaseidentity.Identity, commit string, build *inte
|
|||||||
}
|
}
|
||||||
return nil
|
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)
|
buildRepository, err := filepath.Abs(build.RepositoryDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("resolve build repository: %w", err)
|
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")
|
return errors.New("build result does not identify the selected tagged module")
|
||||||
}
|
}
|
||||||
if len(build.Settings.Targets) == 0 {
|
if len(build.Settings.Targets) == 0 {
|
||||||
if len(assets) != 0 {
|
|
||||||
return errors.New("page-only delivery cannot include binary assets")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !build.SourceVerified {
|
if !build.SourceVerified {
|
||||||
return errors.New("build source was not verified as clean and unchanged during compilation")
|
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 errors.New("build result does not contain the selected artifacts and complete checksum manifest")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stateForError(err error, pageCreated bool) State {
|
func stateForError(err error, pageCreated bool) State {
|
||||||
var failure *gitea.Failure
|
var failure *forge.Failure
|
||||||
if errors.As(err, &failure) {
|
if errors.As(err, &failure) {
|
||||||
switch failure.Kind {
|
switch failure.Kind {
|
||||||
case gitea.FailureConflict:
|
case forge.FailureConflict:
|
||||||
return Conflict
|
return Conflict
|
||||||
case gitea.FailureUnknown:
|
case forge.FailureUnknown:
|
||||||
return Unknown
|
return Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,10 +269,10 @@ func stateForError(err error, pageCreated bool) State {
|
|||||||
return NotAttempted
|
return NotAttempted
|
||||||
}
|
}
|
||||||
|
|
||||||
func assetIdentities(assets []artifact.ExpectedAsset) []gitea.AssetIdentity {
|
func assetIdentities(assets []artifact.ExpectedAsset) []AssetIdentity {
|
||||||
result := make([]gitea.AssetIdentity, 0, len(assets))
|
result := make([]AssetIdentity, 0, len(assets))
|
||||||
for _, asset := range 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
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
releasekit "go.maximumdirect.net/releasekit"
|
releasekit "go.maximumdirect.net/releasekit"
|
||||||
"go.maximumdirect.net/releasekit/internal/gitea"
|
"go.maximumdirect.net/releasekit/internal/artifact"
|
||||||
)
|
)
|
||||||
|
|
||||||
const performanceDeliveryArtifactSize = 16 << 20
|
const performanceDeliveryArtifactSize = 16 << 20
|
||||||
@@ -150,7 +150,7 @@ func newDeliveryBenchmarkFixture(b testing.TB) (string, releasekit.BuildResult,
|
|||||||
}
|
}
|
||||||
manifest := performanceDeliveryManifest(build.Artifacts)
|
manifest := performanceDeliveryManifest(build.Artifacts)
|
||||||
manifestHash := sha256.Sum256(manifest)
|
manifestHash := sha256.Sum256(manifest)
|
||||||
assets = append(assets, deliveryBenchmarkAsset{name: gitea.ManifestFilename, data: manifest, digest: hex.EncodeToString(manifestHash[:])})
|
assets = append(assets, deliveryBenchmarkAsset{name: artifact.ManifestFilename, data: manifest, digest: hex.EncodeToString(manifestHash[:])})
|
||||||
return repository, build, assets
|
return repository, build, assets
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +193,11 @@ func (state *deliveryBenchmarkServer) verifyUploads(b testing.TB) {
|
|||||||
|
|
||||||
func (state *deliveryBenchmarkServer) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
func (state *deliveryBenchmarkServer) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
|
case "/api/v1/repos/owner/example", "/api/v1/repositories/1":
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"id": 1, "name": "example", "full_name": "owner/example",
|
||||||
|
"owner": map[string]string{"login": "owner"},
|
||||||
|
})
|
||||||
case "/api/v1/repos/owner/example/tags/v1.2.3":
|
case "/api/v1/repos/owner/example/tags/v1.2.3":
|
||||||
_, _ = fmt.Fprintf(w, `{"name":"v1.2.3","commit":{"sha":%q}}`, state.commit)
|
_, _ = fmt.Fprintf(w, `{"name":"v1.2.3","commit":{"sha":%q}}`, state.commit)
|
||||||
case "/api/v1/repos/owner/example/releases/tags/v1.2.3":
|
case "/api/v1/repos/owner/example/releases/tags/v1.2.3":
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"forbidden_files": [".releasekit/gitea-token"]
|
"forbidden_files": [".releasekit/gitea-token"]
|
||||||
},
|
},
|
||||||
"delivery": {
|
"delivery": {
|
||||||
|
"forge": "gitea",
|
||||||
"base_url": "https://forge.maximumdirect.net",
|
"base_url": "https://forge.maximumdirect.net",
|
||||||
"owner": "base-layer",
|
"owner": "base-layer",
|
||||||
"repository": "releasekit",
|
"repository": "releasekit",
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func TestSourceOperationsResolveContainingRepository(t *testing.T) {
|
|||||||
if err != nil || built.RepositoryDir != repo.work || built.ModuleDir != wantModule || built.ModulePath != wantPath || built.Tag != wantTag {
|
if err != nil || built.RepositoryDir != repo.work || built.ModuleDir != wantModule || built.ModulePath != wantPath || built.Tag != wantTag {
|
||||||
t.Fatalf("build %q = %#v, error = %v", selected, built, err)
|
t.Fatalf("build %q = %#v, error = %v", selected, built, err)
|
||||||
}
|
}
|
||||||
delivered, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{RepositoryDir: child, ModuleDir: selected, Product: request.Product, Version: request.Version, ExpectedTag: "mismatch"})
|
delivered, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{RepositoryDir: child, ModuleDir: selected, Product: request.Product, Version: request.Version, ExpectedTag: "mismatch", Destination: releasekit.DeliveryDestination{Forge: releasekit.ForgeGitea}})
|
||||||
if err == nil || !strings.Contains(err.Error(), "expected delivery tag") || delivered.RepositoryDir != repo.work || delivered.ModuleDir != wantModule || delivered.ModulePath != wantPath || delivered.Tag != wantTag {
|
if err == nil || !strings.Contains(err.Error(), "expected delivery tag") || delivered.RepositoryDir != repo.work || delivered.ModuleDir != wantModule || delivered.ModulePath != wantPath || delivered.Tag != wantTag {
|
||||||
t.Fatalf("delivery %q = %#v, error = %v", selected, delivered, err)
|
t.Fatalf("delivery %q = %#v, error = %v", selected, delivered, err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user