Skip to content

Commit

Permalink
tools: introduce git-change-exec tool
Browse files Browse the repository at this point in the history
This new tool detects if in your git tree changed files
(compared to master branch and local-only changed files)
and uses this information to run only the specified actions.

Here it is used to run pillar's go-tests only if something
changed there, same for this tool itself and the get-deps tool.

Signed-off-by: Christoph Ostarek <[email protected]>
  • Loading branch information
christoph-zededa committed Sep 9, 2024
1 parent 87af44f commit 4385415
Show file tree
Hide file tree
Showing 13 changed files with 850 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .github/workflows/go-tests.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Copyright (c) 2024 Zededa, Inc.
# SPDX-License-Identifier: Apache-2.0

---
name: Go Tests
on: # yamllint disable-line rule:truthy
Expand Down
3 changes: 3 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Copyright (c) 2024 Zededa, Inc.
# SPDX-License-Identifier: Apache-2.0

---
default: true
line-length: false
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,9 @@ currentversion:

test: $(LINUXKIT) test-images-patches | $(DIST)
@echo Running tests on $(GOMODULE)
make -C pkg/pillar test
make -C tools/git-change-exec
./tools/git-change-exec/git-change-exec test
touch pkg/pillar/results.json pkg/pillar/results.xml
cp pkg/pillar/results.json $(DIST)/
cp pkg/pillar/results.xml $(DIST)/
$(QUIET): $@: Succeeded
Expand Down
1 change: 1 addition & 0 deletions tools/git-change-exec/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
git-change-exec
10 changes: 10 additions & 0 deletions tools/git-change-exec/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2024 Zededa, Inc.
# SPDX-License-Identifier: Apache-2.0

git-change-exec: main.go actions.go
go build

.PHONY: clean

clean:
rm -fv git-change-exec
31 changes: 31 additions & 0 deletions tools/git-change-exec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# git-change-exec

This new tool detects if in your git tree files changed
compared to:

* master branch
* stable branches

also local-only files are considered.

Here it is used to run pillar's go-tests only if something
changed there, same for this tool itself and the get-deps tool.

## Add new Action

Open `actions.go` add new implementation of `action` `interface` and
add it to the `actions` array.

Run `git-change-exec -d` to see if your action gets triggered without
running.

## Example output

```bash
2024/09/04 09:49:17 --- running gitChangeExecTest ...
=== RUN TestId
--- PASS: TestId (0.00s)
PASS
ok git-change-exec 0.004s
2024/09/04 09:49:20 --- running gitChangeExecTest done
```
205 changes: 205 additions & 0 deletions tools/git-change-exec/actions-lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright (c) 2024 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"fmt"
"github.com/go-git/go-git/v5/config"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)

type lintSpdx struct {
extsMap map[string]func(path string)
pathsToFix []string
ownPath string
organization string
}

func (s *lintSpdx) dontfix(path string) {
log.Printf("Cannot fix %s ...\n", path)
}

func (s *lintSpdx) copyright(commentIndicator string) []string {
copyrightLines := []string{
fmt.Sprintf("%s Copyright (c) %d %s, Inc.\n", commentIndicator, time.Now().Year(), s.organization),
fmt.Sprintf("%s SPDX-License-Identifier: Apache-2.0\n\n", commentIndicator),
}

return copyrightLines
}

func (s *lintSpdx) yamlfix(path string) {
log.Printf("Fixing %s ...\n", path)
prepend(path, s.copyright("#"))
}

func (s *lintSpdx) gofix(path string) {
log.Printf("Fixing %s ...\n", path)
prepend(path, s.copyright("//"))
}

func (s *lintSpdx) dockerfilefix(path string) {
log.Printf("Fixing %s ...\n", path)
prepend(path, s.copyright("#"))
}

func prepend(path string, license []string) {
backupFh, err := os.CreateTemp("/var/tmp", "git-change-exec-spdx-fix")
if err != nil {
log.Fatalf("could not create temp file: %v", err)
}
backupPath := backupFh.Name()
defer os.Remove(backupPath)

for _, line := range license {
fmt.Fprint(backupFh, line)
}

origFh, err := os.Open(path)
if err != nil {
log.Fatalf("could not open original file %s: %v", path, err)
}

_, err = io.Copy(backupFh, origFh)
if err != nil {
log.Fatalf("could not copy: %v", err)
}

backupFh.Close()
origFh.Close()

err = copyFile(backupPath, path)
if err != nil {
log.Fatalf("could not rename %s -> %s: %v", backupPath, path, err)
}

}

func readGitConfigOrganization() string {
cfg, err := config.LoadConfig(config.GlobalScope)
if err != nil {
panic(err)
}

for _, sec := range cfg.Raw.Sections {
if sec.Name != "user" {
continue
}
organizations := sec.OptionAll("organization")

for _, organization := range organizations {
if organization != "" {
return organization
}
}
}

return ""
}

func (s *lintSpdx) init() {
var err error

s.extsMap = map[string]func(path string){
".sh": s.dontfix,
".go": s.gofix,
".c": s.dontfix,
".h": s.dontfix,
".py": s.dontfix,
".rs": s.dontfix,
".yaml": s.yamlfix,
".yml": s.yamlfix,
"Dockerfile": s.dockerfilefix,
}

s.pathsToFix = make([]string, 0)

s.ownPath, err = os.Executable()
if err != nil {
log.Fatalf("could not determine executable path: %v", err)
}

s.organization = readGitConfigOrganization()

}

func (s *lintSpdx) pathMatch(path string) (func(path string), bool) {
f, found := s.extsMap[filepath.Ext(path)]
if !found {
f, found = s.extsMap[filepath.Base(path)]
}

return f, found
}

func (s *lintSpdx) hasSpdx(path string) bool {
scriptPath := filepath.Join(filepath.Dir(s.ownPath), "..", "spdx-check.sh")

cmd := exec.Command(scriptPath, path)
err := cmd.Run()

return err == nil
}

func (s *lintSpdx) match(path string) bool {
if s.extsMap == nil {
s.init()
}

if strings.Contains(path, "/vendor/") {
return false
}
_, found := s.pathMatch(path)
if !found {
return false
}
if !s.hasSpdx(path) {
s.pathsToFix = append(s.pathsToFix, path)
return true
}

return false
}

func (s *lintSpdx) do() error {
if s.organization == "" {
log.Printf("could not read organization from git config, cannot fix copyrights")
return nil
}

for _, path := range s.pathsToFix {
extFixFunc, found := s.pathMatch(path)
if !found {
continue
}

extFixFunc(path)
}

return nil
}

func copyFile(srcPath, dstPath string) error {
src, err := os.Open(srcPath)
if err != nil {
return err
}
defer src.Close()

dst, err := os.Create(dstPath)
if err != nil {
return err
}
defer dst.Close()

_, err = io.Copy(dst, src)

return err
}
40 changes: 40 additions & 0 deletions tools/git-change-exec/actions-test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2024 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0

// Copyright (c) 2024 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0

package main

import "strings"

type pillarTestAction struct{}

func (b pillarTestAction) match(path string) bool {
return strings.HasPrefix(path, "pkg/pillar")
}

func (b pillarTestAction) do() error {
return execCmdWithDefaults("make", "-C", "pkg/pillar", "test").Run()
}

type getDepsTestAction struct{}

func (g getDepsTestAction) match(path string) bool {
return strings.HasPrefix(path, "tools/get-deps")

}
func (g getDepsTestAction) do() error {
return execCmdWithDefaults("go", "test", "-C", "tools/get-deps", "-v").Run()
}

type gitChangeExecTest struct{}

func (g gitChangeExecTest) match(path string) bool {
return strings.HasPrefix(path, "tools/git-change-exec")

}
func (g gitChangeExecTest) do() error {
return execCmdWithDefaults("go", "test", "-C", "tools/git-change-exec", "-v").Run()
}

45 changes: 45 additions & 0 deletions tools/git-change-exec/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2024 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"os"
"os/exec"
"reflect"
)

type action interface {
match(path string) bool
do() error
}

func id(i any) string {
ty := reflect.TypeOf(i)
if ty.Name() == "" {
ty = reflect.TypeOf(i).Elem()
}
return ty.Name()
}

func execCmdWithDefaults(name string, args ...string) *exec.Cmd {
cmd := exec.Command(name, args...)

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin

return cmd
}

// Do not forget to add your Action HERE
var actions = map[string][]action{
"test": []action{
pillarTestAction{},
gitChangeExecTest{},
getDepsTestAction{},
},
"lint": []action{
&lintSpdx{},
},
}
15 changes: 15 additions & 0 deletions tools/git-change-exec/actions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2024 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0

package main

import "testing"

func TestId(t *testing.T) {
p := pillarTestAction{}

id := id(p)
if id != "pillarTestAction" {
t.Fatalf("wrong id: %s\n", id)
}
}
Loading

0 comments on commit 4385415

Please sign in to comment.