Skip to content

Commit dfd731f

Browse files
authored
CI/CD with Workflow (#2)
* go mod tidy * add workflow v1 file for lint, test and release * attempt to fix golang-ci-lint with upgrade to 1.64 * lint fixes and make lint added * install shellspec from install.sh with version specifier * fix incorrect Makefile dependency * drop useless step
1 parent ca60678 commit dfd731f

File tree

10 files changed

+198
-34
lines changed

10 files changed

+198
-34
lines changed

.github/workflows/ci-cd.yml

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: CI/CD
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags:
7+
- "v*.*.*"
8+
pull_request:
9+
branches: [main]
10+
11+
permissions: read-all
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
lint:
19+
name: Lint
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Checkout code
23+
uses: actions/checkout@v4
24+
- name: Set up Go
25+
uses: actions/setup-go@v5
26+
with:
27+
go-version-file: "go.mod"
28+
- name: Run golangci-lint
29+
uses: golangci/golangci-lint-action@v6
30+
with:
31+
version: v1.64
32+
args: --timeout=5m
33+
34+
test:
35+
name: Test
36+
needs: lint
37+
runs-on: ubuntu-latest
38+
steps:
39+
- name: Checkout code
40+
uses: actions/checkout@v4
41+
with:
42+
fetch-depth: 0
43+
44+
- name: Set up Go
45+
uses: actions/setup-go@v5
46+
with:
47+
go-version-file: "go.mod"
48+
49+
- name: Install ShellSpec
50+
run: |
51+
SHELLSPEC_VERSION=0.28.1
52+
curl -fsSL https://git.io/shellspec | sh -s ${SHELLSPEC_VERSION} --yes
53+
shellspec --version
54+
55+
- name: Run Unit Tests
56+
run: make test-units
57+
58+
- name: Run Feature Tests
59+
run: make test-features
60+
61+
- name: Run Fuzz Tests
62+
run: make test-fuzz FUZZ_TIME=10s # Shorter duration for CI
63+
64+
create_release:
65+
name: Create GitHub Release
66+
if: startsWith(github.ref, 'refs/tags/v')
67+
needs: test
68+
runs-on: ubuntu-latest
69+
permissions:
70+
contents: write
71+
outputs:
72+
upload_url: ${{ steps.create_release.outputs.upload_url }}
73+
steps:
74+
- name: Checkout code
75+
uses: actions/checkout@v4
76+
77+
- name: Create Release
78+
id: create_release
79+
uses: actions/create-release@v1
80+
env:
81+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
82+
with:
83+
tag_name: ${{ github.ref_name }}
84+
release_name: Release ${{ github.ref_name }}
85+
body: |
86+
Automated release for ${{ github.ref_name }}.
87+
See commit history for changes.
88+
draft: false
89+
prerelease: false
90+
91+
build_and_upload_release_assets:
92+
name: Build and Upload Release Assets
93+
if: startsWith(github.ref, 'refs/tags/v')
94+
needs: create_release
95+
runs-on: ubuntu-latest
96+
permissions:
97+
contents: write
98+
strategy:
99+
matrix:
100+
goos: [linux, darwin]
101+
goarch: [amd64, arm64, 386, arm]
102+
exclude:
103+
- goos: darmin
104+
goarch: 386
105+
- goos: darmin
106+
goarch: arm
107+
steps:
108+
- name: Checkout code
109+
uses: actions/checkout@v4
110+
with:
111+
fetch-depth: 0
112+
113+
- name: Set up Go
114+
uses: actions/setup-go@v5
115+
with:
116+
go-version-file: "go.mod"
117+
118+
- name: Build for ${{ matrix.goos }}/${{ matrix.goarch }}
119+
run: make build GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }}
120+
env:
121+
GOOS: ${{ matrix.goos }}
122+
GOARCH: ${{ matrix.goarch }}
123+
124+
- name: Prepare Asset Names
125+
id: prep_asset
126+
shell: bash
127+
run: |
128+
ARTIFACT_BASENAME="cmdjail-${{ matrix.goos }}-${{ matrix.goarch }}"
129+
ASSET_FILENAME="${ARTIFACT_BASENAME}"
130+
# Move the generic build output to the expected asset name
131+
mv build/cmdjail-${{ matrix.goos }}-${{ matrix.goarch}} build/${ASSET_FILENAME}
132+
echo "asset_path=build/${ASSET_FILENAME}" >> "$GITHUB_OUTPUT"
133+
echo "asset_name=${ASSET_FILENAME}" >> "$GITHUB_OUTPUT"
134+
135+
- name: Upload Release Asset for ${{ matrix.goos }}/${{ matrix.goarch }}
136+
uses: actions/upload-release-asset@v1
137+
with:
138+
upload_url: ${{ needs.create_release.outputs.upload_url }}
139+
asset_path: ${{ steps.prep_asset.outputs.asset_path }}
140+
asset_name: ${{ steps.prep_asset.outputs.asset_name }}
141+
asset_content_type: application/octet-stream

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
###############################################################################
77

88
GO ?= go
9+
GOLANGCI_LINT ?= golangci-lint
910
GOOS ?= $(shell go env GOOS)
1011
GOARCH ?= $(shell go env GOARCH)
1112
FUZZ_TIME ?= 30s
@@ -41,7 +42,7 @@ help:
4142
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m %-43s\033[0m %s\n", $$1, $$2}' \
4243
| sed -e 's/\[32m #-- /[33m/'
4344

44-
bin/cmdjail: bin *.go
45+
bin/cmdjail: *.go
4546
@mkdir -p bin
4647
@$(GO) build $(LDFLAGS) $(BUILD_VERBOSE_CONTROL) $(BUILD_CACHE_CONTROL) -o bin/cmdjail .
4748

@@ -53,6 +54,11 @@ build: ## Build binary for a specific platform (e.g., GOOS=linux GOARCH=arm64 ma
5354
@echo "Building for $(GOOS)/$(GOARCH)..."
5455
@GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 $(GO) build $(LDFLAGS) $(BUILD_VERBOSE_CONTROL) $(BUILD_CACHE_CONTROL) -o build/cmdjail-$(GOOS)-$(GOARCH) .
5556

57+
#-- Linting
58+
.PHONY: lint
59+
lint: ## Run golangci-lint
60+
@$(GOLANGCI_LINT) run $(LINT_VERBOSE_CONTROL) ./...
61+
5662
#-- Testing
5763
.PHONY: test test-units test-features
5864
test: test-units test-features ## test entire application

config.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ The intent command can also be set directly via the CMDJAIL_CMD environment vari
135135
}
136136
}
137137

138-
func parseFlags(envvars envVars) []string {
138+
func parseFlags(envvars envVars) ([]string, error) {
139139
pflag.BoolVar(&flagVersion, "version", false, flagVersionDesc)
140140
pflag.BoolVarP(&flagVerbose, "verbose", "v", envvars.Verbose, flagVerboseDesc)
141141
pflag.StringVarP(&flagLogFile, "log-file", "l", envvars.LogFile, flagLogFileDesc)
@@ -147,9 +147,11 @@ func parseFlags(envvars envVars) []string {
147147
pflag.StringVarP(&flagShellCmd, "shell-cmd", "s", envvars.ShellCmd, flagShellCmdDesc)
148148

149149
args, cmdOptions := splitAtEndOfArgs(os.Args)
150-
pflag.CommandLine.Parse(args)
150+
if err := pflag.CommandLine.Parse(args); err != nil {
151+
return nil, err
152+
}
151153

152-
return cmdOptions
154+
return cmdOptions, nil
153155
}
154156

155157
func parseEnvVars() (envVars, error) {
@@ -231,7 +233,10 @@ func parseEnvAndFlags() (Config, error) {
231233
return NoConfig, err
232234
}
233235

234-
cmdOptions := parseFlags(envvars)
236+
cmdOptions, err := parseFlags(envvars)
237+
if err != nil {
238+
return NoConfig, err
239+
}
235240

236241
if flagJailFile == "-" && flagCheckIntentCmds == "-" {
237242
return NoConfig, ErrJailFileAndCheckCmdsFromStdin

config_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ func TestParseEnvAndFlags(t *testing.T) {
2424
t.Run("Returns config from environment variables", func(t *testing.T) {
2525
setup() // Reset flags and args
2626
cmd := "cmd -s --long-flag -a=123 -a 123"
27-
os.Setenv(EnvPrefix+"_CMD", cmd)
27+
assert.NoError(t, os.Setenv(EnvPrefix+"_CMD", cmd))
2828
log := "/tmp/cmdjail.log"
29-
os.Setenv(EnvPrefix+"_LOGFILE", log)
29+
assert.NoError(t, os.Setenv(EnvPrefix+"_LOGFILE", log))
3030
jailfile := "/tmp/.cmd.jail"
31-
os.Setenv(EnvPrefix+"_JAILFILE", jailfile)
32-
os.Setenv(EnvPrefix+"_VERBOSE", "true")
31+
assert.NoError(t, os.Setenv(EnvPrefix+"_JAILFILE", jailfile))
32+
assert.NoError(t, os.Setenv(EnvPrefix+"_VERBOSE", "true"))
3333
shellCmd := "sh -c"
34-
os.Setenv(EnvPrefix+"_SHELL_CMD", shellCmd)
34+
assert.NoError(t, os.Setenv(EnvPrefix+"_SHELL_CMD", shellCmd))
3535

3636
c, err := parseEnvAndFlags()
3737
assert.NoError(t, err)
@@ -46,8 +46,8 @@ func TestParseEnvAndFlags(t *testing.T) {
4646
t.Run("Returns config with command set from EnvReference", func(t *testing.T) {
4747
setup() // Reset flags and args
4848
cmd := "cmd -s --long-flag -a=123 -a 123"
49-
os.Setenv("CMD", cmd)
50-
os.Setenv(EnvPrefix+"_ENV_REFERENCE", "CMD")
49+
assert.NoError(t, os.Setenv("CMD", cmd))
50+
assert.NoError(t, os.Setenv(EnvPrefix+"_ENV_REFERENCE", "CMD"))
5151

5252
c, err := parseEnvAndFlags()
5353
assert.NoError(t, err)
@@ -57,8 +57,8 @@ func TestParseEnvAndFlags(t *testing.T) {
5757

5858
t.Run("Flag overrides environment variable", func(t *testing.T) {
5959
setup("--jail-file", "/flag/path/.cmd.jail", "--shell-cmd", "zsh -c")
60-
os.Setenv(EnvPrefix+"_JAILFILE", "/env/path/.cmd.jail")
61-
os.Setenv(EnvPrefix+"_SHELL_CMD", "bash -c")
60+
assert.NoError(t, os.Setenv(EnvPrefix+"_JAILFILE", "/env/path/.cmd.jail"))
61+
assert.NoError(t, os.Setenv(EnvPrefix+"_SHELL_CMD", "bash -c"))
6262

6363
c, err := parseEnvAndFlags()
6464
assert.NoError(t, err)

go.mod

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ module github.com/endiangroup/cmdjail
22

33
go 1.24.3
44

5+
require (
6+
github.com/kelseyhightower/envconfig v1.4.0
7+
github.com/spf13/pflag v1.0.6
8+
github.com/stretchr/testify v1.10.0
9+
)
10+
511
require (
612
github.com/davecgh/go-spew v1.1.1 // indirect
7-
github.com/kelseyhightower/envconfig v1.4.0 // indirect
813
github.com/pmezard/go-difflib v1.0.0 // indirect
9-
github.com/spf13/pflag v1.0.6 // indirect
10-
github.com/stretchr/testify v1.10.0 // indirect
1114
gopkg.in/yaml.v3 v3.0.1 // indirect
1215
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
88
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
99
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1010
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1112
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1213
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1314
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

jailfile_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,12 @@ func TestCmdMatcher_Matches(t *testing.T) {
201201
t.Run("Error on non-executable script", func(t *testing.T) {
202202
tmpfile, err := os.CreateTemp("", "test-script")
203203
assert.NoError(t, err)
204-
defer os.Remove(tmpfile.Name())
204+
defer func() { assert.NoError(t, os.Remove(tmpfile.Name())) }()
205205

206-
tmpfile.WriteString("#!/bin/sh\nexit 0")
207-
tmpfile.Close()
208-
os.Chmod(tmpfile.Name(), 0o644)
206+
_, err = tmpfile.WriteString("#!/bin/sh\nexit 0")
207+
assert.NoError(t, err)
208+
assert.NoError(t, tmpfile.Close())
209+
assert.NoError(t, os.Chmod(tmpfile.Name(), 0o644))
209210

210211
matcher := NewCmdMatcher(m, tmpfile.Name(), shellCmd)
211212
matches, err := matcher.Matches("any command")

main.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ func loadCommandsForCheckMode(conf Config) (commands []string, source string, er
198198
printLogErr(os.Stderr, "reading test file %s: %v", conf.CheckIntentCmdsFile, fileErr)
199199
return nil, source, fileErr
200200
}
201-
defer file.Close()
201+
defer func() {
202+
if err := file.Close(); err != nil {
203+
printLogErr(os.Stderr, "closing test commands file %s: %v", conf.CheckIntentCmdsFile, err)
204+
}
205+
}()
202206
r = file
203207
}
204208
commands, err = readLines(r)
@@ -276,7 +280,11 @@ func getJailFile(conf Config) JailFile {
276280
}
277281
// If the reader is a file, ensure it's closed.
278282
if closer, ok := jailFileReader.(io.Closer); ok && jailFileReader != os.Stdin {
279-
defer closer.Close()
283+
defer func() {
284+
if err := closer.Close(); err != nil {
285+
printLogErr(os.Stderr, "closing jailfile reader for %s: %v", conf.JailFile, err)
286+
}
287+
}()
280288
}
281289

282290
jailFile, err := parseJailFile(conf, jailFileReader)
@@ -320,7 +328,11 @@ func appendRuleToFile(filepath, intentCmd string) error {
320328
if err != nil {
321329
return err
322330
}
323-
defer f.Close()
331+
defer func() {
332+
if err := f.Close(); err != nil {
333+
printLogErr(os.Stderr, "closing record file %s: %v", filepath, err)
334+
}
335+
}()
324336

325337
rule := fmt.Sprintf("+ '%s\n", intentCmd)
326338
if _, err := f.WriteString(rule); err != nil {

matchers.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,10 @@ func (c CmdMatcher) Matches(intentCmd string) (bool, error) {
120120
if err = cmd.Start(); err != nil {
121121
return false, err
122122
}
123-
w.Write([]byte(intentCmd))
123+
if _, err = w.Write([]byte(intentCmd)); err != nil {
124+
_ = w.Close() // Best effort close
125+
return false, err
126+
}
124127
if err = w.Close(); err != nil {
125128
return false, err
126129
}

print-log.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func setLoggerToFile(path string) error {
3131
}
3232

3333
func printMsg(printTo io.Writer, msg string, args ...any) {
34-
fmt.Fprintf(printTo, msg+"\n", args...)
34+
_, _ = fmt.Fprintf(printTo, msg+"\n", args...)
3535
}
3636

3737
func logMsg(msg string, args ...any) {
@@ -43,14 +43,6 @@ func printLog(printTo io.Writer, msg string, args ...any) {
4343
logMsg(msg+"\n", args...)
4444
}
4545

46-
func printErr(printTo io.Writer, msg string, args ...any) {
47-
printMsg(printTo, "[error] "+msg, args...)
48-
}
49-
50-
func logErr(msg string, args ...any) {
51-
logMsg("[error] "+msg, args...)
52-
}
53-
5446
func logWarn(msg string, args ...any) {
5547
logMsg("[warn] "+msg, args...)
5648
}

0 commit comments

Comments
 (0)