Skip to content

Commit 9034899

Browse files
committed
Add jsongrep and more flexible docker upgrade command
* Adds jsongrep tool - odd that the author ommitted any Arm support, given Darwin arm64 is the default target for his core audience. * "arkade docker upgrade" now works without arguments or with a positional argument, to make the syntax easier. Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
1 parent ed0f2f4 commit 9034899

8 files changed

Lines changed: 231 additions & 17 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,7 @@ There are 53 apps that you can install on your cluster.
849849
| [inlets-pro](https://github.com/inlets/inlets-pro) | Cloud Native Tunnel for HTTP and TCP traffic. |
850850
| [inletsctl](https://github.com/inlets/inletsctl) | Automates the task of creating an exit-server (tunnel server) on public cloud infrastructure. |
851851
| [istioctl](https://github.com/istio/istio) | Service Mesh to establish a programmable, application-aware network using the Envoy service proxy. |
852+
| [jg](https://github.com/micahkepe/jsongrep) | A CLI tool for filtering JSON data with a jq-like syntax. |
852853
| [jq](https://github.com/jqlang/jq) | jq is a lightweight and flexible command-line JSON processor |
853854
| [just](https://github.com/casey/just) | Just a command runner |
854855
| [k0s](https://github.com/k0sproject/k0s) | Zero Friction Kubernetes |
@@ -962,7 +963,7 @@ There are 53 apps that you can install on your cluster.
962963
| [websocat](https://github.com/vi/websocat) | Command-line client for WebSockets, like netcat/socat but for WebSockets |
963964
| [yq](https://github.com/mikefarah/yq) | Portable command-line YAML processor. |
964965
| [yt-dlp](https://github.com/yt-dlp/yt-dlp) | Fork of youtube-dl with additional features and fixes |
965-
There are 194 tools, use `arkade get NAME` to download one.
966+
There are 195 tools, use `arkade get NAME` to download one.
966967
<!-- end of tool list -->
967968

968969

cmd/docker/gen.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
func MakeGen() *cobra.Command {
1414
var command = &cobra.Command{
15-
Use: "gen",
15+
Use: "gen [path]",
1616
Short: "Generate an arkade.yaml from detected images in a Dockerfile",
1717
Long: `Generate an arkade.yaml from detected images in a Dockerfile.
1818
@@ -26,23 +26,36 @@ substitution (e.g., ${VERSION}) are skipped.
2626
Example: ` # Generate arkade.yaml from current directory's Dockerfile
2727
arkade docker gen
2828
29+
# Generate from a positional argument (Dockerfile or directory)
30+
arkade docker gen ./template/python27-flask
31+
2932
# Generate from a specific Dockerfile
30-
arkade docker gen -f ./Dockerfile.prod
33+
arkade docker gen ./Dockerfile.prod
3134
3235
# Output to stdout only (don't create file)
3336
arkade docker gen --stdout
34-
arkade docker gen -f Dockerfile | tee arkade.yaml
37+
arkade docker gen Dockerfile | tee arkade.yaml
3538
`,
3639
SilenceUsage: true,
40+
Args: cobra.MaximumNArgs(1),
3741
}
3842

39-
command.Flags().StringP("file", "f", "Dockerfile", "Path to Dockerfile")
4043
command.Flags().BoolP("stdout", "s", false, "Output to stdout instead of creating file")
4144

4245
command.RunE = func(cmd *cobra.Command, args []string) error {
43-
file, _ := cmd.Flags().GetString("file")
46+
file := "Dockerfile"
4447
toStdout, _ := cmd.Flags().GetBool("stdout")
4548

49+
if len(args) == 1 {
50+
file = args[0]
51+
}
52+
53+
resolved, err := resolveDockerfilePath(file)
54+
if err != nil {
55+
return err
56+
}
57+
file = resolved
58+
4659
content, err := os.ReadFile(file)
4760
if err != nil {
4861
return fmt.Errorf("failed to read %s: %w", file, err)

cmd/docker/gen_test.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ FROM ghcr.io/openfaas/of-watchdog:0.25.0`
2424
}
2525

2626
cmd := MakeGen()
27-
cmd.SetArgs([]string{"-f", dockerfilePath, "--stdout"})
27+
cmd.SetArgs([]string{dockerfilePath, "--stdout"})
2828

2929
var stdout, stderr strings.Builder
3030
cmd.SetOut(&stdout)
@@ -66,7 +66,7 @@ FROM alpine:3.19`
6666
}
6767

6868
cmd := MakeGen()
69-
cmd.SetArgs([]string{"-f", dockerfilePath})
69+
cmd.SetArgs([]string{dockerfilePath})
7070

7171
var stdout, stderr strings.Builder
7272
cmd.SetOut(&stdout)
@@ -101,6 +101,38 @@ FROM alpine:3.19`
101101
}
102102
}
103103

104+
func TestGenCommand_PositionalDirImpliesDockerfile(t *testing.T) {
105+
tmpDir, err := os.MkdirTemp("", "arkade-docker-gen-test-*")
106+
if err != nil {
107+
t.Fatalf("failed to create temp dir: %v", err)
108+
}
109+
defer os.RemoveAll(tmpDir)
110+
111+
dockerfilePath := filepath.Join(tmpDir, "Dockerfile")
112+
dockerfileContent := `FROM alpine:3.19
113+
FROM golang:1.24`
114+
115+
if err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0644); err != nil {
116+
t.Fatalf("failed to write dockerfile: %v", err)
117+
}
118+
119+
cmd := MakeGen()
120+
cmd.SetArgs([]string{tmpDir, "--stdout"})
121+
122+
var stdout, stderr strings.Builder
123+
cmd.SetOut(&stdout)
124+
cmd.SetErr(&stderr)
125+
126+
if err := cmd.Execute(); err != nil {
127+
t.Fatalf("command failed: %v", err)
128+
}
129+
130+
result := stdout.String()
131+
if !strings.Contains(result, "- alpine") || !strings.Contains(result, "- golang") {
132+
t.Errorf("expected alpine and golang in output, stdout=%q, stderr=%q", result, stderr.String())
133+
}
134+
}
135+
104136
func TestGenCommand_NoImagesFound(t *testing.T) {
105137
tmpDir, err := os.MkdirTemp("", "arkade-docker-gen-test-*")
106138
if err != nil {
@@ -117,7 +149,7 @@ RUN echo "hello"`
117149
}
118150

119151
cmd := MakeGen()
120-
cmd.SetArgs([]string{"-f", dockerfilePath})
152+
cmd.SetArgs([]string{dockerfilePath})
121153

122154
var output strings.Builder
123155
cmd.SetOut(&output)
@@ -149,7 +181,7 @@ FROM alpine:3.19`
149181
}
150182

151183
cmd := MakeGen()
152-
cmd.SetArgs([]string{"-f", dockerfilePath, "--stdout"})
184+
cmd.SetArgs([]string{dockerfilePath, "--stdout"})
153185

154186
var stdout, stderr strings.Builder
155187
cmd.SetOut(&stdout)
@@ -185,7 +217,7 @@ func TestGenCommand_RegistryWithPort(t *testing.T) {
185217
}
186218

187219
cmd := MakeGen()
188-
cmd.SetArgs([]string{"-f", dockerfilePath, "--stdout"})
220+
cmd.SetArgs([]string{dockerfilePath, "--stdout"})
189221

190222
var stdout, stderr strings.Builder
191223
cmd.SetOut(&stdout)
@@ -219,7 +251,7 @@ FROM golang:1.24`
219251
}
220252

221253
cmd := MakeGen()
222-
cmd.SetArgs([]string{"-f", dockerfilePath, "--stdout"})
254+
cmd.SetArgs([]string{dockerfilePath, "--stdout"})
223255

224256
var stdout, stderr strings.Builder
225257
cmd.SetOut(&stdout)

cmd/docker/paths.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package docker
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
)
7+
8+
// resolveDockerfilePath accepts either:
9+
// - a directory path, in which case it implies a "Dockerfile" within it
10+
// - a file path, returned as-is
11+
//
12+
// If the input does not exist, it is returned as-is and the caller's subsequent
13+
// file operations will surface the error.
14+
func resolveDockerfilePath(input string) (string, error) {
15+
st, err := os.Stat(input)
16+
if err != nil {
17+
return input, nil
18+
}
19+
20+
if st.IsDir() {
21+
return filepath.Join(input, "Dockerfile"), nil
22+
}
23+
24+
return input, nil
25+
}

cmd/docker/paths_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package docker
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func Test_resolveDockerfilePath_FileIsUnchanged(t *testing.T) {
10+
tmpDir, err := os.MkdirTemp("", "arkade-docker-paths-test-*")
11+
if err != nil {
12+
t.Fatalf("failed to create temp dir: %v", err)
13+
}
14+
defer os.RemoveAll(tmpDir)
15+
16+
p := filepath.Join(tmpDir, "Dockerfile.custom")
17+
if err := os.WriteFile(p, []byte("FROM alpine:3.19\n"), 0644); err != nil {
18+
t.Fatalf("failed to write file: %v", err)
19+
}
20+
21+
got, err := resolveDockerfilePath(p)
22+
if err != nil {
23+
t.Fatalf("resolveDockerfilePath returned error: %v", err)
24+
}
25+
if got != p {
26+
t.Fatalf("want %q, got %q", p, got)
27+
}
28+
}
29+
30+
func Test_resolveDockerfilePath_DirImpliesDockerfile(t *testing.T) {
31+
tmpDir, err := os.MkdirTemp("", "arkade-docker-paths-test-*")
32+
if err != nil {
33+
t.Fatalf("failed to create temp dir: %v", err)
34+
}
35+
defer os.RemoveAll(tmpDir)
36+
37+
got, err := resolveDockerfilePath(tmpDir)
38+
if err != nil {
39+
t.Fatalf("resolveDockerfilePath returned error: %v", err)
40+
}
41+
42+
want := filepath.Join(tmpDir, "Dockerfile")
43+
if got != want {
44+
t.Fatalf("want %q, got %q", want, got)
45+
}
46+
}
47+
48+
func Test_resolveDockerfilePath_MissingPathIsReturnedAsIs(t *testing.T) {
49+
tmpDir, err := os.MkdirTemp("", "arkade-docker-paths-test-*")
50+
if err != nil {
51+
t.Fatalf("failed to create temp dir: %v", err)
52+
}
53+
defer os.RemoveAll(tmpDir)
54+
55+
missing := filepath.Join(tmpDir, "does-not-exist")
56+
got, err := resolveDockerfilePath(missing)
57+
if err != nil {
58+
t.Fatalf("resolveDockerfilePath returned error: %v", err)
59+
}
60+
if got != missing {
61+
t.Fatalf("want %q, got %q", missing, got)
62+
}
63+
}
64+

cmd/docker/upgrade.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
func MakeUpgrade() *cobra.Command {
1616
var command = &cobra.Command{
17-
Use: "upgrade",
17+
Use: "upgrade [path]",
1818
Short: "Upgrade images in a Dockerfile to the latest version",
1919
Aliases: []string{"u"},
2020
Long: `Upgrade container images in a Dockerfile to the latest version.
@@ -38,31 +38,44 @@ pin_major_minor:
3838
Example: ` # Upgrade specific images and write the changes
3939
arkade docker upgrade --image ghcr.io/openfaas/of-watchdog --write
4040
41+
# Use a positional argument (Dockerfile or directory)
42+
arkade docker upgrade ./template/python27-flask --image alpine
43+
4144
# Pin golang to its current major.minor version
4245
arkade docker upgrade \
4346
--image ghcr.io/openfaas/of-watchdog \
4447
--image golang \
4548
--pin-major-minor golang \
4649
--verbose
4750
48-
# Use a different Dockerfile
49-
arkade docker upgrade -f ./Dockerfile.template --image alpine`,
51+
# Use a different Dockerfile explicitly
52+
arkade docker upgrade ./Dockerfile.template --image alpine`,
5053
SilenceUsage: true,
54+
Args: cobra.MaximumNArgs(1),
5155
}
5256

53-
command.Flags().StringP("file", "f", "Dockerfile", "Path to Dockerfile")
5457
command.Flags().StringArrayP("image", "i", nil, "Image name to upgrade (specify multiple times)")
5558
command.Flags().StringArray("pin-major-minor", nil, "Pin image to current major.minor version, only upgrade patch (specify multiple times)")
5659
command.Flags().BoolP("verbose", "v", true, "Verbose output")
5760
command.Flags().BoolP("write", "w", true, "Write the updated values back to the file, or stdout when set to false")
5861

5962
command.RunE = func(cmd *cobra.Command, args []string) error {
60-
file, _ := cmd.Flags().GetString("file")
63+
file := "Dockerfile"
6164
imageNames, _ := cmd.Flags().GetStringArray("image")
6265
pinnedNames, _ := cmd.Flags().GetStringArray("pin-major-minor")
6366
verbose, _ := cmd.Flags().GetBool("verbose")
6467
writeFile, _ := cmd.Flags().GetBool("write")
6568

69+
if len(args) == 1 {
70+
file = args[0]
71+
}
72+
73+
resolved, err := resolveDockerfilePath(file)
74+
if err != nil {
75+
return err
76+
}
77+
file = resolved
78+
6679
basePath := path.Dir(file)
6780
defaultConfig := path.Join(basePath, "arkade.yaml")
6881
if _, err := os.Stat(defaultConfig); err == nil {

pkg/get/get_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10014,3 +10014,42 @@ func Test_DownloadCrush(t *testing.T) {
1001410014
})
1001510015
}
1001610016
}
10017+
10018+
func Test_DownloadJg(t *testing.T) {
10019+
tools := MakeTools()
10020+
name := "jg"
10021+
const version = "0.7.0"
10022+
10023+
tool := getTool(name, tools)
10024+
10025+
tests := []test{
10026+
{
10027+
os: "linux",
10028+
arch: arch64bit,
10029+
version: version,
10030+
url: `https://github.com/micahkepe/jsongrep/releases/download/0.7.0/jsongrep-0.7.0-x86_64-unknown-linux-musl.tar.gz`,
10031+
},
10032+
{
10033+
os: "darwin",
10034+
arch: arch64bit,
10035+
version: version,
10036+
url: `https://github.com/micahkepe/jsongrep/releases/download/0.7.0/jsongrep-0.7.0-x86_64-apple-darwin.tar.gz`,
10037+
},
10038+
{
10039+
os: "ming",
10040+
arch: arch64bit,
10041+
version: version,
10042+
url: `https://github.com/micahkepe/jsongrep/releases/download/0.7.0/jsongrep-0.7.0-x86_64-pc-windows-msvc.zip`,
10043+
},
10044+
}
10045+
10046+
for _, tc := range tests {
10047+
got, _, err := tool.GetURL(tc.os, tc.arch, tc.version, false)
10048+
if err != nil {
10049+
t.Fatal(err)
10050+
}
10051+
if got != tc.url {
10052+
t.Errorf("want: %s, got: %s", tc.url, got)
10053+
}
10054+
}
10055+
}

pkg/get/tools.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,33 @@ https://storage.googleapis.com/amp-public-assets-prod-0/cli/{{.Version}}/amp-{{$
199199
helmfile_{{.VersionNumber}}_{{$os}}_{{$arch}}.tar.gz`,
200200
})
201201

202+
tools = append(tools,
203+
Tool{
204+
Owner: "micahkepe",
205+
Repo: "jsongrep",
206+
Name: "jg",
207+
Description: "A CLI tool for filtering JSON data with a jq-like syntax.",
208+
BinaryTemplate: `jg`,
209+
URLTemplate: `
210+
{{$target := ""}}
211+
{{$ext := "tar.gz"}}
212+
{{- if eq .OS "linux" -}}
213+
{{- if eq .Arch "x86_64" -}}
214+
{{$target = "x86_64-unknown-linux-musl"}}
215+
{{- end -}}
216+
{{- else if eq .OS "darwin" -}}
217+
{{- if eq .Arch "x86_64" -}}
218+
{{$target = "x86_64-apple-darwin"}}
219+
{{- end -}}
220+
{{- else if HasPrefix .OS "ming" -}}
221+
{{$ext = "zip"}}
222+
{{- if eq .Arch "x86_64" -}}
223+
{{$target = "x86_64-pc-windows-msvc"}}
224+
{{- end -}}
225+
{{- end -}}
226+
https://github.com/micahkepe/jsongrep/releases/download/{{.Version}}/jsongrep-{{.VersionNumber}}-{{$target}}.{{$ext}}`,
227+
})
228+
202229
tools = append(tools,
203230
Tool{
204231
Owner: "jqlang",

0 commit comments

Comments
 (0)