First commit
This commit is contained in:
commit
886da1fce3
29
.github/workflows/release.yaml
vendored
Normal file
29
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
21
.github/workflows/test_golang.yaml
vendored
Normal file
21
.github/workflows/test_golang.yaml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Go package
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
goreleaser-http-repo-builder
|
26
.goreleaser.yaml
Normal file
26
.goreleaser.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines below are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: "{{ .ProjectName }}-{{ .Version }}.{{ .Os }}-{{ .Arch }}"
|
||||
wrap_in_directory: true
|
19
LICENSE.txt
Normal file
19
LICENSE.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2024 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
63
README.md
Normal file
63
README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# goreleaser-http-repo-builder
|
||||
|
||||
This tool was written out of the need to build a release repository compatible with [go-selfupdate](https://github.com/creativeprojects/go-selfupdate) with releases built by [goreleaser](https://goreleaser.com/).
|
||||
|
||||
## Example Usage
|
||||
|
||||
The command has extensive help available, the following is an example of building a release and adding it to a new repo.
|
||||
|
||||
```bash
|
||||
goreleaser release --snapshot --skip=publish
|
||||
mkdir repo
|
||||
goreleaser-http-repo-builder add-release --repo=repo/ --release=dist/
|
||||
```
|
||||
|
||||
After adding a release, you can copy the repo to your web server for update distrobution.
|
||||
|
||||
## Example Goreleaser Config
|
||||
|
||||
While there is good [documentation available](https://goreleaser.com/customization/) that I'd recommend reading, the following provides some examples that may be helpful in generating a release that is compatible with go-selfupdate.
|
||||
|
||||
- The checksums file name defaults to preappend the project name, which is not compatible if you wish to use the checksums to verify an update.
|
||||
- If you're signing releases with an ECDSA key, this is what I found works best.
|
||||
- If you need to specify the version manually, you can edit the version template. By default, goreleaser will use the git tag to determine the version.
|
||||
|
||||
```yaml
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
||||
wrap_in_directory: true
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
signs:
|
||||
- artifacts: all
|
||||
cmd: openssl
|
||||
args:
|
||||
- dgst
|
||||
- -sha256
|
||||
- -sign
|
||||
- "signing.key"
|
||||
- -out
|
||||
- ${signature}
|
||||
- ${artifact}
|
||||
|
||||
snapshot:
|
||||
version_template: "v0.1.2"
|
||||
```
|
187
add_release_cmd.go
Normal file
187
add_release_cmd.go
Normal file
@ -0,0 +1,187 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AddReleaseCmd struct {
|
||||
Release string `help:"Path to goreleaser dist folder." required:"" type:"existingdir"`
|
||||
Notes string `help:"Notes about this release."`
|
||||
Draft bool `help:"Is this release a draft?"`
|
||||
Prerelease bool `help:"Is this a prelease?"`
|
||||
IncludeBinary bool `help:"Include binary artifacts."`
|
||||
Force bool `help:"Force add, removing existing if needed."`
|
||||
PublishedAt time.Time `help:"Specify exact time for release."`
|
||||
PublishedAtNow bool `help:"Use the current time for published at instead of the metadata date."`
|
||||
}
|
||||
|
||||
// Adds a release to a repo.
|
||||
func (a *AddReleaseCmd) Run() error {
|
||||
// Read existing manifest for repo.
|
||||
manifestFile := filepath.Join(app.flags.Repo, "manifest.yaml")
|
||||
manifest, err := readManifestFile(manifestFile)
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(app.flags.Repo, 0755)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read metadata from goreleaser.
|
||||
metadata, err := readMetadataFile(filepath.Join(a.Release, "metadata.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versionPath := filepath.Join(app.flags.Repo, metadata.Version)
|
||||
|
||||
// Read the artifcats to ensure we have a valid release.
|
||||
artifacts, err := readArtifactFile(filepath.Join(a.Release, "artifacts.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(artifacts) == 0 {
|
||||
return errors.New("no artifacts in release")
|
||||
}
|
||||
|
||||
// Validate the base dir for artifacts. It could be one dir up, or 2 dirs up.
|
||||
artifcatBase := a.Release
|
||||
artifactLayers := 0
|
||||
if _, serr := os.Stat(filepath.Join(artifcatBase, artifacts[0].Path)); serr != nil {
|
||||
artifcatBase = filepath.Dir(artifcatBase)
|
||||
artifactLayers = 1
|
||||
if _, serr := os.Stat(filepath.Join(artifcatBase, artifacts[0].Path)); serr != nil {
|
||||
artifcatBase = filepath.Dir(artifcatBase)
|
||||
artifactLayers = 2
|
||||
if _, serr := os.Stat(filepath.Join(artifcatBase, artifacts[0].Path)); serr != nil {
|
||||
return errors.New("unable to determine artificate base path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the version already exists.
|
||||
existingIndex := -1
|
||||
for i, release := range manifest.Releases {
|
||||
if release.TagName == metadata.Version {
|
||||
existingIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the version already exists, ask about replacing.
|
||||
if existingIndex != -1 {
|
||||
if !a.Force {
|
||||
ans := askForConfirmation("This release already exists, should we replace?")
|
||||
|
||||
// If we don't want to replace, we should stop here.
|
||||
if !ans {
|
||||
return errors.New("version already exists")
|
||||
}
|
||||
}
|
||||
|
||||
// We need to replace the release, so remove it.
|
||||
manifest.Releases = append(manifest.Releases[:existingIndex], manifest.Releases[existingIndex+1:]...)
|
||||
|
||||
// Remove the version directory.
|
||||
os.RemoveAll(versionPath)
|
||||
}
|
||||
|
||||
// Make the release.
|
||||
manifest.LastReleaseID++
|
||||
release := &HttpRelease{
|
||||
ReleaseID: manifest.LastReleaseID,
|
||||
Name: metadata.Name,
|
||||
TagName: metadata.Version,
|
||||
URL: metadata.Version,
|
||||
Draft: a.Draft,
|
||||
Prerelease: a.Prerelease,
|
||||
PublishedAt: metadata.Date,
|
||||
ReleaseNotes: a.Notes,
|
||||
}
|
||||
|
||||
// If the publish date provided is valid, override.
|
||||
if !a.PublishedAt.IsZero() {
|
||||
release.PublishedAt = a.PublishedAt
|
||||
}
|
||||
|
||||
// If published at is requested to be now, override.
|
||||
if a.PublishedAtNow {
|
||||
release.PublishedAt = app.now
|
||||
}
|
||||
|
||||
// Make the directory for the release.
|
||||
err = os.Mkdir(versionPath, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("Error making release directory: %s", err)
|
||||
}
|
||||
|
||||
// Add artifacts.
|
||||
for _, artifact := range artifacts {
|
||||
// Skip binaries if not included.
|
||||
if artifact.Type == "Binary" && !a.IncludeBinary {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the file path and confirm it exists and get its stat for file size.
|
||||
path := filepath.Join(artifcatBase, artifact.Path)
|
||||
stat, serr := os.Stat(path)
|
||||
if serr != nil {
|
||||
log.Println("Ignoring artifact", artifact.Name, "as its file does not exist.")
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine relative path.
|
||||
s := strings.Split(artifact.Path, "/")
|
||||
relativePath := filepath.Join(s[artifactLayers:]...)
|
||||
|
||||
// Determine if artifact is in its own sub dir, make sure it exists.
|
||||
dir := filepath.Dir(relativePath)
|
||||
if dir != "." {
|
||||
os.MkdirAll(filepath.Join(versionPath, dir), 0755)
|
||||
}
|
||||
|
||||
// Copy artifact to repo.
|
||||
err = copyFile(path, filepath.Join(versionPath, relativePath))
|
||||
if err != nil {
|
||||
log.Printf("Failed to copy artifact, skipping it: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Make asset.
|
||||
manifest.LastAssetID++
|
||||
asset := &HttpAsset{
|
||||
ID: manifest.LastAssetID,
|
||||
Name: artifact.Name,
|
||||
Size: int(stat.Size()),
|
||||
URL: filepath.Join(metadata.Version, relativePath),
|
||||
}
|
||||
|
||||
// Add to the release.
|
||||
release.Assets = append(release.Assets, asset)
|
||||
}
|
||||
|
||||
// Add release to manifest.
|
||||
manifest.Releases = append(manifest.Releases, release)
|
||||
|
||||
// Write the manifest.
|
||||
err = writeManifestFile(manifestFile, manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If not a draft or prerelease, link latest to this release.
|
||||
if !a.Draft && !a.Prerelease {
|
||||
latestPath := filepath.Join(app.flags.Repo, "latest")
|
||||
os.Remove(latestPath)
|
||||
os.Symlink(metadata.Version, latestPath)
|
||||
}
|
||||
|
||||
log.Println("Added release", metadata.Version, "for", metadata.Name, "to the repo", app.flags.Repo)
|
||||
|
||||
return nil
|
||||
}
|
39
flags.go
Normal file
39
flags.go
Normal file
@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
type VersionFlag bool
|
||||
|
||||
func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil }
|
||||
func (v VersionFlag) IsBool() bool { return true }
|
||||
func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error {
|
||||
fmt.Println(appName + ": " + appVersion)
|
||||
app.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flags supplied to cli.
|
||||
type Flags struct {
|
||||
Version VersionFlag `name:"version" help:"Print version information and quit"`
|
||||
Repo string `help:"The path to a repo" required:"" type:"existingdir"`
|
||||
AddRelease AddReleaseCmd `cmd:"" help:"Add an release to the repo"`
|
||||
Prune PruneCmd `cmd:"" help:"Prune releases from repo."`
|
||||
}
|
||||
|
||||
// Parse the supplied flags.
|
||||
func (a *App) ParseFlags() *kong.Context {
|
||||
app.flags = &Flags{}
|
||||
|
||||
ctx := kong.Parse(app.flags,
|
||||
kong.Name(appName),
|
||||
kong.Description(appDescription),
|
||||
kong.UsageOnError(),
|
||||
kong.ConfigureHelp(kong.HelpOptions{
|
||||
Compact: true,
|
||||
}),
|
||||
)
|
||||
return ctx
|
||||
}
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
||||
module github.com/grmrgecko/goreleaser-http-repo-builder
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kong v1.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
||||
github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY=
|
||||
github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q=
|
||||
github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
57
goreleaser.go
Normal file
57
goreleaser.go
Normal file
@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The metadata needed from goreleaser.
|
||||
type Metadata struct {
|
||||
Name string `json:"project_name"`
|
||||
Version string `json:"version"`
|
||||
Date time.Time `json:"date"`
|
||||
}
|
||||
|
||||
// Read and parse metadata file
|
||||
func readMetadataFile(metadataFile string) (*Metadata, error) {
|
||||
// Read file, if error return the error.
|
||||
jsonFile, err := os.Open(metadataFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Attempt to decode the file.
|
||||
metadata := new(Metadata)
|
||||
decoder := json.NewDecoder(jsonFile)
|
||||
err = decoder.Decode(metadata)
|
||||
jsonFile.Close()
|
||||
|
||||
// Return the metadata and if any error occurred.
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
// Artifcat map.
|
||||
type Artifact struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Read and parse metadata file
|
||||
func readArtifactFile(artifactFile string) ([]*Artifact, error) {
|
||||
// Read file, if error return the error.
|
||||
jsonFile, err := os.Open(artifactFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Attempt to decode the file.
|
||||
var artifacts []*Artifact
|
||||
decoder := json.NewDecoder(jsonFile)
|
||||
err = decoder.Decode(&artifacts)
|
||||
jsonFile.Close()
|
||||
|
||||
// Return the metadata and if any error occurred.
|
||||
return artifacts, err
|
||||
}
|
28
main.go
Normal file
28
main.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "goreleaser-http-repo-builder"
|
||||
appDescription = "Builds a repo for use with go-selfupdate"
|
||||
appVersion = "0.1.0"
|
||||
)
|
||||
|
||||
// App is the global application structure for communicating between servers and storing information.
|
||||
type App struct {
|
||||
flags *Flags
|
||||
now time.Time
|
||||
}
|
||||
|
||||
var app *App
|
||||
|
||||
func main() {
|
||||
app = new(App)
|
||||
app.now = time.Now()
|
||||
ctx := app.ParseFlags()
|
||||
|
||||
err := ctx.Run()
|
||||
ctx.FatalIfErrorf(err)
|
||||
}
|
224
main_test.go
Normal file
224
main_test.go
Normal file
@ -0,0 +1,224 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Test the add release option.
|
||||
func TestAppFunctionality(t *testing.T) {
|
||||
// Make temp directory to build repo.
|
||||
dname, err := os.MkdirTemp("", "goreleaser-http-repo-builder")
|
||||
if err != nil {
|
||||
t.Errorf("error making tempdir: %s", err)
|
||||
}
|
||||
|
||||
// Get the tests dir with test files.
|
||||
testsDir, err := filepath.Abs("tests")
|
||||
if err != nil {
|
||||
t.Errorf("error finding tests dir: %s", err)
|
||||
}
|
||||
|
||||
// Now date for app defines.
|
||||
now, _ := time.Parse(time.DateOnly, "2024-10-08")
|
||||
|
||||
// Test adding a release of v0.1.
|
||||
os.Args = []string{"test", "--repo", dname, "add-release", "--notes", "This is a test.", "--release", filepath.Join(testsDir, "v0.1")}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx := app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Test adding a release of v0.1.1.
|
||||
os.Args = []string{"test", "--repo", dname, "add-release", "--draft", "--release", filepath.Join(testsDir, "v0.1.1")}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx = app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Test adding a release of v0.1.2.
|
||||
os.Args = []string{"test", "--repo", dname, "add-release", "--prerelease", "--published-at", "2024-10-05T22:15:21.731224367-05:00", "--release", filepath.Join(testsDir, "v0.1.2")}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx = app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Confirm the latest release is v0.1.0.
|
||||
latestPath, err := os.Readlink(filepath.Join(dname, "latest"))
|
||||
if err != nil {
|
||||
t.Errorf("error reading link to latest: %s", err)
|
||||
}
|
||||
if latestPath != "v0.1.0" {
|
||||
t.Error("the latest link isn't correctly linked")
|
||||
}
|
||||
|
||||
// Hash the manifest file without the published_at dates.
|
||||
hfun := md5.New()
|
||||
d, err := os.ReadFile(filepath.Join(dname, "manifest.yaml"))
|
||||
if err != nil {
|
||||
t.Errorf("error reading manifest file: %s", err)
|
||||
}
|
||||
|
||||
// Hash the result and confirm.
|
||||
hfun.Write(d)
|
||||
sum := hfun.Sum(nil)
|
||||
hash := hex.EncodeToString(sum)
|
||||
if hash != "01240af1d189ea540418903e15eb3068" {
|
||||
t.Errorf("hash isn't valid for manifest file: %s", hash)
|
||||
}
|
||||
|
||||
// Binaries are not included.
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.0/example_linux_amd64/example")); !os.IsNotExist(serr) {
|
||||
t.Error("v0.1.0 binary exists, when it shouldn't exist.")
|
||||
}
|
||||
|
||||
// Confirm the asset was copied correctly.
|
||||
d, err = os.ReadFile(filepath.Join(dname, "v0.1.0/example_linux_amd64.tar.gz"))
|
||||
if err != nil {
|
||||
t.Errorf("error reading test file: %s", err)
|
||||
}
|
||||
hfun.Reset()
|
||||
hfun.Write(d)
|
||||
sum = hfun.Sum(nil)
|
||||
hash = hex.EncodeToString(sum)
|
||||
if hash != "9cffcbe826ae684db1c8a08ff9216f34" {
|
||||
t.Errorf("hash isn't valid for test file: %s", hash)
|
||||
}
|
||||
|
||||
// Confirm pruning of max releases works.
|
||||
os.Args = []string{"test", "--repo", dname, "prune", "--max-age=216h"}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx = app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Confirm pruned state.
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.0/example_linux_amd64.tar.gz")); !os.IsNotExist(serr) {
|
||||
t.Error("v0.1.0 exists, when it shouldn't exist.")
|
||||
}
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.1/example_linux_amd64.tar.gz")); os.IsNotExist(serr) {
|
||||
t.Error("v0.1.1 does not exists, when it should.")
|
||||
}
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.2/example_linux_amd64.tar.gz")); os.IsNotExist(serr) {
|
||||
t.Error("v0.1.2 does not exists, when it should.")
|
||||
}
|
||||
|
||||
// Delete all files, and reset.
|
||||
os.RemoveAll(dname)
|
||||
os.Mkdir(dname, 0755)
|
||||
|
||||
// Test adding a release of v0.1.
|
||||
os.Args = []string{"test", "--repo", dname, "add-release", "--include-binary", "--release", filepath.Join(testsDir, "v0.1")}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx = app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Test adding a release of v0.1.1.
|
||||
os.Args = []string{"test", "--repo", dname, "add-release", "--release", filepath.Join(testsDir, "v0.1.1")}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx = app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Test adding a release of v0.1.2.
|
||||
os.Args = []string{"test", "--repo", dname, "add-release", "--published-at-now", "--release", filepath.Join(testsDir, "v0.1.2")}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx = app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Confirm the latest release is v0.1.2.
|
||||
latestPath, err = os.Readlink(filepath.Join(dname, "latest"))
|
||||
if err != nil {
|
||||
t.Errorf("error reading link to latest: %s", err)
|
||||
}
|
||||
if latestPath != "v0.1.2" {
|
||||
t.Error("the latest link isn't correctly linked")
|
||||
}
|
||||
|
||||
// Hash the manifest file without the published_at dates.
|
||||
hfun.Reset()
|
||||
d, err = os.ReadFile(filepath.Join(dname, "manifest.yaml"))
|
||||
if err != nil {
|
||||
t.Errorf("error reading manifest file: %s", err)
|
||||
}
|
||||
|
||||
// Hash the result and confirm.
|
||||
hfun.Write(d)
|
||||
sum = hfun.Sum(nil)
|
||||
hash = hex.EncodeToString(sum)
|
||||
if hash != "dfac4ec2fc35bb04c8f5f79e057dfbe9" {
|
||||
t.Errorf("hash isn't valid for manifest file: %s", hash)
|
||||
}
|
||||
|
||||
// Binaries are not included.
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.0/example_linux_amd64/example")); os.IsNotExist(serr) {
|
||||
t.Error("v0.1.0 binary does not exists, when it shouldn.")
|
||||
}
|
||||
|
||||
// Confirm pruning of max releases works.
|
||||
os.Args = []string{"test", "--repo", dname, "prune", "--max-releases=1"}
|
||||
app = new(App)
|
||||
app.now = now
|
||||
ctx = app.ParseFlags()
|
||||
|
||||
// Run the command.
|
||||
err = ctx.Run()
|
||||
if err != nil {
|
||||
t.Errorf("error running the app: %s", err)
|
||||
}
|
||||
|
||||
// Confirm pruned state.
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.0/example_linux_amd64.tar.gz")); !os.IsNotExist(serr) {
|
||||
t.Error("v0.1.0 exists, when it shouldn't exist.")
|
||||
}
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.1/example_linux_amd64.tar.gz")); !os.IsNotExist(serr) {
|
||||
t.Error("v0.1.1 exists, when it shouldn't exist.")
|
||||
}
|
||||
if _, serr := os.Stat(filepath.Join(dname, "v0.1.2/example_linux_amd64.tar.gz")); os.IsNotExist(serr) {
|
||||
t.Error("v0.1.2 does not exists, when it should.")
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
os.RemoveAll(dname)
|
||||
}
|
71
manifest.go
Normal file
71
manifest.go
Normal file
@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// An individual asset.
|
||||
type HttpAsset struct {
|
||||
ID int64 `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
Size int `yaml:"size"`
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// An individual release.
|
||||
type HttpRelease struct {
|
||||
ReleaseID int64 `yaml:"release_id"`
|
||||
Name string `yaml:"name"`
|
||||
TagName string `yaml:"tag_name"`
|
||||
URL string `yaml:"url"`
|
||||
Draft bool `yaml:"draft"`
|
||||
Prerelease bool `yaml:"prerelease"`
|
||||
PublishedAt time.Time `yaml:"published_at"`
|
||||
ReleaseNotes string `yaml:"release_notes"`
|
||||
Assets []*HttpAsset `yaml:"assets"`
|
||||
}
|
||||
|
||||
// The manifest file structure.
|
||||
type HttpManifest struct {
|
||||
LastReleaseID int64 `yaml:"last_release_id"`
|
||||
LastAssetID int64 `yaml:"last_asset_id"`
|
||||
Releases []*HttpRelease `yaml:"releases"`
|
||||
}
|
||||
|
||||
// Read and parse manifest file.
|
||||
func readManifestFile(manifestFile string) (*HttpManifest, error) {
|
||||
// We always want a manifest incase repo just needs to start from scratch.
|
||||
manifest := new(HttpManifest)
|
||||
|
||||
// Read file, if error return the error.
|
||||
yamlFile, err := os.Open(manifestFile)
|
||||
if err != nil {
|
||||
return manifest, err
|
||||
}
|
||||
|
||||
// Attempt to decode the file.
|
||||
decoder := yaml.NewDecoder(yamlFile)
|
||||
err = decoder.Decode(manifest)
|
||||
yamlFile.Close()
|
||||
|
||||
// Return the manifest and if any error occurred.
|
||||
return manifest, err
|
||||
}
|
||||
|
||||
// Write manifest file.
|
||||
func writeManifestFile(manifestFile string, manifest *HttpManifest) error {
|
||||
// Open the file for write.
|
||||
yamlFile, err := os.OpenFile(manifestFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer yamlFile.Close()
|
||||
|
||||
// Encode data.
|
||||
encoder := yaml.NewEncoder(yamlFile)
|
||||
err = encoder.Encode(manifest)
|
||||
return err
|
||||
}
|
125
prune_cmd.go
Normal file
125
prune_cmd.go
Normal file
@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PruneCmd struct {
|
||||
MaxAge time.Duration `help:"Delete releases older than."`
|
||||
MaxReleases int `help:"Maximum number of releases to keep."`
|
||||
DryRun bool `help:"Just log the result without actually pruning."`
|
||||
}
|
||||
|
||||
// Extra help to explain you can't set 2 prune stratages.
|
||||
func (a *PruneCmd) Help() string {
|
||||
return "You cannot use both max-age and max-releases, only set one."
|
||||
}
|
||||
|
||||
// Verify the options provided to the command.
|
||||
func (a *PruneCmd) AfterApply() error {
|
||||
// If both stratages are defined, we don't allow that.
|
||||
if a.MaxAge > time.Duration(0) && a.MaxReleases > 0 {
|
||||
return errors.New("must only provide one prune argument")
|
||||
}
|
||||
// If no stratages are defined, we don't allow that.
|
||||
if a.MaxAge <= time.Duration(0) && a.MaxReleases <= 0 {
|
||||
return errors.New("must provide one prune argument")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Adds a release to a repo.
|
||||
func (a *PruneCmd) Run() error {
|
||||
// Read existing manifest for repo.
|
||||
manifestFile := filepath.Join(app.flags.Repo, "manifest.yaml")
|
||||
manifest, err := readManifestFile(manifestFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Keep reference of number of pruned releases.
|
||||
releasesPruned := 0
|
||||
n := len(manifest.Releases)
|
||||
|
||||
// If max releases defined and is less than number of releases, look for items to prune.
|
||||
if a.MaxReleases > 0 && n > a.MaxReleases {
|
||||
// Loop starting at max releases.
|
||||
for i := a.MaxReleases; i < n; i++ {
|
||||
// Get the current release.
|
||||
// We want pull from the top of the stack downward to keep newer releases.
|
||||
version := manifest.Releases[n-(i+1)].TagName
|
||||
log.Println("Removing release:", version)
|
||||
|
||||
// If this isn't a dry run, remove the version directory.
|
||||
if !a.DryRun {
|
||||
err = os.RemoveAll(filepath.Join(app.flags.Repo, version))
|
||||
if err != nil {
|
||||
return fmt.Errorf("untable to remove release files: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Count the number pruned.
|
||||
releasesPruned++
|
||||
}
|
||||
|
||||
// Remove releases from the slice.
|
||||
manifest.Releases = manifest.Releases[n-a.MaxReleases:]
|
||||
}
|
||||
|
||||
// If we are pruning based on duration, do so.
|
||||
if a.MaxAge > time.Duration(0) {
|
||||
// Loop through the releases, and find old releases.
|
||||
for i := 0; i < n; i++ {
|
||||
// If n is 1, we removed too many entries and need to stop.
|
||||
if n == 1 {
|
||||
log.Println("The repo has only 1 release remaining, ending the prune here to keep 1 release.")
|
||||
break
|
||||
}
|
||||
|
||||
// Get the current release, and confirm its age.
|
||||
release := manifest.Releases[i]
|
||||
if app.now.Sub(release.PublishedAt) >= a.MaxAge {
|
||||
// This release is too old, so we need to remove it.
|
||||
version := release.TagName
|
||||
log.Println("Removing release:", version)
|
||||
|
||||
// If this isn't a dry run, remove the version files.
|
||||
if !a.DryRun {
|
||||
err = os.RemoveAll(filepath.Join(app.flags.Repo, version))
|
||||
if err != nil {
|
||||
return fmt.Errorf("untable to remove release files: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the release from the slice.
|
||||
manifest.Releases = append(manifest.Releases[:i], manifest.Releases[i+1:]...)
|
||||
|
||||
// Back up one on the index and number of releases as one release was deleted.
|
||||
i--
|
||||
n--
|
||||
|
||||
// Count number of releases pruned.
|
||||
releasesPruned++
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the manifest if this isn't a dry run.
|
||||
if !a.DryRun {
|
||||
err = writeManifestFile(manifestFile, manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Provide details on what's been pruned.
|
||||
log.Println("Pruned", releasesPruned, "release from the repo.")
|
||||
|
||||
return nil
|
||||
}
|
34
tests/v0.1.1/artifacts.json
Normal file
34
tests/v0.1.1/artifacts.json
Normal file
@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"name": "metadata.json",
|
||||
"path": "v0.1.1/metadata.json",
|
||||
"internal_type": 30,
|
||||
"type": "Metadata"
|
||||
},
|
||||
{
|
||||
"name": "example_linux_amd64.tar.gz",
|
||||
"path": "v0.1.1/example_linux_amd64.tar.gz",
|
||||
"goos": "linux",
|
||||
"goarch": "amd64",
|
||||
"goamd64": "v1",
|
||||
"internal_type": 1,
|
||||
"type": "Archive",
|
||||
"extra": {
|
||||
"Binaries": [
|
||||
"example"
|
||||
],
|
||||
"Checksum": "sha256:9208c58af1265438c6894499847355bd5e77f93d04b201393baf41297d4680a3",
|
||||
"Format": "tar.gz",
|
||||
"ID": "default",
|
||||
"Replaces": null,
|
||||
"WrappedIn": "example_linux_amd64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "checksums.txt",
|
||||
"path": "v0.1.1/checksums.txt",
|
||||
"internal_type": 12,
|
||||
"type": "Checksum",
|
||||
"extra": {}
|
||||
}
|
||||
]
|
1
tests/v0.1.1/checksums.txt
Normal file
1
tests/v0.1.1/checksums.txt
Normal file
@ -0,0 +1 @@
|
||||
9208c58af1265438c6894499847355bd5e77f93d04b201393baf41297d4680a3 example_linux_amd64.tar.gz
|
113
tests/v0.1.1/config.yaml
Normal file
113
tests/v0.1.1/config.yaml
Normal file
@ -0,0 +1,113 @@
|
||||
version: 2
|
||||
project_name: example
|
||||
release:
|
||||
github:
|
||||
owner: GRMrGecko
|
||||
name: goreleaser-http-repo-builder
|
||||
name_template: '{{.Tag}}'
|
||||
builds:
|
||||
- id: example
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
gomips:
|
||||
- hardfloat
|
||||
goamd64:
|
||||
- v1
|
||||
targets:
|
||||
- linux_amd64_v1
|
||||
- linux_arm64
|
||||
dir: .
|
||||
main: .
|
||||
binary: example
|
||||
builder: go
|
||||
gobinary: go
|
||||
command: build
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
archives:
|
||||
- id: default
|
||||
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}'
|
||||
format: tar.gz
|
||||
wrap_in_directory: "true"
|
||||
files:
|
||||
- src: license*
|
||||
- src: LICENSE*
|
||||
- src: readme*
|
||||
- src: README*
|
||||
- src: changelog*
|
||||
- src: CHANGELOG*
|
||||
snapshot:
|
||||
version_template: v0.1.0
|
||||
checksum:
|
||||
name_template: checksums.txt
|
||||
algorithm: sha256
|
||||
changelog:
|
||||
format: '{{ .SHA }}: {{ .Message }} ({{ with .AuthorUsername }}@{{ . }}{{ else }}{{ .AuthorName }} <{{ .AuthorEmail }}>{{ end }})'
|
||||
dist: dist
|
||||
env_files:
|
||||
github_token: ~/.config/goreleaser/github_token
|
||||
gitlab_token: ~/.config/goreleaser/gitlab_token
|
||||
gitea_token: ~/.config/goreleaser/gitea_token
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
source:
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}'
|
||||
format: tar.gz
|
||||
gomod:
|
||||
gobinary: go
|
||||
announce:
|
||||
twitter:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
mastodon:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
server: ""
|
||||
reddit:
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
url_template: '{{ .ReleaseURL }}'
|
||||
slack:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
username: GoReleaser
|
||||
discord:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
author: GoReleaser
|
||||
color: "3888754"
|
||||
icon_url: https://goreleaser.com/static/avatar.png
|
||||
teams:
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
color: '#2D313E'
|
||||
icon_url: https://goreleaser.com/static/avatar.png
|
||||
smtp:
|
||||
subject_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
body_template: 'You can view details from: {{ .ReleaseURL }}'
|
||||
mattermost:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
username: GoReleaser
|
||||
linkedin:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
telegram:
|
||||
message_template: '{{ mdv2escape .ProjectName }} {{ mdv2escape .Tag }} is out{{ mdv2escape "!" }} Check it out at {{ mdv2escape .ReleaseURL }}'
|
||||
parse_mode: MarkdownV2
|
||||
webhook:
|
||||
message_template: '{ "message": "{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}"}'
|
||||
content_type: application/json; charset=utf-8
|
||||
opencollective:
|
||||
title_template: '{{ .Tag }}'
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out!<br/>Check it out at <a href="{{ .ReleaseURL }}">{{ .ReleaseURL }}</a>'
|
||||
bluesky:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
git:
|
||||
tag_sort: -version:refname
|
||||
github_urls:
|
||||
download: https://github.com
|
||||
gitlab_urls:
|
||||
download: https://gitlab.com
|
2
tests/v0.1.1/example_linux_amd64.tar.gz
Normal file
2
tests/v0.1.1/example_linux_amd64.tar.gz
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
ŽíõÞ}k© Î<>Iƒ`É<>cú|ÀGéNÙãðFñttV—hÉtÍËËõ€ÕØÙŸ¥øF
|
12
tests/v0.1.1/metadata.json
Normal file
12
tests/v0.1.1/metadata.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"project_name": "example",
|
||||
"tag": "v0.0.0",
|
||||
"previous_tag": "",
|
||||
"version": "v0.1.1",
|
||||
"commit": "521be63afb85d785ce36b4bd0d7412664593ac1d",
|
||||
"date": "2024-09-30T09:26:01.612178185-05:00",
|
||||
"runtime": {
|
||||
"goos": "linux",
|
||||
"goarch": "amd64"
|
||||
}
|
||||
}
|
34
tests/v0.1.2/artifacts.json
Normal file
34
tests/v0.1.2/artifacts.json
Normal file
@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"name": "metadata.json",
|
||||
"path": "v0.1.2/metadata.json",
|
||||
"internal_type": 30,
|
||||
"type": "Metadata"
|
||||
},
|
||||
{
|
||||
"name": "example_linux_amd64.tar.gz",
|
||||
"path": "v0.1.2/example_linux_amd64.tar.gz",
|
||||
"goos": "linux",
|
||||
"goarch": "amd64",
|
||||
"goamd64": "v1",
|
||||
"internal_type": 1,
|
||||
"type": "Archive",
|
||||
"extra": {
|
||||
"Binaries": [
|
||||
"example"
|
||||
],
|
||||
"Checksum": "sha256:9208c58af1265438c6894499847355bd5e77f93d04b201393baf41297d4680a3",
|
||||
"Format": "tar.gz",
|
||||
"ID": "default",
|
||||
"Replaces": null,
|
||||
"WrappedIn": "example_linux_amd64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "checksums.txt",
|
||||
"path": "v0.1.2/checksums.txt",
|
||||
"internal_type": 12,
|
||||
"type": "Checksum",
|
||||
"extra": {}
|
||||
}
|
||||
]
|
1
tests/v0.1.2/checksums.txt
Normal file
1
tests/v0.1.2/checksums.txt
Normal file
@ -0,0 +1 @@
|
||||
9208c58af1265438c6894499847355bd5e77f93d04b201393baf41297d4680a3 example_linux_amd64.tar.gz
|
113
tests/v0.1.2/config.yaml
Normal file
113
tests/v0.1.2/config.yaml
Normal file
@ -0,0 +1,113 @@
|
||||
version: 2
|
||||
project_name: example
|
||||
release:
|
||||
github:
|
||||
owner: GRMrGecko
|
||||
name: goreleaser-http-repo-builder
|
||||
name_template: '{{.Tag}}'
|
||||
builds:
|
||||
- id: example
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
gomips:
|
||||
- hardfloat
|
||||
goamd64:
|
||||
- v1
|
||||
targets:
|
||||
- linux_amd64_v1
|
||||
- linux_arm64
|
||||
dir: .
|
||||
main: .
|
||||
binary: example
|
||||
builder: go
|
||||
gobinary: go
|
||||
command: build
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
archives:
|
||||
- id: default
|
||||
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}'
|
||||
format: tar.gz
|
||||
wrap_in_directory: "true"
|
||||
files:
|
||||
- src: license*
|
||||
- src: LICENSE*
|
||||
- src: readme*
|
||||
- src: README*
|
||||
- src: changelog*
|
||||
- src: CHANGELOG*
|
||||
snapshot:
|
||||
version_template: v0.1.0
|
||||
checksum:
|
||||
name_template: checksums.txt
|
||||
algorithm: sha256
|
||||
changelog:
|
||||
format: '{{ .SHA }}: {{ .Message }} ({{ with .AuthorUsername }}@{{ . }}{{ else }}{{ .AuthorName }} <{{ .AuthorEmail }}>{{ end }})'
|
||||
dist: dist
|
||||
env_files:
|
||||
github_token: ~/.config/goreleaser/github_token
|
||||
gitlab_token: ~/.config/goreleaser/gitlab_token
|
||||
gitea_token: ~/.config/goreleaser/gitea_token
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
source:
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}'
|
||||
format: tar.gz
|
||||
gomod:
|
||||
gobinary: go
|
||||
announce:
|
||||
twitter:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
mastodon:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
server: ""
|
||||
reddit:
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
url_template: '{{ .ReleaseURL }}'
|
||||
slack:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
username: GoReleaser
|
||||
discord:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
author: GoReleaser
|
||||
color: "3888754"
|
||||
icon_url: https://goreleaser.com/static/avatar.png
|
||||
teams:
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
color: '#2D313E'
|
||||
icon_url: https://goreleaser.com/static/avatar.png
|
||||
smtp:
|
||||
subject_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
body_template: 'You can view details from: {{ .ReleaseURL }}'
|
||||
mattermost:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
username: GoReleaser
|
||||
linkedin:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
telegram:
|
||||
message_template: '{{ mdv2escape .ProjectName }} {{ mdv2escape .Tag }} is out{{ mdv2escape "!" }} Check it out at {{ mdv2escape .ReleaseURL }}'
|
||||
parse_mode: MarkdownV2
|
||||
webhook:
|
||||
message_template: '{ "message": "{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}"}'
|
||||
content_type: application/json; charset=utf-8
|
||||
opencollective:
|
||||
title_template: '{{ .Tag }}'
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out!<br/>Check it out at <a href="{{ .ReleaseURL }}">{{ .ReleaseURL }}</a>'
|
||||
bluesky:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
git:
|
||||
tag_sort: -version:refname
|
||||
github_urls:
|
||||
download: https://github.com
|
||||
gitlab_urls:
|
||||
download: https://gitlab.com
|
2
tests/v0.1.2/example_linux_amd64.tar.gz
Normal file
2
tests/v0.1.2/example_linux_amd64.tar.gz
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
ŽíõÞ}k© Î<>Iƒ`É<>cú|ÀGéNÙãðFñttV—hÉtÍËËõ€ÕØÙŸ¥øF
|
12
tests/v0.1.2/metadata.json
Normal file
12
tests/v0.1.2/metadata.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"project_name": "example",
|
||||
"tag": "v0.0.0",
|
||||
"previous_tag": "",
|
||||
"version": "v0.1.2",
|
||||
"commit": "94bb85eb32ea07f33d627ce0dee905e29d8d1c96",
|
||||
"date": "2024-10-07T22:15:21.731224367-05:00",
|
||||
"runtime": {
|
||||
"goos": "linux",
|
||||
"goarch": "amd64"
|
||||
}
|
||||
}
|
47
tests/v0.1/artifacts.json
Normal file
47
tests/v0.1/artifacts.json
Normal file
@ -0,0 +1,47 @@
|
||||
[
|
||||
{
|
||||
"name": "metadata.json",
|
||||
"path": "v0.1/metadata.json",
|
||||
"internal_type": 30,
|
||||
"type": "Metadata"
|
||||
},
|
||||
{
|
||||
"name": "example",
|
||||
"path": "v0.1/example_linux_amd64/example",
|
||||
"goos": "linux",
|
||||
"goarch": "amd64",
|
||||
"internal_type": 4,
|
||||
"type": "Binary",
|
||||
"extra": {
|
||||
"Binary": "example",
|
||||
"Ext": "",
|
||||
"ID": "example"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "example_linux_amd64.tar.gz",
|
||||
"path": "v0.1/example_linux_amd64.tar.gz",
|
||||
"goos": "linux",
|
||||
"goarch": "amd64",
|
||||
"goamd64": "v1",
|
||||
"internal_type": 1,
|
||||
"type": "Archive",
|
||||
"extra": {
|
||||
"Binaries": [
|
||||
"example"
|
||||
],
|
||||
"Checksum": "sha256:9208c58af1265438c6894499847355bd5e77f93d04b201393baf41297d4680a3",
|
||||
"Format": "tar.gz",
|
||||
"ID": "default",
|
||||
"Replaces": null,
|
||||
"WrappedIn": "example_linux_amd64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "checksums.txt",
|
||||
"path": "v0.1/checksums.txt",
|
||||
"internal_type": 12,
|
||||
"type": "Checksum",
|
||||
"extra": {}
|
||||
}
|
||||
]
|
1
tests/v0.1/checksums.txt
Normal file
1
tests/v0.1/checksums.txt
Normal file
@ -0,0 +1 @@
|
||||
9208c58af1265438c6894499847355bd5e77f93d04b201393baf41297d4680a3 example_linux_amd64.tar.gz
|
113
tests/v0.1/config.yaml
Normal file
113
tests/v0.1/config.yaml
Normal file
@ -0,0 +1,113 @@
|
||||
version: 2
|
||||
project_name: example
|
||||
release:
|
||||
github:
|
||||
owner: GRMrGecko
|
||||
name: goreleaser-http-repo-builder
|
||||
name_template: '{{.Tag}}'
|
||||
builds:
|
||||
- id: example
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
gomips:
|
||||
- hardfloat
|
||||
goamd64:
|
||||
- v1
|
||||
targets:
|
||||
- linux_amd64_v1
|
||||
- linux_arm64
|
||||
dir: .
|
||||
main: .
|
||||
binary: example
|
||||
builder: go
|
||||
gobinary: go
|
||||
command: build
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
archives:
|
||||
- id: default
|
||||
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}'
|
||||
format: tar.gz
|
||||
wrap_in_directory: "true"
|
||||
files:
|
||||
- src: license*
|
||||
- src: LICENSE*
|
||||
- src: readme*
|
||||
- src: README*
|
||||
- src: changelog*
|
||||
- src: CHANGELOG*
|
||||
snapshot:
|
||||
version_template: v0.1.0
|
||||
checksum:
|
||||
name_template: checksums.txt
|
||||
algorithm: sha256
|
||||
changelog:
|
||||
format: '{{ .SHA }}: {{ .Message }} ({{ with .AuthorUsername }}@{{ . }}{{ else }}{{ .AuthorName }} <{{ .AuthorEmail }}>{{ end }})'
|
||||
dist: dist
|
||||
env_files:
|
||||
github_token: ~/.config/goreleaser/github_token
|
||||
gitlab_token: ~/.config/goreleaser/gitlab_token
|
||||
gitea_token: ~/.config/goreleaser/gitea_token
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
source:
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}'
|
||||
format: tar.gz
|
||||
gomod:
|
||||
gobinary: go
|
||||
announce:
|
||||
twitter:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
mastodon:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
server: ""
|
||||
reddit:
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
url_template: '{{ .ReleaseURL }}'
|
||||
slack:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
username: GoReleaser
|
||||
discord:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
author: GoReleaser
|
||||
color: "3888754"
|
||||
icon_url: https://goreleaser.com/static/avatar.png
|
||||
teams:
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
color: '#2D313E'
|
||||
icon_url: https://goreleaser.com/static/avatar.png
|
||||
smtp:
|
||||
subject_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
body_template: 'You can view details from: {{ .ReleaseURL }}'
|
||||
mattermost:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
title_template: '{{ .ProjectName }} {{ .Tag }} is out!'
|
||||
username: GoReleaser
|
||||
linkedin:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
telegram:
|
||||
message_template: '{{ mdv2escape .ProjectName }} {{ mdv2escape .Tag }} is out{{ mdv2escape "!" }} Check it out at {{ mdv2escape .ReleaseURL }}'
|
||||
parse_mode: MarkdownV2
|
||||
webhook:
|
||||
message_template: '{ "message": "{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}"}'
|
||||
content_type: application/json; charset=utf-8
|
||||
opencollective:
|
||||
title_template: '{{ .Tag }}'
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out!<br/>Check it out at <a href="{{ .ReleaseURL }}">{{ .ReleaseURL }}</a>'
|
||||
bluesky:
|
||||
message_template: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'
|
||||
git:
|
||||
tag_sort: -version:refname
|
||||
github_urls:
|
||||
download: https://github.com
|
||||
gitlab_urls:
|
||||
download: https://gitlab.com
|
2
tests/v0.1/example_linux_amd64.tar.gz
Normal file
2
tests/v0.1/example_linux_amd64.tar.gz
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
ŽíõÞ}k© Î<>Iƒ`É<>cú|ÀGéNÙãðFñttV—hÉtÍËËõ€ÕØÙŸ¥øF
|
2
tests/v0.1/example_linux_amd64/example
Normal file
2
tests/v0.1/example_linux_amd64/example
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
ŽíõÞ}k© Î<>Iƒ`É<>cú|ÀGéNÙãðFñttV—hÉtÍËËõ€ÕØÙŸ¥øF
|
12
tests/v0.1/metadata.json
Normal file
12
tests/v0.1/metadata.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"project_name": "example",
|
||||
"tag": "v0.0.0",
|
||||
"previous_tag": "",
|
||||
"version": "v0.1.0",
|
||||
"commit": "1869b4455d572d3e3c7e00114586b47f184a2e35",
|
||||
"date": "2024-09-27T12:10:11.314135185-05:00",
|
||||
"runtime": {
|
||||
"goos": "linux",
|
||||
"goarch": "amd64"
|
||||
}
|
||||
}
|
61
util.go
Normal file
61
util.go
Normal file
@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Helper for CLI to ask for confirmation.
|
||||
func askForConfirmation(message string) bool {
|
||||
// Read stdanrd input for each new line.
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
|
||||
// Loop the question until answered.
|
||||
for {
|
||||
fmt.Printf("%s [y/n]: ", message)
|
||||
|
||||
// Get next line.
|
||||
scanner.Scan()
|
||||
resp := strings.ToLower(strings.TrimSpace(scanner.Text()))
|
||||
|
||||
// Check if yes or no.
|
||||
switch resp {
|
||||
case "y", "yes":
|
||||
return true
|
||||
case "n", "no":
|
||||
return false
|
||||
default:
|
||||
fmt.Println("Invalid answer.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for copying files.
|
||||
func copyFile(srcFile, dstFile string) (err error) {
|
||||
// Open the source file.
|
||||
f, err := os.Open(srcFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Open the destination file.
|
||||
d, err := os.Create(dstFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
// Copy the data to the new file.
|
||||
_, err = io.Copy(d, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure new file is fully written.
|
||||
err = d.Sync()
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue
Block a user