Files
homarr/tools/homarr-dev/internal/run/build.go
T

106 lines
3.5 KiB
Go

package run
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/docker"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/gh"
)
func BuildCurrentImage(tag, directory string) error {
return buildCurrentImage(context.Background(), tag, directory, os.Stdout, os.Stderr)
}
func buildCurrentImage(ctx context.Context, tag, directory string, stdout, stderr io.Writer) error {
root, revision, err := checkoutDetails(ctx, directory)
if err != nil {
return err
}
return docker.BuildContext(ctx, docker.BuildOptions{Context: root, Tag: tag, Source: root, Revision: revision}, stdout, stderr)
}
func BuildPRImage(number int, tag string) error {
return buildPRImage(context.Background(), number, tag, os.Stdout, os.Stderr)
}
func buildPRImage(ctx context.Context, number int, tag string, stdout, stderr io.Writer) error {
if number <= 0 {
return fmt.Errorf("PR number must be positive")
}
temporaryRoot, err := os.MkdirTemp("", "homarr-pr-*")
if err != nil {
return fmt.Errorf("create temporary checkout: %w", err)
}
defer os.RemoveAll(temporaryRoot)
checkout := filepath.Join(temporaryRoot, "homarr")
clone := exec.CommandContext(ctx, "gh", prCloneArgs(checkout)...)
clone.Stdout = stdout
clone.Stderr = stderr
if err := clone.Run(); err != nil {
return fmt.Errorf("clone %s: %w", gh.Repo, err)
}
command := exec.CommandContext(ctx, "gh", "pr", "checkout", strconv.Itoa(number), "--detach")
command.Dir = checkout
command.Stdout = stdout
command.Stderr = stderr
if err := command.Run(); err != nil {
return fmt.Errorf("checkout PR #%d: %w", number, err)
}
_, revision, err := checkoutDetails(ctx, checkout)
if err != nil {
return err
}
return docker.BuildContext(ctx, docker.BuildOptions{
Context: checkout,
Tag: tag,
Source: fmt.Sprintf("https://github.com/%s/pull/%d", gh.Repo, number),
Revision: revision,
PRNumber: number,
}, stdout, stderr)
}
func prCloneArgs(checkout string) []string {
return []string{"repo", "clone", gh.Repo, checkout, "--", "--depth=1", "--filter=blob:none"}
}
func RebuildImage(image docker.Image) error {
return RebuildImageContext(context.Background(), image, os.Stdout, os.Stderr)
}
func RebuildImageContext(ctx context.Context, image docker.Image, stdout, stderr io.Writer) error {
if image.PRNumber > 0 {
return buildPRImage(ctx, image.PRNumber, image.Tag, stdout, stderr)
}
if image.Source == "" {
return fmt.Errorf("homarr:%s has no build source metadata", image.Tag)
}
if info, err := os.Stat(image.Source); err != nil || !info.IsDir() {
return fmt.Errorf("build source %s is no longer available", image.Source)
}
return buildCurrentImage(ctx, image.Tag, image.Source, stdout, stderr)
}
func checkoutDetails(ctx context.Context, directory string) (root string, revision string, err error) {
rootOutput, err := exec.CommandContext(ctx, "git", "-C", directory, "rev-parse", "--show-toplevel").CombinedOutput()
if err != nil {
return "", "", fmt.Errorf("find Homarr checkout: %w: %s", err, strings.TrimSpace(string(rootOutput)))
}
root = strings.TrimSpace(string(rootOutput))
if _, err := os.Stat(filepath.Join(root, "Dockerfile")); err != nil {
return "", "", fmt.Errorf("%s is not a Homarr checkout", root)
}
revisionOutput, err := exec.CommandContext(ctx, "git", "-C", root, "rev-parse", "HEAD").CombinedOutput()
if err != nil {
return "", "", fmt.Errorf("read checkout revision: %w: %s", err, strings.TrimSpace(string(revisionOutput)))
}
return root, strings.TrimSpace(string(revisionOutput)), nil
}