diff --git a/release/formula/main.go b/release/formula/main.go index fdb4af1ea6..f534e133a1 100644 --- a/release/formula/main.go +++ b/release/formula/main.go @@ -15,103 +15,84 @@ package main import ( - "bytes" + "context" + "crypto/sha256" + "encoding/hex" "fmt" + "hash" "io" "net/http" "os" - "os/exec" "path/filepath" "strings" - "text/template" ) func main() { - if len(os.Args) < 2 { - fmt.Fprintf(os.Stderr, "must specify new version\n") - os.Exit(1) - } - input := Input{Version: os.Args[1]} - var err error - input.Sha, err = getSha(input.Version) + err := run(context.Background()) if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } +} - // generate the formula text - t, err := template.New("formula").Parse(formula) - if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(1) +func run(ctx context.Context) error { + if len(os.Args) < 2 { + return fmt.Errorf("must specify new version") } - // write the new formula - b := &bytes.Buffer{} - if err = t.Execute(b, input); err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(1) + version := os.Args[1] + url := "https://github.com/GoogleContainerTools/kpt/archive/" + version + ".tar.gz" + + formula, err := buildFormula(http.DefaultClient, url) + if err != nil { + return err } - err = os.WriteFile(filepath.Join("Formula", "kpt.rb"), b.Bytes(), 0644) + err = os.WriteFile(filepath.Join("Formula", "kpt.rb"), []byte(formula), 0644) if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(1) + return err } + return nil } -func getSha(version string) (string, error) { - // create the dir for the data - d, err := os.MkdirTemp("", "kpt-bin") +func buildFormula(httpClient *http.Client, url string) (string, error) { + sha256, err := hashURL(httpClient, url, sha256.New()) if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) return "", err } - defer os.RemoveAll(d) - fmt.Println( - "fetching https://github.com/GoogleContainerTools/kpt/archive/" + version + ".tar.gz") + // generate the formula text + formula := formulaTemplate + formula = strings.ReplaceAll(formula, "{{url}}", url) + formula = strings.ReplaceAll(formula, "{{sha256}}", sha256) + + return formula, nil +} + +func hashURL(httpClient *http.Client, url string, hasher hash.Hash) (string, error) { + fmt.Printf("fetching %q\n", url) + // get the content - resp, err := http.Get( - "https://github.com/GoogleContainerTools/kpt/archive/" + version + ".tar.gz") + resp, err := httpClient.Get(url) if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - return "", err + return "", fmt.Errorf("error getting %q: %w", url, err) } defer resp.Body.Close() - // write the file - func() { - out, err := os.Create(filepath.Join(d, version+".tar.gz")) - if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(1) - } - - if _, err = io.Copy(out, resp.Body); err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - os.Exit(1) - } - out.Close() - }() + if resp.StatusCode != 200 { + return "", fmt.Errorf("unexpected response from %q: %v", url, resp.Status) + } - // calculate the sha - e := exec.Command("sha256sum", filepath.Join(d, version+".tar.gz")) - o, err := e.Output() - if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - return "", err + if _, err := io.Copy(hasher, resp.Body); err != nil { + return "", fmt.Errorf("error hashing response from %q: %w", url, err) } - parts := strings.Split(string(o), " ") - fmt.Println("new sha: " + parts[0]) - return parts[0], nil -} -type Input struct { - Version string - Sha string + // calculate the sha + hash := hasher.Sum(nil) + return hex.EncodeToString(hash), nil } -const formula = `# Copyright 2019 Google LLC +const formulaTemplate = `# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -128,8 +109,8 @@ const formula = `# Copyright 2019 Google LLC class Kpt < Formula desc "Toolkit to manage,and apply Kubernetes Resource config data files" homepage "https://googlecontainertools.github.io/kpt" - url "https://github.com/GoogleContainerTools/kpt/archive/{{.Version}}.tar.gz" - sha256 "{{.Sha}}" + url "{{url}}" + sha256 "{{sha256}}" depends_on "go" => :build diff --git a/release/formula/main_test.go b/release/formula/main_test.go new file mode 100644 index 0000000000..7ea62ebbd1 --- /dev/null +++ b/release/formula/main_test.go @@ -0,0 +1,87 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "io" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestFormula(t *testing.T) { + want := `# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Kpt < Formula + desc "Toolkit to manage,and apply Kubernetes Resource config data files" + homepage "https://googlecontainertools.github.io/kpt" + url "https://github.com/GoogleContainerTools/kpt/archive/v0.0.0-fake.tar.gz" + sha256 "4e42c5ce1a23511405beb5f51cfe07885fa953db448265fe74ee9b81e0def277" + + depends_on "go" => :build + + def install + ENV["GO111MODULE"] = "on" + system "go", "build", "-ldflags", "-X github.com/GoogleContainerTools/kpt/run.version=#{version}", *std_go_args + end + + test do + assert_match version.to_s, shell_output("#{bin}/kpt version") + end +end +` + + httpClient := &http.Client{ + Transport: &fakeServer{t: t}, + } + url := "https://github.com/GoogleContainerTools/kpt/archive/v0.0.0-fake.tar.gz" + got, err := buildFormula(httpClient, url) + if err != nil { + t.Fatalf("error from buildFormula(%q): %v", url, err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("buildFormula(%q) returned unexpected diff (-want +got):\n%s", url, diff) + } +} + +type fakeServer struct { + t *testing.T +} + +func (s *fakeServer) RoundTrip(req *http.Request) (*http.Response, error) { + if req.URL.Path != "/GoogleContainerTools/kpt/archive/v0.0.0-fake.tar.gz" { + s.t.Errorf("Expected to request '/GoogleContainerTools/kpt/archive/v0.0.0-fake.tar.gz', got: %s", req.URL.Path) + } + body := bytes.NewReader([]byte(`This is fake content so that our tests don't download big files`)) + return &http.Response{ + Status: http.StatusText(http.StatusOK), + StatusCode: http.StatusOK, + Body: io.NopCloser(body), + }, nil +}