Skip to content

Commit

Permalink
Merge pull request #31 from jhrozek/interface
Browse files Browse the repository at this point in the history
Use a wrapper interface instead of using go-github directly
  • Loading branch information
jhrozek committed Dec 11, 2023
2 parents 5991373 + 659cb54 commit 41c54fa
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 42 deletions.
11 changes: 3 additions & 8 deletions cmd/ghactions/ghactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import (
"fmt"
"os"

"github.com/google/go-github/v56/github"
"github.com/spf13/cobra"

"github.com/stacklok/frizbee/internal/ghrest"
"github.com/stacklok/frizbee/pkg/config"
cliutils "github.com/stacklok/frizbee/pkg/utils/cli"
)
Expand Down Expand Up @@ -81,12 +81,7 @@ func replace(cmd *cobra.Command, _ []string) error {

ctx := cmd.Context()

ghcli := github.NewClient(nil)

tok := os.Getenv("GITHUB_TOKEN")
if tok != "" {
ghcli = ghcli.WithAuthToken(tok)
}
ghcli := ghrest.NewGhRest(os.Getenv("GITHUB_TOKEN"))

replacer := &replacer{
Replacer: cliutils.Replacer{
Expand All @@ -96,7 +91,7 @@ func replace(cmd *cobra.Command, _ []string) error {
ErrOnModified: errOnModified,
Cmd: cmd,
},
ghcli: ghcli,
restIf: ghcli,
}

return replacer.do(ctx, cfg)
Expand Down
9 changes: 2 additions & 7 deletions cmd/ghactions/one.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import (
"fmt"
"os"

"github.com/google/go-github/v56/github"
"github.com/spf13/cobra"

"github.com/stacklok/frizbee/internal/ghrest"
"github.com/stacklok/frizbee/pkg/ghactions"
)

Expand Down Expand Up @@ -52,12 +52,7 @@ func replaceOne(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
ref := args[0]

ghcli := github.NewClient(nil)

tok := os.Getenv("GITHUB_TOKEN")
if tok != "" {
ghcli = ghcli.WithAuthToken(tok)
}
ghcli := ghrest.NewGhRest(os.Getenv("GITHUB_TOKEN"))

act, ref, err := ghactions.ParseActionReference(ref)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions cmd/ghactions/replacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ import (
"sync/atomic"

"github.com/go-git/go-billy/v5/osfs"
"github.com/google/go-github/v56/github"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v3"

"github.com/stacklok/frizbee/pkg/config"
"github.com/stacklok/frizbee/pkg/ghactions"
"github.com/stacklok/frizbee/pkg/interfaces"
"github.com/stacklok/frizbee/pkg/utils"
cliutils "github.com/stacklok/frizbee/pkg/utils/cli"
)

type replacer struct {
cliutils.Replacer
ghcli *github.Client
restIf interfaces.REST
}

func (r *replacer) do(ctx context.Context, cfg *config.Config) error {
Expand All @@ -54,7 +54,7 @@ func (r *replacer) do(ctx context.Context, cfg *config.Config) error {
err := ghactions.TraverseGitHubActionWorkflows(bfs, base, func(path string, wflow *yaml.Node) error {
eg.Go(func() error {
r.Logf("Processing %s\n", path)
m, err := ghactions.ModifyReferencesInYAML(ctx, r.ghcli, wflow, &cfg.GHActions)
m, err := ghactions.ModifyReferencesInYAML(ctx, r.restIf, wflow, &cfg.GHActions)
if err != nil {
return fmt.Errorf("failed to process YAML file %s: %w", path, err)
}
Expand Down
70 changes: 70 additions & 0 deletions internal/ghrest/ghrest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// Copyright 2023 Stacklok, Inc.
//
// 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 ghrest provides a wrapper around the go-github client that implements the internal REST API
package ghrest

import (
"bytes"
"context"
"io"
"net/http"

"github.com/google/go-github/v56/github"
)

// GhRest is the struct that contains the GitHub REST API client
// this struct implements the REST API
type GhRest struct {
client *github.Client
}

// NewGhRest creates a new instance of GhRest
func NewGhRest(token string) *GhRest {
ghcli := github.NewClient(nil)

if token != "" {
ghcli = ghcli.WithAuthToken(token)
}
return &GhRest{
client: ghcli,
}
}

// NewRequest creates an API request. A relative URL can be provided in urlStr,
// which will be resolved to the BaseURL of the Client. Relative URLS should
// always be specified without a preceding slash. If specified, the value
// pointed to by body is JSON encoded and included as the request body.
func (c *GhRest) NewRequest(method, requestUrl string, body any) (*http.Request, error) {
return c.client.NewRequest(method, requestUrl, body)
}

// Do sends an API request and returns the API response.
func (c *GhRest) Do(ctx context.Context, req *http.Request) (*http.Response, error) {
var buf bytes.Buffer

// The GitHub client closes the response body, so we need to capture it
// in a buffer so that we can return it to the caller
resp, err := c.client.Do(ctx, req, &buf)
if err != nil && resp == nil {
return nil, err
}

if resp.Response != nil {
resp.Response.Body = io.NopCloser(&buf)
}

return resp.Response, err
}
14 changes: 7 additions & 7 deletions pkg/ghactions/ghactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import (

mapset "github.com/deckarep/golang-set/v2"
"github.com/go-git/go-billy/v5/osfs"
"github.com/google/go-github/v56/github"
"gopkg.in/yaml.v3"

"github.com/stacklok/frizbee/pkg/config"
"github.com/stacklok/frizbee/pkg/interfaces"
)

// IsLocal returns true if the input is a local path.
Expand All @@ -47,21 +47,21 @@ func ParseActionReference(input string) (action string, reference string, err er
}

// GetChecksum returns the checksum for a given action and tag.
func GetChecksum(ctx context.Context, ghcli *github.Client, action, ref string) (string, error) {
func GetChecksum(ctx context.Context, restIf interfaces.REST, action, ref string) (string, error) {
owner, repo, err := parseActionFragments(action)
if err != nil {
return "", err
}

res, err := getCheckSumForTag(ctx, ghcli, owner, repo, ref)
res, err := getCheckSumForTag(ctx, restIf, owner, repo, ref)
if err != nil {
return "", fmt.Errorf("failed to get checksum for tag: %w", err)
} else if res != "" {
return res, nil
}

// check branch
res, err = getCheckSumForBranch(ctx, ghcli, owner, repo, ref)
res, err = getCheckSumForBranch(ctx, restIf, owner, repo, ref)
if err != nil {
return "", fmt.Errorf("failed to get checksum for branch: %w", err)
} else if res != "" {
Expand All @@ -80,7 +80,7 @@ func GetChecksum(ctx context.Context, ghcli *github.Client, action, ref string)
// all references to tags with the checksum of the tag.
// Note that the given YAML structure is modified in-place.
// The function returns true if any references were modified.
func ModifyReferencesInYAML(ctx context.Context, ghcli *github.Client, node *yaml.Node, cfg *config.GHActions) (bool, error) {
func ModifyReferencesInYAML(ctx context.Context, restIf interfaces.REST, node *yaml.Node, cfg *config.GHActions) (bool, error) {
// `uses` will be immediately before the action
// name in the YAML `Content` array. We use a toggle
// to track if we've found `uses` and then look for
Expand Down Expand Up @@ -111,7 +111,7 @@ func ModifyReferencesInYAML(ctx context.Context, ghcli *github.Client, node *yam
return modified, fmt.Errorf("failed to parse action reference '%s': %w", v.Value, err)
}

sum, err := GetChecksum(ctx, ghcli, act, ref)
sum, err := GetChecksum(ctx, restIf, act, ref)
if err != nil {
return modified, fmt.Errorf("failed to get checksum for action '%s': %w", v.Value, err)
}
Expand All @@ -125,7 +125,7 @@ func ModifyReferencesInYAML(ctx context.Context, ghcli *github.Client, node *yam
}

// Otherwise recursively look more
m, err := ModifyReferencesInYAML(ctx, ghcli, v, cfg)
m, err := ModifyReferencesInYAML(ctx, restIf, v, cfg)
if err != nil {
return m, err
}
Expand Down
41 changes: 29 additions & 12 deletions pkg/ghactions/ghactions_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ package ghactions

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"

"github.com/google/go-github/v56/github"

"github.com/stacklok/frizbee/pkg/interfaces"
)

func parseActionFragments(action string) (owner string, repo string, err error) {
Expand All @@ -37,39 +40,53 @@ func parseActionFragments(action string) (owner string, repo string, err error)
return frags[0], frags[1], nil
}

func getCheckSumForTag(ctx context.Context, ghcli *github.Client, owner, repo, tag string) (string, error) {
func getCheckSumForTag(ctx context.Context, restIf interfaces.REST, owner, repo, tag string) (string, error) {
path, err := url.JoinPath("repos", owner, repo, "git", "refs", "tags", tag)
if err != nil {
return "", fmt.Errorf("failed to join path: %w", err)
}

return doGetReference(ctx, ghcli, path)
return doGetReference(ctx, restIf, path)
}

func getCheckSumForBranch(ctx context.Context, ghcli *github.Client, owner, repo, branch string) (string, error) {
func getCheckSumForBranch(ctx context.Context, restIf interfaces.REST, owner, repo, branch string) (string, error) {
path, err := url.JoinPath("repos", owner, repo, "git", "refs", "heads", branch)
if err != nil {
return "", fmt.Errorf("failed to join path: %w", err)
}

return doGetReference(ctx, ghcli, path)
return doGetReference(ctx, restIf, path)
}

func doGetReference(ctx context.Context, ghcli *github.Client, path string) (string, error) {
req, _ := ghcli.NewRequest(http.MethodGet, path, nil)
func doGetReference(ctx context.Context, restIf interfaces.REST, path string) (string, error) {
req, err := restIf.NewRequest(http.MethodGet, path, nil)
if err != nil {
return "", fmt.Errorf("cannot create REST request: %w", err)
}

resp, err := restIf.Do(ctx, req)

if resp != nil {
defer func() {
_ = resp.Body.Close()
}()
}

var t *github.Reference
resp, err := ghcli.Do(ctx, req, &t)
if err != nil && resp.StatusCode != http.StatusNotFound {
if err != nil && strings.Contains(err.Error(), "cannot unmarshal array into Go value of type") {
// This is a branch, not a tag
return "", nil
}
return "", fmt.Errorf("failed to do API request: %w", err)
} else if resp.StatusCode == http.StatusNotFound {
// No error, but no tag found
return "", nil
}

var t github.Reference
err = json.NewDecoder(resp.Body).Decode(&t)
if err != nil && strings.Contains(err.Error(), "cannot unmarshal array into Go value of type") {
// This is a branch, not a tag
return "", nil
} else if err != nil {
return "", fmt.Errorf("canont decode response: %w", err)
}

return t.GetObject().GetSHA(), nil
}
7 changes: 2 additions & 5 deletions pkg/ghactions/ghactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import (
"os"
"testing"

"github.com/google/go-github/v56/github"
"github.com/stretchr/testify/require"

"github.com/stacklok/frizbee/internal/ghrest"
"github.com/stacklok/frizbee/pkg/ghactions"
)

Expand Down Expand Up @@ -224,10 +224,7 @@ func TestGetChecksum(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ghcli := github.NewClient(nil)
if tok != "" {
ghcli = ghcli.WithAuthToken(tok)
}
ghcli := ghrest.NewGhRest(tok)
got, err := ghactions.GetChecksum(context.Background(), ghcli, tt.args.action, tt.args.ref)
if tt.wantErr {
require.Error(t, err, "Wanted error, got none")
Expand Down
33 changes: 33 additions & 0 deletions pkg/interfaces/rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright 2023 Stacklok, Inc.
//
// 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 interfaces provides generic interfaces for communicating with remotes
// like github
package interfaces

import (
"context"
"net/http"
)

// The REST interface allows to wrap clients to talk to remotes
// When talking to GitHub, wrap a github client to provide this interface
type REST interface {
// NewRequest creates an HTTP request.
NewRequest(method, url string, body any) (*http.Request, error)

// Do executes an HTTP request.
Do(ctx context.Context, req *http.Request) (*http.Response, error)
}

0 comments on commit 41c54fa

Please sign in to comment.