Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a3c1c00
feat: Add kubernetes targets
AtzeDeVries Feb 26, 2026
2d6fc76
add function/struct to setup routing to buf/std(in/out)
AtzeDeVries Mar 2, 2026
97cb2b2
enable dyff devtool
AtzeDeVries Mar 2, 2026
bf1334a
enable helm devtool
AtzeDeVries Mar 2, 2026
ef13eb9
add worktree func to git
AtzeDeVries Mar 2, 2026
0dd98e7
update kubernetes targets
AtzeDeVries Mar 2, 2026
928101b
merge main to this
AtzeDeVries Mar 2, 2026
7b2a138
sync cmd lib with mage pr
AtzeDeVries Mar 2, 2026
5e8bcaa
make file files in sub dir always return relative paths
AtzeDeVries Mar 2, 2026
cdcc5cd
use better var name
AtzeDeVries Mar 2, 2026
8616b7c
make kubercorm, helm annd kubescore actually work in docker
AtzeDeVries Mar 2, 2026
5b60779
make dyff also work in docker
AtzeDeVries Mar 2, 2026
87352b8
rewrite the render template again after some learning
AtzeDeVries Mar 2, 2026
f68483b
removed unused function
AtzeDeVries Mar 4, 2026
8d0b265
add github stuff
AtzeDeVries Mar 4, 2026
5cce67f
add changes detection
AtzeDeVries Mar 4, 2026
9c34703
use custom escape
AtzeDeVries Mar 4, 2026
a6ed219
add log groups
AtzeDeVries Mar 4, 2026
bb5d448
Merge branch 'main' into add-kubernetes
AtzeDeVries Mar 4, 2026
1179430
fix a bunch of linting issues
AtzeDeVries Mar 4, 2026
7fc417e
fix typos in devtool.dyff and add renovate snippet for dyff updates
AtzeDeVries Mar 6, 2026
f5e2c3b
add test for listrecursivefiles
AtzeDeVries Mar 6, 2026
ffb4804
make function outof printing buffers to strings
AtzeDeVries Mar 6, 2026
3919a63
return removal error as string in worktree
AtzeDeVries Mar 6, 2026
6230409
add kubernets validation as default target
AtzeDeVries Mar 6, 2026
4cd42ef
fix dyff version diff
AtzeDeVries Mar 6, 2026
5b47d37
fix version comment in kubescore
AtzeDeVries Mar 6, 2026
8222c8b
rename ListRecursiveFiles to ListFilesRecursively
AtzeDeVries Mar 6, 2026
6014e58
make finding of environment and related value values independed of a …
AtzeDeVries Mar 6, 2026
cb04608
fix double err check
AtzeDeVries Mar 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/goapp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
policy-bot: ${{ steps.policy-bot.outputs.policy_bot == 'true' && github.event_name == 'pull_request' }}
pallets: ${{ steps.pallets.outputs.pallets == 'true' && github.event_name == 'pull_request' }}
catalog-info: ${{ steps.catalog-info.outputs.catalog_info == 'true' && github.event_name == 'pull_request' }}
kubernetes: ${{ steps.kubernetes.outputs.kubernetes == 'true' && github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -98,6 +99,11 @@ jobs:
run: echo "catalog_info=$(go tool mage catalogInfo:changes)" >> $GITHUB_OUTPUT
env:
CHANGED_FILES: ${{ steps.filter.outputs.changed_files }}
- name: Check for Kubernetes changes
id: kubernetes
run: echo "kubernetes=$(go tool mage k8s:changes)" >> $GITHUB_OUTPUT
env:
CHANGED_FILES: ${{ steps.filter.outputs.changed_files }}

goapp:
name: Go application
Expand Down Expand Up @@ -149,3 +155,13 @@ jobs:
contents: read
pull-requests: read
secrets: inherit

kubernetes:
name: Kubernetes
needs: ["detect-changes"]
if: ${{ needs.detect-changes.outputs.kubernetes == 'true' }}
uses: ./.github/workflows/reusable-kubernetes.yaml
permissions:
contents: read
pull-requests: read
issues: write
39 changes: 39 additions & 0 deletions .github/workflows/reusable-kubernetes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
concurrency:
group: reusable-kubernetes-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_call:
inputs: {}
secrets: {}

jobs:
validate-kubernetes:
name: Validate Kuberenetes
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: write
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "stable"
cache-dependency-path: "**/go.sum"
- name: Install go Tools
run: go install tool

- name: Kubernetes List Detected charts
id: list
run: go tool mage k8s:list

- name: Kubernetes validation
id: validate
run: go tool mage k8s:validate

- name: Kubernetes validation
id: diff
run: go tool mage k8s:diff
131 changes: 98 additions & 33 deletions internal/core/cmd.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package core

// Copyright 2026 Coop Norge SA
// Copyright 2017 Nate Finch (Original Mage Authors)
//
// Licensed under the Apache License, Version 2.0;
// this file contains modifications from the original source.
// Original source: https://github.com/magefile/mage/blob/master/sh/cmd.go

import (
"bytes"
"fmt"
Expand All @@ -19,80 +12,155 @@ import (
"github.com/magefile/mage/mg"
)

// This is moslty just copied from the mage/sh library. Added because
// we need to introduce running commands in a different directory
// RunCmd returns a function that will call Run with the given command. This is
// useful for creating command aliases to make your scripts easier to read, like
// this:
//
// // in a helper file somewhere
// var g0 = sh.RunCmd("go") // go is a keyword :(
//
// // somewhere in your main code
// if err := g0("install", "github.com/gohugo/hugo"); err != nil {
// return err
// }
//
// Args passed to command get baked in as args to the command when you run it.
// Any args passed in when you run the returned function will be appended to the
// original args. For example, this is equivalent to the above:
//
// var goInstall = sh.RunCmd("go", "install") goInstall("github.com/gohugo/hugo")
//
// RunCmd uses Exec underneath, so see those docs for more details.
func RunCmd(cmd string, args ...string) func(args ...string) error {
return func(args2 ...string) error {
return Run(cmd, append(args, args2...)...)
}
}

// RunAtCmd returns a function that will call Run with the given command at a given path.
// This is useful for creating command aliases to make your scripts easier to read, like
// this:
//
// // in a helper file somewhere
// var g0 = sh.RunAtCmd("go") // go is a keyword :(
//
// // somewhere in your main code
// if err := g0("/tmp", "install", "github.com/gohugo/hugo"); err != nil {
// return err
// }
//
// Args passed to command get baked in as args to the command when you run it.
// Any args passed in when you run the returned function will be appended to the
// original args. For example, this is equivalent to the above:
//
// var goInstall = sh.RunAtCmd("go", "install") goInstall("tmp", "github.com/gohugo/hugo")
//
// RunAtCmd uses Exec underneath, so see those docs for more details.
func RunAtCmd(cmd string, args ...string) func(pwd string, args ...string) error {
return func(pwd string, args2 ...string) error {
return RunAt(pwd, cmd, append(args, args2...)...)
}
}

// OutCmd is like RunCmd except the command returns the output of the
// command.
func OutCmd(cmd string, args ...string) func(args ...string) (string, error) {
return func(args2 ...string) (string, error) {
return Output(cmd, append(args, args2...)...)
}
}

// OutAtCmd is like RunAtCmd except the command returns the output of the
// command.
func OutAtCmd(cmd string, args ...string) func(pwd string, args ...string) (string, error) {
return func(pwd string, args2 ...string) (string, error) {
return OutputAt(pwd, cmd, append(args, args2...)...)
}
}

// Run is like RunWith, but doesn't specify any environment variables.
func Run(cmd string, args ...string) error {
return RunAtWith(nil, "", cmd, args...)
return RunWith(nil, cmd, args...)
}

// RunAt is like RunAtWith, but doesn't specify any environment variables.
func RunAt(pwd string, cmd string, args ...string) error {
func RunAt(pwd, cmd string, args ...string) error {
return RunAtWith(nil, pwd, cmd, args...)
}

// RunV is like RunAtV, but doesn't specify any environment variables.
// RunV is like Run, but always sends the command's stdout to os.Stdout.
func RunV(cmd string, args ...string) error {
return RunAtV("", cmd, args...)
}

// RunAtV is like RunAt, but always sends the command's stdout to os.Stdout.
func RunAtV(pwd string, cmd string, args ...string) error {
_, err := Exec(nil, os.Stdout, os.Stderr, pwd, cmd, args...)
_, err := ExecAt(nil, os.Stdout, os.Stderr, pwd, cmd, args...)
return err
}

// RunAtWith runs the given command at a specific path, directing stderr to
// this program's stderr and printing stdout to stdout if mage was run with -v.
// It adds adds env to the environment variables for the command being run. Environment
// RunWith runs the given command, directing stderr to this program's stderr and
// printing stdout to stdout if mage was run with -v. It adds adds env to the
// environment variables for the command being run. Environment variables should
// be in the format name=value.
func RunWith(env map[string]string, cmd string, args ...string) error {
return RunAtWith(env, "", cmd, args...)
}

// RunAtWith runs the given command at a certain path, directing stderr to this
// program's stderr and printing stdout to stdout if mage was run with -v. It adds
// adds env to the environment variables for the command being run. Environment
// variables should be in the format name=value.
func RunAtWith(env map[string]string, pwd string, cmd string, args ...string) error {
var output io.Writer
if mg.Verbose() {
output = os.Stdout
}
_, err := Exec(env, output, os.Stderr, pwd, cmd, args...)
_, err := ExecAt(env, output, os.Stderr, pwd, cmd, args...)
return err
}

// RunWithV is like RunWith, but always sends the command's stdout to os.Stdout.
func RunWithV(env map[string]string, cmd string, args ...string) error {
_, err := Exec(env, os.Stdout, os.Stderr, "", cmd, args...)
return err
return RunAtWithV(env, "", cmd, args...)
}

// RunAtWithV is like RunAtWith, but always sends the command's stdout to os.Stdout.
func RunAtWithV(env map[string]string, pwd string, cmd string, args ...string) error {
_, err := Exec(env, os.Stdout, os.Stderr, pwd, cmd, args...)
_, err := ExecAt(env, os.Stdout, os.Stderr, pwd, cmd, args...)
return err
}

// Output is like OuttAt but run at the current working directry.
// Output runs the command and returns the text from stdout.
func Output(cmd string, args ...string) (string, error) {
return OutputAt("", cmd, args...)
}

// OutputAt runs the command and returns the text from stdout.
// OutputAt runs the command at a certain path and returns the text from stdout.
func OutputAt(pwd string, cmd string, args ...string) (string, error) {
buf := &bytes.Buffer{}
_, err := Exec(nil, buf, os.Stderr, pwd, cmd, args...)
_, err := ExecAt(nil, buf, os.Stderr, pwd, cmd, args...)
return strings.TrimSuffix(buf.String(), "\n"), err
}

// OutputWith is like OutputAtWith but run at the current working directry.
// OutputWith is like RunWith, but returns what is written to stdout.
func OutputWith(env map[string]string, cmd string, args ...string) (string, error) {
return OutputAtWith(env, "", cmd, args...)
}

// OutputAtWith is like RunWith, but returns what is written to stdout.
func OutputAtWith(env map[string]string, pwd, cmd string, args ...string) (string, error) {
// OutputAtWith is like RunAtWith, but returns what is written to stdout.
func OutputAtWith(env map[string]string, pwd string, cmd string, args ...string) (string, error) {
buf := &bytes.Buffer{}
_, err := Exec(env, buf, os.Stderr, pwd, cmd, args...)
_, err := ExecAt(env, buf, os.Stderr, pwd, cmd, args...)
return strings.TrimSuffix(buf.String(), "\n"), err
}

// Exec executes the command, piping its stdout and stderr to the given
// Exec is like execAt but always runs in the current workdir.
func Exec(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, err error) {
return ExecAt(env, stdout, stderr, "", cmd, args...)
}

// ExecAt executes the command, piping its stdout and stderr to the given
// writers. If the command fails, it will return an error that, if returned
// from a target or mg.Deps call, will cause mage to exit with the same code as
// the command failed with. Env is a list of environment variables to set when
Expand All @@ -104,7 +172,7 @@ func OutputAtWith(env map[string]string, pwd, cmd string, args ...string) (strin
// Ran reports if the command ran (rather than was not found or not executable).
// Code reports the exit code the command returned if it ran. If err == nil, ran
// is always true and code is always 0.
func Exec(env map[string]string, stdout, stderr io.Writer, pwd string, cmd string, args ...string) (ran bool, err error) {
func ExecAt(env map[string]string, stdout, stderr io.Writer, pwd string, cmd string, args ...string) (ran bool, err error) {
expand := func(s string) string {
s2, ok := env[s]
if ok {
Expand Down Expand Up @@ -132,14 +200,11 @@ func run(env map[string]string, stdout, stderr io.Writer, pwd string, cmd string
for k, v := range env {
c.Env = append(c.Env, k+"="+v)
}
c.Dir = pwd
c.Stderr = stderr
c.Stdout = stdout
c.Stdin = os.Stdin

if pwd != "" {
c.Dir = pwd
}

var quoted []string
for i := range args {
quoted = append(quoted, fmt.Sprintf("%q", args[i]))
Expand Down
8 changes: 4 additions & 4 deletions internal/core/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func TestExitCode(t *testing.T) {
ran, err := Exec(nil, nil, nil, "", "sh", "-c", "exit 99")
ran, err := Exec(nil, nil, nil, "sh", "-c", "exit 99")
if err == nil {
t.Fatal("unexpected nil error from run")
}
Expand All @@ -24,7 +24,7 @@ func TestExitCode(t *testing.T) {
func TestSettingPwd(t *testing.T) {
pwd := "/"
out := &bytes.Buffer{}
ran, err := Exec(nil, out, nil, pwd, "pwd")
ran, err := ExecAt(nil, out, nil, pwd, "pwd")
if err != nil {
t.Fatalf("unexpected error from runner: %#v", err)
}
Expand All @@ -42,7 +42,7 @@ func TestSettingNoPwd(t *testing.T) {
t.Errorf("Failed getting current working directory")
}
out := &bytes.Buffer{}
ran, err := Exec(nil, out, nil, "", "pwd")
ran, err := ExecAt(nil, out, nil, "", "pwd")
if err != nil {
t.Fatalf("unexpected error from runner: %#v", err)
}
Expand All @@ -66,7 +66,7 @@ func TestSettingInvalidPwd(t *testing.T) {
func TestEnv(t *testing.T) {
env := "SOME_REALLY_LONG_MAGEFILE_SPECIFIC_THING"
out := &bytes.Buffer{}
ran, err := Exec(map[string]string{env: "foobar"}, out, nil, "", "echo", fmt.Sprintf("$%s", env))
ran, err := Exec(map[string]string{env: "foobar"}, out, nil, "echo", fmt.Sprintf("$%s", env))
if err != nil {
t.Fatalf("unexpected error from runner: %#v", err)
}
Expand Down
47 changes: 47 additions & 0 deletions internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,53 @@ func GetRepoRoot() (string, error) {
return cwd, nil
}

// ListRescursiveFiles recursively finds all files in the root directory that match the given pattern.
func ListRescursiveFiles(root, pattern string) ([]string, error) {
Comment thread
AtzeDeVries marked this conversation as resolved.
Outdated
var matches []string
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
// If an error occurs (e.g., permission denied on a directory),
// the function can decide how to handle it. Returning nil skips the error
// for this specific path and continues the traversal.
return nil
}

// Check if it's a file and if its name matches the pattern.
if !d.IsDir() {
// filepath.Match checks a filename against a glob pattern.
if matched, err := filepath.Match(pattern, d.Name()); matched {
if err != nil {
return err
}
// make relateive
relPath, err := filepath.Rel(root, path)
if err != nil {
return err
}
matches = append(matches, relPath)
}
}
return nil
})
if err != nil {
return nil, fmt.Errorf("error walking directory: %w", err)
}

return matches, nil
}
Comment thread
AtzeDeVries marked this conversation as resolved.
Outdated

// DirExists returns true if a dir exists
func DirExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if errors.Is(err, fs.ErrNotExist) {
return false
}
return false
}

// GetAbsWorkDir accepts a directory as string and joins it with the current workdir
// directory to return a absolute directory. If the supplied directory is
// already absolute it will just return the input.
Expand Down
Loading
Loading