diff --git a/.github/goreleaser.yaml b/.github/goreleaser.yaml index f05ad7ffc..202c161ef 100644 --- a/.github/goreleaser.yaml +++ b/.github/goreleaser.yaml @@ -14,6 +14,7 @@ env: before: hooks: - go mod download + - make get-envoy - make build-deps - make yarn - make build-ui @@ -37,12 +38,6 @@ builds: - -X github.com/pomerium/pomerium/internal/version.ProjectName=pomerium - -X github.com/pomerium/pomerium/internal/version.ProjectURL=https://wwww.pomerium.io - hooks: - pre: - - cmd: ./scripts/get-envoy.bash - env: - - TARGET={{ .Os }}-{{ .Arch }} - archives: - name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" id: pomerium diff --git a/Makefile b/Makefile index 0d88e0e2b..ad75c537f 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ all: clean build-deps test lint build ## Runs a clean, build, fmt, lint, test, a .PHONY: get-envoy get-envoy: ## Fetch envoy binaries @echo "==> $@" - @./scripts/get-envoy.bash + @cd pkg/envoy/files && go run ../get-envoy .PHONY: deps-build deps-build: get-envoy ## Install build dependencies diff --git a/pkg/envoy/get-envoy/main.go b/pkg/envoy/get-envoy/main.go new file mode 100644 index 000000000..051befe5b --- /dev/null +++ b/pkg/envoy/get-envoy/main.go @@ -0,0 +1,135 @@ +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "time" + + "golang.org/x/sync/errgroup" + + "github.com/pomerium/pomerium/internal/log" +) + +func main() { + ctx := context.Background() + err := run(ctx) + if err != nil { + log.Fatal().Err(err).Send() + } +} + +func run( + ctx context.Context, +) error { + envoyVersion := "1.30.2" + targets := []string{ + "darwin-amd64", + "darwin-arm64", + "linux-amd64", + "linux-arm64", + } + baseURL := "https://github.com/pomerium/envoy-binaries/releases/download/v" + envoyVersion + eg, ctx := errgroup.WithContext(ctx) + for _, target := range targets { + target := target + eg.Go(func() error { + return download(ctx, "./envoy-"+target, baseURL+"/envoy-"+target) + }) + eg.Go(func() error { + return download(ctx, "./envoy-"+target+".sha256", baseURL+"/envoy-"+target+".sha256") + }) + eg.Go(func() error { + return os.WriteFile("./envoy-"+target+".version", []byte(envoyVersion+"\n"), 0o600) + }) + } + return eg.Wait() +} + +func download( + ctx context.Context, + dstPath string, + srcURL string, +) error { + fi, err := os.Stat(dstPath) + if err == nil { + lastModified, err := getURLLastModified(ctx, srcURL) + if err != nil { + return fmt.Errorf("error getting download last modified (url=%s): %w", srcURL, err) + } + + if timesMatch(fi.ModTime(), lastModified) { + log.Debug(ctx).Str("url", srcURL).Str("dst", dstPath).Msg("skipping download") + return nil + } + + } else if !os.IsNotExist(err) { + return fmt.Errorf("error reading destination path file info (dst=%s): %w", dstPath, err) + } + + log.Info(ctx).Str("url", srcURL).Str("dst", dstPath).Msg("downloading") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srcURL, nil) + if err != nil { + return fmt.Errorf("error creating GET request for download (url=%s): %w", srcURL, err) + } + + res, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("error making GET request for download (url=%s): %w", srcURL, err) + } + defer res.Body.Close() + + dst, err := os.Create(dstPath) + if err != nil { + return fmt.Errorf("error creating downloaded file (url=%s dst=%s): %w", srcURL, dstPath, err) + } + + _, err = io.Copy(dst, res.Body) + if err != nil { + _ = dst.Close() + _ = os.Remove(dstPath) + return fmt.Errorf("error copying downloaded file (url=%s dst=%s): %w", srcURL, dstPath, err) + } + + err = dst.Close() + if err != nil { + _ = os.Remove(dstPath) + return fmt.Errorf("error closing destination file: %w", err) + } + + if lastModified, err := time.Parse(http.TimeFormat, res.Header.Get("Last-Modified")); err == nil { + err = os.Chtimes(dstPath, time.Time{}, lastModified) + if err != nil { + return fmt.Errorf("error writing last modified timestamp: %w", err) + } + } + + return nil +} + +func getURLLastModified( + ctx context.Context, + srcURL string, +) (time.Time, error) { + // check to see if the file needs to be updated + headReq, err := http.NewRequestWithContext(ctx, http.MethodHead, srcURL, nil) + if err != nil { + return time.Time{}, fmt.Errorf("error creating head request for download: %w", err) + } + + res, err := http.DefaultClient.Do(headReq) + if err != nil { + return time.Time{}, fmt.Errorf("error making head request for download: %w", err) + } + _ = res.Body.Close() + + return time.Parse(http.TimeFormat, res.Header.Get("Last-Modified")) +} + +func timesMatch(tm1, tm2 time.Time) bool { + diff := tm2.Sub(tm1) + return diff >= -5*time.Minute && diff <= 5*time.Minute +} diff --git a/scripts/get-envoy.bash b/scripts/get-envoy.bash deleted file mode 100755 index 6c2561e4b..000000000 --- a/scripts/get-envoy.bash +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -euo pipefail - -PATH="$PATH:$(go env GOPATH)/bin" -export PATH - -_project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)/.." -_envoy_version=1.30.2 -_dir="$_project_root/pkg/envoy/files" - -for _target in darwin-amd64 darwin-arm64 linux-amd64 linux-arm64; do - _url="https://github.com/pomerium/envoy-binaries/releases/download/v${_envoy_version}/envoy-${_target}" - - curl \ - --silent \ - --fail \ - --show-error \ - --compressed \ - --location \ - --time-cond "$_dir/envoy-$_target" \ - --output "$_dir/envoy-$_target" \ - "$_url" & - - curl \ - --silent \ - --fail \ - --show-error \ - --compressed \ - --location \ - --time-cond "$_dir/envoy-$_target.sha256" \ - --output "$_dir/envoy-$_target.sha256" \ - "$_url.sha256" & - - echo "$_envoy_version" >"$_dir/envoy-$_target.version" -done - -wait