-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: allow publishing to Brew via custom script
- Loading branch information
1 parent
3577e7e
commit c666541
Showing
9 changed files
with
479 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module ocm.software/ocm/hack/brew | ||
|
||
go 1.23.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package internal | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"text/template" | ||
) | ||
|
||
const ClassName = "Ocm" | ||
|
||
// GenerateVersionedHomebrewFormula generates a Homebrew formula for a specific version, | ||
// architecture, and operating system. It fetches the SHA256 digest for each combination | ||
// and uses a template to create the formula file. | ||
func GenerateVersionedHomebrewFormula( | ||
version string, | ||
architectures []string, | ||
operatingSystems []string, | ||
releaseURL string, | ||
templateFile string, | ||
outputDir string, | ||
writer io.Writer, | ||
) error { | ||
values := map[string]string{ | ||
"ReleaseURL": releaseURL, | ||
"Version": version, | ||
} | ||
|
||
for _, targetOs := range operatingSystems { | ||
for _, arch := range architectures { | ||
digest, err := FetchDigestFromGithubRelease(releaseURL, version, targetOs, arch) | ||
if err != nil { | ||
return fmt.Errorf("failed to fetch digest for %s/%s: %w", targetOs, arch, err) | ||
} | ||
values[fmt.Sprintf("%s_%s_sha256", targetOs, arch)] = digest | ||
} | ||
} | ||
|
||
if err := GenerateFormula(templateFile, outputDir, version, values, writer); err != nil { | ||
return fmt.Errorf("failed to generate formula: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// FetchDigestFromGithubRelease retrieves the SHA256 digest for a specific version, operating system, and architecture | ||
// from the given release URL. | ||
func FetchDigestFromGithubRelease(releaseURL, version, targetOs, arch string) (_ string, err error) { | ||
url := fmt.Sprintf("%s/v%s/ocm-%s-%s-%s.tar.gz.sha256", releaseURL, version, version, targetOs, arch) | ||
resp, err := http.Get(url) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get digest: %w", err) | ||
} | ||
defer func() { | ||
err = errors.Join(err, resp.Body.Close()) | ||
}() | ||
|
||
digestBytes, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to read digest: %w", err) | ||
} | ||
|
||
return strings.TrimSpace(string(digestBytes)), nil | ||
} | ||
|
||
// GenerateFormula generates the Homebrew formula file using the provided template and values. | ||
func GenerateFormula(templateFile, outputDir, version string, values map[string]string, writer io.Writer) error { | ||
tmpl, err := template.New(filepath.Base(templateFile)).Funcs(template.FuncMap{ | ||
"classname": func() string { | ||
return fmt.Sprintf("%sAT%s", ClassName, strings.ReplaceAll(version, ".", "")) | ||
}, | ||
}).ParseFiles(templateFile) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse template: %w", err) | ||
} | ||
|
||
outputFile := fmt.Sprintf("ocm@%s.rb", version) | ||
if err := ensureDirectory(outputDir); err != nil { | ||
return err | ||
} | ||
|
||
versionedFormula, err := os.Create(filepath.Join(outputDir, outputFile)) | ||
if err != nil { | ||
return fmt.Errorf("failed to create output file: %w", err) | ||
} | ||
defer versionedFormula.Close() | ||
|
||
if err := tmpl.Execute(versionedFormula, values); err != nil { | ||
return fmt.Errorf("failed to execute template: %w", err) | ||
} | ||
|
||
if _, err := io.WriteString(writer, versionedFormula.Name()); err != nil { | ||
return fmt.Errorf("failed to write output file path: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ensureDirectory checks if a directory exists and creates it if it does not. | ||
func ensureDirectory(dir string) error { | ||
fi, err := os.Stat(dir) | ||
if os.IsNotExist(err) { | ||
if err := os.MkdirAll(dir, 0755); err != nil { | ||
return fmt.Errorf("failed to create directory: %w", err) | ||
} | ||
} else if err != nil { | ||
return fmt.Errorf("failed to stat directory: %w", err) | ||
} else if !fi.IsDir() { | ||
return fmt.Errorf("path is not a directory") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package internal | ||
|
||
import ( | ||
"bytes" | ||
_ "embed" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
//go:embed ocm_formula_template.rb.tpl | ||
var tplFile []byte | ||
|
||
//go:embed testdata/expected_formula.rb | ||
var expectedResolved []byte | ||
|
||
func TestGenerateVersionedHomebrewFormula(t *testing.T) { | ||
version := "1.0.0" | ||
architectures := []string{"amd64", "arm64"} | ||
operatingSystems := []string{"darwin", "linux"} | ||
outputDir := t.TempDir() | ||
|
||
templateFile := filepath.Join(outputDir, "ocm_formula_template.rb.tpl") | ||
if err := os.WriteFile(templateFile, tplFile, os.ModePerm); err != nil { | ||
t.Fatalf("failed to write template file: %v", err) | ||
} | ||
|
||
dummyDigest := "dummy-digest" | ||
// Mock server to simulate fetching digests | ||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte(dummyDigest)) | ||
})) | ||
defer server.Close() | ||
expectedResolved = bytes.ReplaceAll(expectedResolved, []byte("$$TEST_SERVER$$"), []byte(server.URL)) | ||
|
||
var buf bytes.Buffer | ||
|
||
err := GenerateVersionedHomebrewFormula( | ||
version, | ||
architectures, | ||
operatingSystems, | ||
server.URL, | ||
templateFile, | ||
outputDir, | ||
&buf, | ||
) | ||
if err != nil { | ||
t.Fatalf("expected no error, got %v", err) | ||
} | ||
|
||
file := buf.String() | ||
|
||
fi, err := os.Stat(file) | ||
if err != nil { | ||
t.Fatalf("expected no error, got %v", err) | ||
} | ||
if fi.Size() == 0 { | ||
t.Fatalf("expected file to be non-empty") | ||
} | ||
if filepath.Ext(file) != ".rb" { | ||
t.Fatalf("expected file to have .rb extension") | ||
} | ||
if !strings.Contains(file, version) { | ||
t.Fatalf("expected file to contain version") | ||
} | ||
|
||
data, err := os.ReadFile(file) | ||
if err != nil { | ||
t.Fatalf("expected no error, got %v", err) | ||
} | ||
|
||
if string(data) != string(expectedResolved) { | ||
t.Fatalf("expected %s, got %s", string(expectedResolved), string(data)) | ||
} | ||
} | ||
|
||
func TestFetchDigest(t *testing.T) { | ||
expectedDigest := "dummy-digest" | ||
version := "1.0.0" | ||
targetOS, arch := "linux", "amd64" | ||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if r.URL.Path != "/v1.0.0/ocm-1.0.0-linux-amd64.tar.gz.sha256" { | ||
t.Fatalf("expected path %s, got %s", fmt.Sprintf("/v%[1]s/ocm-%[1]s-%s-%s.tar.gz.sha256", version, targetOS, arch), r.URL.Path) | ||
} | ||
w.Write([]byte(expectedDigest)) | ||
})) | ||
defer server.Close() | ||
|
||
digest, err := FetchDigestFromGithubRelease(server.URL, version, targetOS, arch) | ||
if err != nil { | ||
t.Fatalf("expected no error, got %v", err) | ||
} | ||
if digest != expectedDigest { | ||
t.Fatalf("expected %s, got %s", expectedDigest, digest) | ||
} | ||
} | ||
|
||
func TestGenerateFormula(t *testing.T) { | ||
templateContent := `class {{ classname }} < Formula | ||
version "{{ .Version }}" | ||
end` | ||
templateFile := "test_template.rb.tpl" | ||
if err := os.WriteFile(templateFile, []byte(templateContent), 0644); err != nil { | ||
t.Fatalf("failed to write template file: %v", err) | ||
} | ||
defer os.Remove(templateFile) | ||
|
||
outputDir := t.TempDir() | ||
values := map[string]string{"Version": "1.0.0"} | ||
|
||
var buf bytes.Buffer | ||
|
||
if err := GenerateFormula(templateFile, outputDir, "1.0.0", values, &buf); err != nil { | ||
t.Fatalf("expected no error, got %v", err) | ||
} | ||
|
||
if buf.String() == "" { | ||
t.Fatalf("expected non-empty output") | ||
} | ||
|
||
outputFile := filepath.Join(outputDir, "[email protected]") | ||
if _, err := os.Stat(outputFile); os.IsNotExist(err) { | ||
t.Fatalf("expected output file to exist") | ||
} | ||
} | ||
|
||
func TestEnsureDirectory(t *testing.T) { | ||
dir := t.TempDir() | ||
if err := ensureDirectory(dir); err != nil { | ||
t.Fatalf("expected no error, got %v", err) | ||
} | ||
|
||
nonDirFile := filepath.Join(dir, "file") | ||
if err := os.WriteFile(nonDirFile, []byte("content"), 0644); err != nil { | ||
t.Fatalf("failed to write file: %v", err) | ||
} | ||
|
||
if err := ensureDirectory(nonDirFile); err == nil { | ||
t.Fatalf("expected error, got nil") | ||
} | ||
} |
Oops, something went wrong.