Skip to content

Commit

Permalink
chore(ci): add third party dep check (googleapis#9343)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahdietz authored Feb 4, 2024
1 parent 9ce3d5b commit 5bfee69
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 9 deletions.
46 changes: 46 additions & 0 deletions .github/workflows/third_party_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: third_party_check
on:
pull_request:
paths:
- '**/go.mod'

permissions:
contents: read

jobs:
changed_gomods:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: actions/setup-go@v4
with:
go-version: 1.21.x
- name: Find modified go.mod files
id: modfiles
run: |
dirs=$(go run ./internal/actions/cmd/changefinder -q -internal --base=HEAD~1 --diff-filter=d --path-filter='*go.mod')
if [ -z "$dirs" ]
then
echo "skip=1" >> "$GITHUB_OUTPUT"
echo "No new/modified go.mod files!"
else
for d in $dirs; do list=${list},\"${d}\"; done
echo "mods={\"mods\":[${list#,}]}" >> "$GITHUB_OUTPUT"
echo "skip=" >> "$GITHUB_OUTPUT"
fi
outputs:
mods: ${{ steps.modfiles.outputs.mods }}
skip: ${{ steps.modfiles.outputs.skip }}
check_deps:
needs: changed_gomods
runs-on: ubuntu-latest
if: "!needs.changed_gomods.outputs.skip"
continue-on-error: true
strategy:
matrix: ${{ fromJson(needs.changed_gomods.outputs.mods) }}
steps:
- uses: actions/checkout@v3
- run: go run ./internal/actions/cmd/thirdpartycheck -q -mod ${{ matrix.mods }}/go.mod
18 changes: 9 additions & 9 deletions internal/actions/cmd/changefinder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ var (
ghVarName = flag.String("gh-var", "submodules", "github format's variable name to set output for, defaults to 'submodules'.")
base = flag.String("base", "origin/main", "the base ref to compare to, defaults to 'origin/main'")
// See https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
filter = flag.String("diff-filter", "", "the git diff filter to apply [A|C|D|M|R|T|U|X|B] - lowercase to exclude")
pathFilter = flag.String("path-filter", "", "filter commits by changes to target path(s)")
contentPattern = flag.String("content-regex", "", "regular expression to execute against contents of diff")
commitMessage = flag.String("commit-message", "", "message to use with the module in nested commit format")
commitScope = flag.String("commit-scope", "", "scope to use in commit message - only for format=commit")
touch = flag.Bool("touch", false, "touches the CHANGES.md file to elicit a submodule change - only works when used with -format=commit")
filter = flag.String("diff-filter", "", "the git diff filter to apply [A|C|D|M|R|T|U|X|B] - lowercase to exclude")
pathFilter = flag.String("path-filter", "", "filter commits by changes to target path(s)")
contentPattern = flag.String("content-regex", "", "regular expression to execute against contents of diff")
commitMessage = flag.String("commit-message", "", "message to use with the module in nested commit format")
commitScope = flag.String("commit-scope", "", "scope to use in commit message - only for format=commit")
touch = flag.Bool("touch", false, "touches the CHANGES.md file to elicit a submodule change - only works when used with -format=commit")
includeInternal = flag.Bool("internal", false, "toggles inclusion of the internal modules")
)

func main() {
Expand Down Expand Up @@ -72,7 +73,7 @@ func main() {
modulesSeen := map[string]bool{}
updatedSubmoduleDirs := []string{}
for _, change := range changes {
if strings.HasPrefix(change, "internal") {
if strings.HasPrefix(change, "internal") && !*includeInternal {
continue
}
submodDir, ok := owner(change, submodulesDirs)
Expand Down Expand Up @@ -140,8 +141,7 @@ func modDirs(dir string) (submodulesDirs []string, err error) {

submodulesDirs = []string{}
for _, modPath := range list {
// Skip non-submodule or internal submodules.
if strings.Contains(modPath, "internal") {
if strings.Contains(modPath, "internal") && !*includeInternal {
continue
}
logg.Printf("found module: %s", modPath)
Expand Down
110 changes: 110 additions & 0 deletions internal/actions/cmd/thirdpartycheck/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2024 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 (
"flag"
"fmt"
"os"
"path"
"strings"

"cloud.google.com/go/internal/actions/logg"
"golang.org/x/mod/modfile"
)

var (
dir = flag.String("dir", "", "the root directory to evaluate")
mod = flag.String("mod", "", "path to go.mod file relative to root directory")

// List of allowlist module prefixes.
allowlist = []string{
// First party deps.
"cloud.google.com/go",
"github.com/google/",
"github.com/googleapis/",
"github.com/golang/",
"google.golang.org/",
"golang.org/",

// Third party deps (allowed).
"go.opencensus.io",
"go.opentelemetry.io/",

// Third party deps (temporary exception(s)).
"go.einride.tech/aip", // https://github.com/googleapis/google-cloud-go/issues/9338
}
)

func main() {
flag.BoolVar(&logg.Quiet, "q", false, "quiet mode, minimal logging")
flag.Parse()
if *mod == "" {
logg.Fatalf("missing required flag: -mod")
}

rootDir, err := os.Getwd()
if err != nil {
logg.Fatal(err)
}
if *dir != "" {
rootDir = *dir
}

modPath := path.Join(rootDir, *mod)
findings, err := check(modPath)
if err != nil {
logg.Fatal(err)
}

if len(findings) != 0 {
fmt.Println("found disallowed module(s) in direct dependencies:")
fmt.Println()
fmt.Printf("\t%s\n", strings.Join(findings, "\n\t"))
fmt.Println()
os.Exit(1)
}
}

// check reads & parses the specified go.mod, then validates that
// all direct dependencies are part of the allowlist.
func check(file string) ([]string, error) {
data, err := os.ReadFile(file)
if err != nil {
return nil, err
}
m, err := modfile.ParseLax(*mod, data, nil)
if err != nil {
return nil, err
}

var disallowed []string
for _, r := range m.Require {
if r.Indirect {
continue
}
var allowed bool
for _, a := range allowlist {
if strings.HasPrefix(r.Mod.Path, a) {
allowed = true
break
}
}
if !allowed {
disallowed = append(disallowed, r.Mod.Path)
}
}
return disallowed, nil
}
2 changes: 2 additions & 0 deletions internal/actions/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module cloud.google.com/go/internal/actions

go 1.19

require golang.org/x/mod v0.14.0
2 changes: 2 additions & 0 deletions internal/actions/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=

0 comments on commit 5bfee69

Please sign in to comment.