diff --git a/.github/workflows/testbuild.yml b/.github/workflows/testbuild.yml index 2b8a3c4..1a41210 100644 --- a/.github/workflows/testbuild.yml +++ b/.github/workflows/testbuild.yml @@ -19,3 +19,14 @@ jobs: run: go mod tidy - name: Run tests run: go test ./... -v + lint: + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v3 + - name: Set up Go + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v3 + with: + go-version: '1.22.1' + - name: golangci-lint + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v3 \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..daf1327 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,111 @@ +run: + go: "1.21" + issues-exit-code: 1 + timeout: 5m + +linters-settings: + lll: + line-length: 130 + gocyclo: + min-complexity: 15 + gci: + sections: + - standard + - default + - prefix(github.com/stacklok/frizbee-action) + revive: + # see https://github.com/mgechev/revive#available-rules for details. + ignore-generated-header: true + severity: warning + rules: + - name: blank-imports + severity: warning + - name: context-as-argument + - name: context-keys-type + - name: duplicated-imports + - name: error-naming + # - name: error-strings #BDG: This was enabled for months, but it suddenly started working on 3/2/2022.. come to find out we have TONS of error messages starting with capital... disabling for now(ever?) + - name: error-return + - name: exported + severity: error + - name: if-return + # - name: get-return // BDG: We have a lot of API endpoint handlers named like getFoos but write to response vs return... maybe later can figure that out + - name: identical-branches + - name: indent-error-flow + - name: import-shadowing + - name: package-comments + - name: range-val-in-closure + - name: range-val-address + - name: redefines-builtin-id + - name: struct-tag + - name: unconditional-recursion + - name: unnecessary-stmt + - name: unreachable-code + - name: unused-parameter + - name: unused-receiver + - name: unhandled-error + disabled: true + +linters: + disable-all: true + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - decorder + - dogsled + - dupl + - errcheck + - errname + - exhaustive + - exportloopref + - forbidigo + - forcetypeassert + - gci + - gochecknoglobals + - gochecknoinits + - gochecksumtype + - goconst + - gocyclo + - gofmt + - goprintffuncname + - gosec + - gosimple + - gosimple + - govet + - importas + - inamedparam + - ineffassign + - interfacebloat + - lll + - makezero + - mirror + - noctx + - nosprintfhostport + - paralleltest + - perfsprint + - promlinter + - revive + - staticcheck + - tenv + - thelper + - tparallel + - unparam + - unused + - usestdlibvars + +issues: + exclude-use-default: false + exclude-rules: + - path: '(.+)_test\.go' + linters: + - lll + +output: + formats: + - format: colored-line-number + print-issued-lines: true + print-linter-name: true + sort-results: true diff --git a/main.go b/main.go index 74be470..143531e 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package main provides the entrypoint for the frizbee action package main import ( @@ -20,20 +21,21 @@ import ( "encoding/json" "errors" "fmt" + "log" + "os" + "strings" + "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/storage/memory" "github.com/google/go-github/v60/github" - "github.com/stacklok/frizbee-action/pkg/action" "github.com/stacklok/frizbee/pkg/replacer" "github.com/stacklok/frizbee/pkg/utils/config" - "golang.org/x/oauth2" - "log" - "os" - "strings" + + "github.com/stacklok/frizbee-action/pkg/action" ) func main() { @@ -60,7 +62,7 @@ func initAction(ctx context.Context) (*action.FrizbeeAction, error) { // Get the GitHub token from the environment token := os.Getenv("GITHUB_TOKEN") if token == "" { - return nil, fmt.Errorf("GITHUB_TOKEN environment variable is not set") + return nil, errors.New("GITHUB_TOKEN environment variable is not set") } // Create a new GitHub client @@ -70,17 +72,17 @@ func initAction(ctx context.Context) (*action.FrizbeeAction, error) { // Get the GITHUB_REPOSITORY_OWNER repoOwner := os.Getenv("GITHUB_REPOSITORY_OWNER") if repoOwner == "" { - return nil, fmt.Errorf("GITHUB_REPOSITORY_OWNER environment variable is not set") + return nil, errors.New("GITHUB_REPOSITORY_OWNER environment variable is not set") } // Split the GITHUB_REPOSITORY environment variable to get repo name repoFullName := os.Getenv("GITHUB_REPOSITORY") if repoFullName == "" { - return nil, fmt.Errorf("GITHUB_REPOSITORY environment variable is not set") + return nil, errors.New("GITHUB_REPOSITORY environment variable is not set") } // Clone the repository - fs, repo, err := cloneRepository(fmt.Sprintf("https://github.com/%s", repoFullName), repoOwner, token) + fs, repo, err := cloneRepository("https://github.com/"+repoFullName, repoOwner, token) if err != nil { return nil, fmt.Errorf("failed to clone repository: %w", err) } diff --git a/pkg/action/action.go b/pkg/action/action.go index c953395..20732c0 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -13,11 +13,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package action provides the actual frizbee action package action import ( "context" "fmt" + "log" + "os" + "strings" + "time" + mapset "github.com/deckarep/golang-set/v2" "github.com/go-git/go-billy/v5" "github.com/go-git/go-git/v5" @@ -27,21 +33,8 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/google/go-github/v60/github" "github.com/stacklok/frizbee/pkg/replacer" - "log" - "os" - "time" ) -var prBody = ` -The following PR pins images and actions to their commit hash. - -Pinning images and actions to their commit hash ensures that the same version of the image or action is used every time the workflow runs. This is important for reproducibility and security. - -Pinning is a [security practice recommended by GitHub](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions). - -> 🌟 If you like this action, why not try out [Minder](https://github.com/stacklok/minder), the secure supply chain platform. It has vastly more protections and is also free (as in :beer:) to opensource projects. -` - // FrizbeeAction is the main struct for the frizbee action type FrizbeeAction struct { Client *github.Client @@ -60,6 +53,7 @@ type FrizbeeAction struct { ImagesReplacer *replacer.Replacer BFS billy.Filesystem Repo *git.Repository + bodyBuilder *strings.Builder } // Run runs the frizbee action @@ -228,7 +222,8 @@ func (fa *FrizbeeAction) createPR(ctx context.Context) error { } } - if existingPR != nil && existingPR.GetState() == "closed" && branchExists(ctx, fa.Client, fa.RepoOwner, fa.RepoName, branchName) { + if existingPR != nil && existingPR.GetState() == "closed" && + branchExists(ctx, fa.Client, fa.RepoOwner, fa.RepoName, branchName) { log.Printf("PR %d is closed. Won't do anything as long as branch %s exists", existingPR.GetNumber(), branchName) return nil } @@ -258,10 +253,10 @@ func (fa *FrizbeeAction) createPR(ctx context.Context) error { log.Fatalf("failed to push branch: %v", err) } - fmt.Printf("Branch %s pushed successfully\n", branchName) + log.Printf("Branch %s pushed successfully\n", branchName) if existingPR != nil && existingPR.GetState() == "open" { - fmt.Printf("PR %d already exists. Won't create a new one\n", existingPR.GetNumber()) + log.Printf("PR %d already exists. Won't create a new one\n", existingPR.GetNumber()) return nil } // either the PR doesn't exist or was merged and it's time for another one @@ -273,10 +268,20 @@ func (fa *FrizbeeAction) createPR(ctx context.Context) error { } defaultBranch := repository.GetDefaultBranch() + fa.bodyBuilder = &strings.Builder{} + fa.bodyBuilder.WriteString("The following PR pins images and actions to their commit hash.\n\n") + fa.bodyBuilder.WriteString("Pinning images and actions to their commit hash ensures that the same " + + "version of the image or action is used every time the workflow runs. This is important for " + + "reproducibility and security.\n\n") + //nolint:lll + fa.bodyBuilder.WriteString("Pinning is a [security practice recommended by GitHub](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions).\n\n") + //nolint:lll + fa.bodyBuilder.WriteString("> 🌟 If you like this action, why not try out [Minder](https://github.com/stacklok/minder), the secure supply chain platform. It has vastly more protections and is also free (as in :beer:) to opensource projects.") + // Create a new PR pr, _, err := fa.Client.PullRequests.Create(ctx, fa.RepoOwner, fa.RepoName, &github.NewPullRequest{ Title: github.String("Frizbee: Pin images and actions to commit hash"), - Body: github.String(prBody), + Body: github.String(fa.bodyBuilder.String()), Head: github.String(branchName), Base: github.String(defaultBranch), MaintainerCanModify: github.Bool(true), @@ -284,7 +289,7 @@ func (fa *FrizbeeAction) createPR(ctx context.Context) error { if err != nil { return err } - fmt.Printf("PR %d created successfully\n", pr.GetNumber()) + log.Printf("PR %d created successfully\n", pr.GetNumber()) return nil }