Skip to content

Commit 5cf0d86

Browse files
committed
chore: implement self update mechanism & update README
1 parent ca81498 commit 5cf0d86

File tree

7 files changed

+243
-17
lines changed

7 files changed

+243
-17
lines changed

.github/workflows/go.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
go-version: '1.24'
2323

2424
- name: Build
25-
run: go build -o kpeek .
25+
run: go build -ldflags "-X github.com/hacktivist123/kpeek/cmd.version=${{ env.VERSION }}" -o kpeek .
2626

2727
- name: Upload Archives
2828
uses: actions/upload-artifact@v4

.github/workflows/release.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,19 @@ jobs:
5050
username: ${{ secrets.DOCKERHUB_USERNAME }}
5151
password: ${{ secrets.DOCKERHUB_PASSWORD }}
5252

53+
- name: Build kpeek with version
54+
run: |
55+
echo "Building kpeek with version: ${{ steps.bump_version.outputs.new_tag }}"
56+
go build -ldflags "-X github.com/hacktivist123/kpeek/cmd.version=$${{ steps.bump_version.outputs.new_tag }}" -o kpeek .
57+
5358
- name: Build and push Docker image
5459
uses: docker/build-push-action@v5
5560
with:
5661
push: true
5762
platforms: linux/amd64,linux/arm64
58-
tags: coderblvck/kpeek:latest
63+
tags: |
64+
coderblvck/kpeek:latest
65+
coderblvck/kpeek:${{ steps.bump_version.outputs.new_tag }}
5966
cache-from: type=registry,ref=coderblvck/kpeek:buildcache
6067
cache-to: type=registry,ref=coderblvck/kpeek:buildcache,mode=max
6168
secrets: |

Dockerfile

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
# Dockerfile
2-
FROM golang:1.24-alpine AS builder
3-
WORKDIR /app
4-
COPY go.mod go.sum ./
5-
RUN go mod download
6-
COPY . .
7-
RUN go build -o kpeek .
8-
9-
FROM alpine:latest
10-
WORKDIR /app
11-
COPY --from=builder /app/kpeek /usr/local/bin/kpeek
2+
FROM alpine:3.18
3+
COPY kpeek /usr/local/bin/kpeek
124
ENTRYPOINT ["kpeek"]

README.md

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,32 @@
1616

1717
## Installation
1818

19-
- Go Install
19+
### Using Go Install
2020

2121
```bash
2222
go install github.com/hacktivist123/kpeek@latest
2323
```
2424

25-
> [!NOTE]
26-
> You might need to add `$HOME/go/bin` to Your `$PATH` after installation
25+
> [!INFO]
26+
> - The above command does not embed an official release version in the binary. If you run `kpeek update` and your local version is unrecognized, kpeek may always see itself as outdated. You can still successfully update to the latest release, but your local binary won’t display an exact version string.
27+
> - You might need to add `$HOME/go/bin` to Your `$PATH` after installation
2728
28-
- Build From Source
29+
## Pre-Compiled Binaries
30+
31+
For an officially versioned kpeek build, download a pre-built release binary. Place it in your PATH, and you’ll have the correct version string for self-updates.
32+
33+
### Build From Source (with Version Injection)
2934

3035
```bash
3136
git clone https://github.com/hacktivist123/kpeek.git
3237
cd kpeek
33-
go build -o kpeek .
38+
go build -ldflags "-X github.com/hacktivist123/kpeek/cmd.version=v0.7.0" -o kpeek .
3439
./kpeek --help
3540
```
3641

42+
>[!INFO]
43+
> Replace `v0.7.0` with the appropriate release version. This approach embeds the release version in the binary, which ensures that commands like kpeek update correctly compare the local version with the latest release.
44+
3745
- Run With `go run`
3846

3947
```bash
@@ -59,6 +67,72 @@ kpeek [resource/type-name] [flags]
5967
| `--log-tail int` | Display only the last N lines of logs (`0` for all) |
6068
| `--log-regex` | Regular expression to filter log lines |
6169

70+
## How to Update
71+
72+
kpeek includes a built-in command that checks GitHub for a newer release and updates the local binary if one is found. To use it, simply run:
73+
74+
```bash
75+
kpeek update
76+
```
77+
78+
If a newer version is available, kpeek will automatically download the latest release and replace your current binary. This makes staying up-to-date easy and requires no manual download or reinstallation.
79+
80+
## Running kpeek as a kubectl Plugin
81+
82+
kpeek is designed to work seamlessly as a standalone CLI tool—but you can also run it as a kubectl plugin for a more native Kubernetes experience. Follow the steps below to set up and use kpeek as a kubectl plugin.
83+
84+
### Installation Steps
85+
86+
1. **Build or Download kpeek**
87+
88+
- **Build from Source:**
89+
Run the following command in your kpeek project directory:
90+
91+
```bash
92+
go build -o kpeek .
93+
```
94+
95+
- **Download Pre-Built Binary:**
96+
Alternatively, download the latest release binary from our [GitHub Releases](https://github.com/hacktivist123/kpeek/releases).
97+
98+
2. **Rename the Binary**
99+
Kubectl recognizes plugins by the `kubectl-` prefix. Rename the kpeek binary:
100+
101+
```bash
102+
mv kpeek kubectl-kpeek
103+
```
104+
105+
3. **Place the Plugin in Your PATH**
106+
107+
Ensure that the directory containing kubectl-kpeek is in your system’s PATH. For example, you can move it to /usr/local/bin:
108+
109+
```bash
110+
sudo mv kubectl-kpeek /usr/local/bin/
111+
```
112+
113+
If you prefer a custom directory, add that directory to your PATH in your shell configuration file (e.g., .bashrc or .zshrc).
114+
115+
4. **Verify Installation**
116+
117+
Run the following command to ensure kubectl discovers the plugin:
118+
119+
```bash
120+
kubectl kpeek --help
121+
```
122+
123+
You should see the kpeek help output, which confirms that the plugin is correctly installed.
124+
125+
### Additional Tips
126+
127+
- **Pre-Built Binaries**:
128+
If you download a pre-built binary from GitHub releases, remember to rename it to kubectl-kpeek before moving it to a directory in your $PATH.
129+
130+
- **Plugin Discovery**:
131+
Kubectl automatically discovers any executable named with the kubectl- prefix. No further configuration is necessary, but you can optionally create a plugin configuration file at $HOME/.kube/plugins.yaml if you want to document your installed plugins.
132+
133+
- **Flag Compatibility**:
134+
All the flags available in the standalone kpeek (e.g., --no-logs, --include-events, --log-tail, --log-regex) work exactly the same when you run kpeek as a kubectl plugin.
135+
62136
## Contributing
63137

64138
- Fork this repository and clone your fork.

cmd/update.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net"
7+
"os"
8+
"strings"
9+
"time"
10+
11+
"github.com/blang/semver"
12+
"github.com/rhysd/go-github-selfupdate/selfupdate"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
var version = "v0.7.0"
17+
18+
var updateCmd = &cobra.Command{
19+
Use: "update",
20+
Short: "Update kpeek to the latest version",
21+
Long: `Check GitHub for a newer release of kpeek and update the binary automatically.
22+
23+
This command fetches the latest release from GitHub (repository "hacktivist123/kpeek") and
24+
replaces the current binary if a newer version is available. It will retry a few times if
25+
the network is unreachable.`,
26+
Run: func(cmd *cobra.Command, args []string) {
27+
fmt.Println("Checking for updates...")
28+
29+
rawVersion := strings.TrimPrefix(version, "v")
30+
vLocal, err := semver.ParseTolerant(rawVersion)
31+
if err != nil {
32+
fmt.Printf("Could not parse local version (%s): %v\n", version, err)
33+
fmt.Println("Falling back to version 0.0.0 for update checks.")
34+
vLocal = semver.MustParse("0.0.0")
35+
}
36+
37+
const maxRetries = 3
38+
var success bool
39+
var updateErr error
40+
41+
for attempt := 1; attempt <= maxRetries; attempt++ {
42+
updateErr = attemptUpdate(vLocal)
43+
if updateErr == nil {
44+
success = true
45+
break
46+
}
47+
48+
if isOfflineOrNetworkError(updateErr) {
49+
fmt.Printf("Network error (attempt %d/%d): %v\n", attempt, maxRetries, updateErr)
50+
if attempt < maxRetries {
51+
fmt.Println("Retrying in 2s...")
52+
time.Sleep(2 * time.Second)
53+
}
54+
} else {
55+
break
56+
}
57+
}
58+
59+
if !success {
60+
fmt.Printf("Update failed after %d attempt(s): %v\n", maxRetries, updateErr)
61+
os.Exit(1)
62+
}
63+
},
64+
}
65+
66+
func init() {
67+
rootCmd.AddCommand(updateCmd)
68+
}
69+
70+
func attemptUpdate(current semver.Version) error {
71+
updater, err := selfupdate.NewUpdater(selfupdate.Config{})
72+
if err != nil {
73+
return fmt.Errorf("error creating updater: %w", err)
74+
}
75+
76+
res, err := updater.UpdateSelf(current, "hacktivist123/kpeek")
77+
if err != nil {
78+
return err
79+
}
80+
81+
if res.Version.Equals(current) {
82+
fmt.Println("You are already using the latest version!")
83+
} else {
84+
fmt.Printf("Successfully updated to version %s\n", res.Version)
85+
}
86+
return nil
87+
}
88+
89+
// isOfflineOrNetworkError checks if the error is likely a network issue, prompting a retry.
90+
func isOfflineOrNetworkError(err error) bool {
91+
var netErr net.Error
92+
if errors.As(err, &netErr) {
93+
return true
94+
}
95+
96+
lowerMsg := strings.ToLower(err.Error())
97+
if strings.Contains(lowerMsg, "connection refused") ||
98+
strings.Contains(lowerMsg, "dial tcp") {
99+
return true
100+
}
101+
102+
return false
103+
}

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
)
1313

1414
require (
15+
github.com/blang/semver v3.5.1+incompatible // indirect
1516
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1617
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
1718
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
@@ -23,8 +24,11 @@ require (
2324
github.com/golang/protobuf v1.5.4 // indirect
2425
github.com/google/gnostic-models v0.6.9 // indirect
2526
github.com/google/go-cmp v0.6.0 // indirect
27+
github.com/google/go-github/v30 v30.1.0 // indirect
28+
github.com/google/go-querystring v1.0.0 // indirect
2629
github.com/google/gofuzz v1.2.0 // indirect
2730
github.com/google/uuid v1.6.0 // indirect
31+
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
2832
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2933
github.com/josharian/intern v1.0.0 // indirect
3034
github.com/json-iterator/go v1.1.12 // indirect
@@ -36,9 +40,13 @@ require (
3640
github.com/modern-go/reflect2 v1.0.2 // indirect
3741
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
3842
github.com/pkg/errors v0.9.1 // indirect
43+
github.com/rhysd/go-github-selfupdate v1.2.3 // indirect
3944
github.com/rivo/uniseg v0.4.7 // indirect
4045
github.com/spf13/pflag v1.0.5 // indirect
46+
github.com/tcnksm/go-gitconfig v0.1.2 // indirect
47+
github.com/ulikunitz/xz v0.5.9 // indirect
4148
github.com/x448/float16 v0.8.4 // indirect
49+
golang.org/x/crypto v0.31.0 // indirect
4250
golang.org/x/net v0.33.0 // indirect
4351
golang.org/x/oauth2 v0.24.0 // indirect
4452
golang.org/x/sys v0.28.0 // indirect

0 commit comments

Comments
 (0)