refactor: internal actions package

Migrate all 'action' code to the new internal actions package to reduce
the number of internal packages.

Migrate the Bundle type to a separate package to avoid circular
dependencies.

Refactor code around bundle definition and download for relevant
services.
This commit is contained in:
Dan Anglin 2023-11-26 07:46:10 +00:00
parent 9f7f920fb9
commit ce490db95d
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
15 changed files with 306 additions and 393 deletions

View file

@ -1,4 +1,4 @@
package deploy
package actions
import (
"flow/services/internal"
@ -8,7 +8,7 @@ import (
"github.com/magefile/mage/sh"
)
func Deploy(dockerHost, environment, name string, daemon bool) error {
func Deploy(dockerHost, environment, service string, daemon bool) error {
os.Setenv("DOCKER_HOST", dockerHost)
command := []string{
@ -24,8 +24,8 @@ func Deploy(dockerHost, environment, name string, daemon bool) error {
command = append(command, "-d")
}
if name != "all" {
command = append(command, name)
if service != "all" {
command = append(command, service)
}
return sh.RunV(command[0], command[1:]...)

View file

@ -1,6 +1,7 @@
package download
package actions
import (
"flow/services/internal/bundle"
"fmt"
"io"
"net/http"
@ -10,48 +11,25 @@ import (
"github.com/magefile/mage/sh"
)
type Bundle struct {
DestinationDir string
Packages []Pack
Checksum Checksum
ValidateGPGSignature bool
}
type Pack struct {
File Object
GPGSignature Object
}
type Checksum struct {
File Object
Validate bool
ValidateFunc func() error
}
type Object struct {
Source string
Destination string
}
// Download downloads all the files in the download pack,
// verifies all the GPG signatures (if enabled) and
// verifies the checksums (if enabled).
func Download(bundle Bundle) error {
if err := os.MkdirAll(bundle.DestinationDir, 0o750); err != nil {
return fmt.Errorf("unable to make '%s'; %w", bundle.DestinationDir, err)
func Download(b bundle.Bundle) error {
if err := os.MkdirAll(b.DestinationDir, 0o750); err != nil {
return fmt.Errorf("unable to make '%s'; %w", b.DestinationDir, err)
}
var objects []Object
var objects []bundle.Object
for i := range bundle.Packages {
objects = append(objects, bundle.Packages[i].File)
if bundle.ValidateGPGSignature {
objects = append(objects, bundle.Packages[i].GPGSignature)
for i := range b.Packages {
objects = append(objects, b.Packages[i].File)
if b.ValidateGPGSignature {
objects = append(objects, b.Packages[i].GPGSignature)
}
}
if bundle.Checksum.Validate {
objects = append(objects, bundle.Checksum.File)
if b.Checksum.Validate {
objects = append(objects, b.Checksum.File)
}
for _, object := range objects {
@ -94,20 +72,20 @@ func Download(bundle Bundle) error {
}
}
if bundle.ValidateGPGSignature {
for i := range bundle.Packages {
if err := sh.RunV("gpg", "--verify", bundle.Packages[i].GPGSignature.Destination, bundle.Packages[i].File.Destination); err != nil {
return fmt.Errorf("GPG verification failed for '%s'; %w", bundle.Packages[i].File.Destination, err)
if b.ValidateGPGSignature {
for i := range b.Packages {
if err := sh.RunV("gpg", "--verify", b.Packages[i].GPGSignature.Destination, b.Packages[i].File.Destination); err != nil {
return fmt.Errorf("GPG verification failed for '%s'; %w", b.Packages[i].File.Destination, err)
}
}
}
if bundle.Checksum.Validate {
if b.Checksum.Validate {
var err error
if bundle.Checksum.ValidateFunc != nil {
err = bundle.Checksum.ValidateFunc()
if b.Checksum.ValidateFunc != nil {
err = b.Checksum.ValidateFunc()
} else {
err = validateChecksum(bundle.DestinationDir, bundle.Checksum.File.Destination)
err = validateChecksum(b.DestinationDir, b.Checksum.File.Destination)
}
if err != nil {

View file

@ -1,4 +1,4 @@
package prepare
package actions
import (
"fmt"
@ -10,6 +10,7 @@ import (
"text/template"
"flow/services/internal"
"flow/services/internal/bundle"
"flow/services/internal/config"
"flow/services/internal/services"
"flow/services/internal/services/forgejo"
@ -81,7 +82,7 @@ func prepareService(cfg config.Config, environment, service string) error {
}
}
if err := downloadServiceFiles(cfg, environment, service); err != nil {
if err := downloadBundle(cfg, environment, service); err != nil {
return fmt.Errorf("error downloading service files for %q; %w", service, err)
}
@ -96,25 +97,23 @@ func prepareService(cfg config.Config, environment, service string) error {
return nil
}
func downloadServiceFiles(cfg config.Config, environment, service string) error {
func downloadBundle(cfg config.Config, environment, service string) error {
var b bundle.Bundle
switch service {
case services.Forgejo:
obj := forgejo.NewForgejo(environment, cfg.Forgejo.Version)
if err := obj.Download(); err != nil {
return fmt.Errorf("error downloading the files for %q; %w", services.Forgejo, err)
}
b = forgejo.Bundle(environment, cfg.Forgejo.Version)
case services.Gotosocial:
obj := gotosocial.NewGotosocial(environment, cfg.GoToSocial.Version)
if err := obj.Download(); err != nil {
return fmt.Errorf("error downloading the files for %q; %w", services.Gotosocial, err)
}
b = gotosocial.Bundle(environment, cfg.GoToSocial.Version)
case services.Woodpecker:
obj := woodpecker.NewWoodpecker(environment, cfg.Woodpecker.Version)
if err := obj.Download(); err != nil {
return fmt.Errorf("error downloading the files for %q; %w", services.Woodpecker, err)
}
b = woodpecker.Bundle(environment, cfg.Woodpecker.Version)
default:
fmt.Printf("There's no files to download for %q.\n", service)
return nil
}
if err := Download(b); err != nil {
return fmt.Errorf("error downloading the files for %q; %w", service, err)
}
return nil

24
internal/actions/stop.go Normal file
View file

@ -0,0 +1,24 @@
package actions
import (
"flow/services/internal"
"fmt"
"os"
"github.com/magefile/mage/sh"
)
func Stop(dockerHost, environment, service string) error {
os.Setenv("DOCKER_HOST", dockerHost)
command := []string{
"docker",
"compose",
"--project-directory",
fmt.Sprintf("%s/%s/compose", internal.RootBuildDir, environment),
"stop",
service,
}
return sh.RunV(command[0], command[1:]...)
}

24
internal/bundle/bundle.go Normal file
View file

@ -0,0 +1,24 @@
package bundle
type Bundle struct {
DestinationDir string
Packages []Pack
Checksum Checksum
ValidateGPGSignature bool
}
type Pack struct {
File Object
GPGSignature Object
}
type Checksum struct {
File Object
Validate bool
ValidateFunc func() error
}
type Object struct {
Source string
Destination string
}

View file

@ -0,0 +1,58 @@
package forgejo
import (
"fmt"
"path/filepath"
"flow/services/internal"
"flow/services/internal/bundle"
"flow/services/internal/services"
)
const (
binaryURLFormat = "https://codeberg.org/forgejo/forgejo/releases/download/v%s/forgejo-%s-linux-amd64"
signatureURLFormat = "https://codeberg.org/forgejo/forgejo/releases/download/v%s/forgejo-%s-linux-amd64.asc"
checksumURLFormat = "https://codeberg.org/forgejo/forgejo/releases/download/v%s/forgejo-%s-linux-amd64.sha256"
forgejoBinaryFileFormat = "forgejo-%s-linux-amd64"
forgejoDigestExtension = ".sha256"
forgejoSignatureExtension = ".asc"
)
func Bundle(environment, version string) bundle.Bundle {
var (
destinationDir = filepath.Join(internal.RootBuildDir, environment, services.Forgejo)
binaryURL = fmt.Sprintf(binaryURLFormat, version, version)
binaryPath = filepath.Join(destinationDir, fmt.Sprintf(forgejoBinaryFileFormat, version))
signatureURL = fmt.Sprintf(signatureURLFormat, version, version)
signaturePath = binaryPath + forgejoSignatureExtension
checksumURL = fmt.Sprintf(checksumURLFormat, version, version)
checksumPath = binaryPath + forgejoDigestExtension
)
bundle := bundle.Bundle{
DestinationDir: destinationDir,
Packages: []bundle.Pack{
{
File: bundle.Object{
Source: binaryURL,
Destination: binaryPath,
},
GPGSignature: bundle.Object{
Source: signatureURL,
Destination: signaturePath,
},
},
},
ValidateGPGSignature: true,
Checksum: bundle.Checksum{
File: bundle.Object{
Source: checksumURL,
Destination: checksumPath,
},
Validate: true,
ValidateFunc: nil,
},
}
return bundle
}

View file

@ -1,40 +0,0 @@
package forgejo
import (
"fmt"
"flow/services/internal/download"
)
func (f Forgejo) Download() error {
bundle := download.Bundle{
DestinationDir: f.destinationDir,
Packages: []download.Pack{
{
File: download.Object{
Source: f.binaryURL,
Destination: f.binaryPath,
},
GPGSignature: download.Object{
Source: f.signatureURL,
Destination: f.signaturePath,
},
},
},
ValidateGPGSignature: true,
Checksum: download.Checksum{
File: download.Object{
Source: f.checksumURL,
Destination: f.checksumPath,
},
Validate: true,
ValidateFunc: nil,
},
}
if err := download.Download(bundle); err != nil {
return fmt.Errorf("error downloading files for Forgejo; %w", err)
}
return nil
}

View file

@ -1,59 +0,0 @@
package forgejo
import (
"fmt"
"path/filepath"
"flow/services/internal"
"flow/services/internal/services"
)
type Forgejo struct {
binaryURL string
binaryPath string
signatureURL string
signaturePath string
checksumURL string
checksumPath string
destinationDir string
}
func NewForgejo(environment, version string) Forgejo {
forgejoBinaryFileFormat := "forgejo-%s-linux-amd64"
forgejoDigestExtension := ".sha256"
forgejoSignatureExtension := ".asc"
destinationDir := filepath.Join(internal.RootBuildDir, environment, services.Forgejo)
binaryPath := filepath.Join(destinationDir, fmt.Sprintf(forgejoBinaryFileFormat, version))
forgejo := Forgejo{
destinationDir: destinationDir,
binaryURL: fmt.Sprintf(
"https://codeberg.org/forgejo/forgejo/releases/download/v%s/forgejo-%s-linux-amd64",
version,
version,
),
binaryPath: filepath.Join(
destinationDir,
fmt.Sprintf(forgejoBinaryFileFormat, version),
),
signatureURL: fmt.Sprintf(
"https://codeberg.org/forgejo/forgejo/releases/download/v%s/forgejo-%s-linux-amd64.asc",
version,
version,
),
signaturePath: binaryPath + forgejoSignatureExtension,
checksumURL: fmt.Sprintf(
"https://codeberg.org/forgejo/forgejo/releases/download/v%s/forgejo-%s-linux-amd64.sha256",
version,
version,
),
checksumPath: binaryPath + forgejoDigestExtension,
}
return forgejo
}

View file

@ -0,0 +1,57 @@
package gotosocial
import (
"fmt"
"path/filepath"
"flow/services/internal"
"flow/services/internal/bundle"
"flow/services/internal/services"
)
const (
binaryTarURLFormat = "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_linux_amd64.tar.gz"
webAssetsTarURLFormat = "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_web-assets.tar.gz"
checksumURLFormat = "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/checksums.txt"
)
func Bundle(environment, version string) bundle.Bundle {
var (
destinationDir = filepath.Join(internal.RootBuildDir, environment, services.Gotosocial)
binaryTarURL = fmt.Sprintf(binaryTarURLFormat, version, version)
binaryTarFilepath = filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_linux_amd64.tar.gz", version))
webAssetsTarURL = fmt.Sprintf(webAssetsTarURLFormat, version, version)
webAssetsFilepath = filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_web-assets.tar.gz", version))
checksumURL = fmt.Sprintf(checksumURLFormat, version)
checksumPath = filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_checksums.txt", version))
)
bundle := bundle.Bundle{
DestinationDir: destinationDir,
Packages: []bundle.Pack{
{
File: bundle.Object{
Source: binaryTarURL,
Destination: binaryTarFilepath,
},
},
{
File: bundle.Object{
Source: webAssetsTarURL,
Destination: webAssetsFilepath,
},
},
},
ValidateGPGSignature: false,
Checksum: bundle.Checksum{
File: bundle.Object{
Source: checksumURL,
Destination: checksumPath,
},
Validate: true,
ValidateFunc: nil,
},
}
return bundle
}

View file

@ -1,43 +0,0 @@
package gotosocial
import (
"fmt"
"flow/services/internal/download"
)
// Download downloads and validates the files for GoToSocial.
func (g Gotosocial) Download() error {
bundle := download.Bundle{
DestinationDir: g.destinationDir,
Packages: []download.Pack{
{
File: download.Object{
Source: g.binaryTarURL,
Destination: g.binaryTarFilepath,
},
},
{
File: download.Object{
Source: g.webAssetsTarURL,
Destination: g.webAssetsFilepath,
},
},
},
ValidateGPGSignature: false,
Checksum: download.Checksum{
File: download.Object{
Source: g.checksumURL,
Destination: g.checksumPath,
},
Validate: true,
ValidateFunc: nil,
},
}
if err := download.Download(bundle); err != nil {
return fmt.Errorf("error downloading the files for Gotosocial; %w", err)
}
return nil
}

View file

@ -1,48 +0,0 @@
package gotosocial
import (
"fmt"
"path/filepath"
"flow/services/internal"
"flow/services/internal/services"
)
type Gotosocial struct {
binaryTarURL string
binaryTarFilepath string
checksumURL string
checksumPath string
webAssetsTarURL string
webAssetsFilepath string
destinationDir string
}
func NewGotosocial(environment, version string) Gotosocial {
destinationDir := filepath.Join(internal.RootBuildDir, environment, services.Gotosocial)
gotosocial := Gotosocial{
destinationDir: destinationDir,
binaryTarURL: fmt.Sprintf(
"https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_linux_amd64.tar.gz",
version,
version,
),
binaryTarFilepath: filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_linux_amd64.tar.gz", version)),
webAssetsTarURL: fmt.Sprintf(
"https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_web-assets.tar.gz",
version,
version,
),
webAssetsFilepath: filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_web-assets.tar.gz", version)),
checksumURL: fmt.Sprintf(
"https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/checksums.txt",
version,
),
checksumPath: filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_checksums.txt", version)),
}
return gotosocial
}

View file

@ -0,0 +1,99 @@
package woodpecker
import (
"bufio"
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"flow/services/internal"
"flow/services/internal/bundle"
"flow/services/internal/services"
)
const (
binaryTarURLFormat = "https://github.com/woodpecker-ci/woodpecker/releases/download/v%s/woodpecker-server_linux_amd64.tar.gz"
checksumURLFormat = "https://github.com/woodpecker-ci/woodpecker/releases/download/v%s/checksums.txt"
)
func Bundle(environment, version string) bundle.Bundle {
var (
destinationDir = filepath.Join(internal.RootBuildDir, environment, services.Woodpecker)
binaryTarURL = fmt.Sprintf(binaryTarURLFormat, version)
binaryTarPath = filepath.Join(destinationDir, fmt.Sprintf("woodpecker-server-%s_linux_amd64.tar.gz", version))
checksumURL = fmt.Sprintf(checksumURLFormat, version)
checksumPath = filepath.Join(destinationDir, fmt.Sprintf("woodpecker_%s_checksums.txt", version))
)
bundle := bundle.Bundle{
DestinationDir: destinationDir,
Packages: []bundle.Pack{
{
File: bundle.Object{
Source: binaryTarURL,
Destination: binaryTarPath,
},
},
},
ValidateGPGSignature: false,
Checksum: bundle.Checksum{
File: bundle.Object{
Source: checksumURL,
Destination: checksumPath,
},
Validate: true,
ValidateFunc: checksumValidationFunc(binaryTarPath, checksumPath),
},
}
return bundle
}
func checksumValidationFunc(binaryTarPath, checksumPath string) func() error {
return func() error {
var wantChecksum, gotChecksum string
// get the tar file's checksum
tarFile, err := os.Open(binaryTarPath)
if err != nil {
return fmt.Errorf("unable to open %s; %w", binaryTarPath, err)
}
defer tarFile.Close()
h := sha256.New()
if _, err = io.Copy(h, tarFile); err != nil {
return fmt.Errorf("unable to get the shasum for %s; %w", binaryTarPath, err)
}
gotChecksum = fmt.Sprintf("%x", h.Sum(nil))
// get the expected checksum from the checksum file
checksumFile, err := os.Open(checksumPath)
if err != nil {
return fmt.Errorf("unable to open %s; %w", checksumPath, err)
}
defer checksumFile.Close()
scanner := bufio.NewScanner(checksumFile)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "woodpecker-server_linux_amd64.tar.gz") {
wantChecksum = strings.Split(line, " ")[0]
break
}
}
// compare the two checksum strings
if gotChecksum != wantChecksum {
return fmt.Errorf("checksum validation failed: want %s, got %s", wantChecksum, gotChecksum)
} else {
fmt.Printf("Checksum validation successful: %s\n", gotChecksum)
}
return nil
}
}

View file

@ -1,85 +0,0 @@
package woodpecker
import (
"bufio"
"crypto/sha256"
"fmt"
"io"
"os"
"strings"
"flow/services/internal/download"
)
func (w Woodpecker) Download() error {
bundle := download.Bundle{
DestinationDir: w.destinationDir,
Packages: []download.Pack{
{
File: download.Object{
Source: w.binaryTarURL,
Destination: w.binaryTarPath,
},
},
},
ValidateGPGSignature: false,
Checksum: download.Checksum{
File: download.Object{
Source: w.checksumURL,
Destination: w.checksumPath,
},
Validate: true,
ValidateFunc: w.checksumValidation,
},
}
if err := download.Download(bundle); err != nil {
return err
}
return nil
}
func (w Woodpecker) checksumValidation() error {
var wantChecksum, gotChecksum string
// get the tar file's checksum
tarFile, err := os.Open(w.binaryTarPath)
if err != nil {
return fmt.Errorf("unable to open %s; %w", w.binaryTarPath, err)
}
defer tarFile.Close()
h := sha256.New()
if _, err = io.Copy(h, tarFile); err != nil {
return fmt.Errorf("unable to get the shasum for %s; %w", w.binaryTarPath, err)
}
gotChecksum = fmt.Sprintf("%x", h.Sum(nil))
// get the expected checksum from the checksum file
checksumFile, err := os.Open(w.checksumPath)
if err != nil {
return fmt.Errorf("unable to open %s; %w", w.checksumPath, err)
}
defer checksumFile.Close()
scanner := bufio.NewScanner(checksumFile)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "woodpecker-server_linux_amd64.tar.gz") {
wantChecksum = strings.Split(line, " ")[0]
break
}
}
// compare the two checksum strings
if gotChecksum != wantChecksum {
return fmt.Errorf("checksum validation failed: want %s, got %s", wantChecksum, gotChecksum)
} else {
fmt.Printf("Checksum validation successful: %s\n", gotChecksum)
}
return nil
}

View file

@ -1,43 +0,0 @@
package woodpecker
import (
"fmt"
"path/filepath"
"flow/services/internal"
"flow/services/internal/services"
)
type Woodpecker struct {
binaryTarURL string
binaryTarPath string
checksumURL string
checksumPath string
destinationDir string
}
func NewWoodpecker(environment, version string) Woodpecker {
destinationDir := filepath.Join(internal.RootBuildDir, environment, services.Woodpecker)
woodpecker := Woodpecker{
destinationDir: destinationDir,
binaryTarURL: fmt.Sprintf(
"https://github.com/woodpecker-ci/woodpecker/releases/download/v%s/woodpecker-server_linux_amd64.tar.gz",
version,
),
binaryTarPath: filepath.Join(
destinationDir,
fmt.Sprintf("woodpecker-server-%s_linux_amd64.tar.gz", version),
),
checksumURL: fmt.Sprintf(
"https://github.com/woodpecker-ci/woodpecker/releases/download/v%s/checksums.txt",
version,
),
checksumPath: filepath.Join(
destinationDir,
fmt.Sprintf("woodpecker_%s_checksums.txt", version),
),
}
return woodpecker
}

View file

@ -4,9 +4,8 @@ package main
import (
"flow/services/internal"
"flow/services/internal/actions"
"flow/services/internal/config"
"flow/services/internal/deploy"
"flow/services/internal/prepare"
"fmt"
"os"
"path/filepath"
@ -48,7 +47,7 @@ func Deploy(environment, service string) error {
return fmt.Errorf("unable to load the configuration; %w", err)
}
if err := deploy.Deploy(cfg.Docker.Host, environment, service, true); err != nil {
if err := actions.Deploy(cfg.Docker.Host, environment, service, true); err != nil {
return fmt.Errorf("error deploying %q; %w", service, err)
}
@ -57,7 +56,7 @@ func Deploy(environment, service string) error {
// Prepare prepares the service's build directory.
func Prepare(environment, service string) error {
if err := prepare.Prepare(environment, service); err != nil {
if err := actions.Prepare(environment, service); err != nil {
return fmt.Errorf("error running preparations; %w", err)
}
@ -91,16 +90,9 @@ func Stop(environment, service string) error {
return fmt.Errorf("unable to load the configuration; %w", err)
}
os.Setenv("DOCKER_HOST", cfg.Docker.Host)
command := []string{
"docker",
"compose",
"--project-directory",
fmt.Sprintf("%s/%s/compose", internal.RootBuildDir, environment),
"stop",
service,
if err := actions.Stop(cfg.Docker.Host, environment, service); err != nil {
return fmt.Errorf("error stopping %q; %w", service, err)
}
return sh.RunV(command[0], command[1:]...)
return nil
}