Compare commits

...

6 Commits

Author SHA1 Message Date
Parship Chowdhury 7c240e5828
Add support for sonatype nancy vulnerability scanning (#153)
* added sonatype nancy vulnerability scanning

Signed-off-by: Parship Chowdhury <i.am.parship@gmail.com>

* fix 1

Signed-off-by: Parship Chowdhury <i.am.parship@gmail.com>

* fix 2

Signed-off-by: Parship Chowdhury <i.am.parship@gmail.com>

* vulnerability check fixed

Signed-off-by: Parship Chowdhury <i.am.parship@gmail.com>

---------

Signed-off-by: Parship Chowdhury <i.am.parship@gmail.com>
2025-07-22 21:26:30 +08:00
Gautam Manchandani da2b3fbac8
added unit test for util package (#138)
Signed-off-by: GautamBytes <manchandanigautam@gmail.com>
2025-07-21 13:00:51 +08:00
Gautam Manchandani ebb124d8cd
updated actions and enhanced build process (#137)
Signed-off-by: GautamBytes <manchandanigautam@gmail.com>
2025-07-08 09:51:29 +08:00
Gautam Manchandani 68d571ec42
add Go module & build cache to speed up CI (#135)
Signed-off-by: GautamBytes <manchandanigautam@gmail.com>
Co-authored-by: Jeremy <hantmac@outlook.com>
2025-07-06 22:41:44 +08:00
Gautam Manchandani d973c2aa1d
verify go.mod & go.sum consistency in CI (#136)
Signed-off-by: GautamBytes <manchandanigautam@gmail.com>
2025-07-06 18:22:15 +08:00
Gautam Manchandani 94742398cd
support UnitedDeployment in kubectl-kruise rollout restart (#128)
Signed-off-by: GautamBytes <manchandanigautam@gmail.com>
2025-06-23 17:01:26 +08:00
10 changed files with 444 additions and 76 deletions

View File

@ -12,7 +12,7 @@ on:
env:
# Common versions
GO_VERSION: '1.22'
GO_VERSION: '1.23'
GOLANGCI_VERSION: 'v1.55.2'
jobs:
@ -27,6 +27,14 @@ jobs:
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ env.GO_VERSION }}
- name: Verify go.mod is tidy
run: |
go mod tidy
if [ -n "$(git status --porcelain go.mod go.sum)" ]; then
echo "go.mod or go.sum is not tidy"
git diff go.mod go.sum
exit 1
fi
- name: Cache Go Dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
@ -47,13 +55,69 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: true
- name: Setup Go
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go modules and build cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: Verify go.mod is tidy
run: |
go mod tidy
if [ -n "$(git status --porcelain go.mod go.sum)" ]; then
echo "go.mod or go.sum is not tidy"
git diff go.mod go.sum
exit 1
fi
- name: Build
run: |
make build
- name: Test
run: |
make test
security-scan:
name: Security Vulnerability Scan
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: true
- name: Setup Go
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ env.GO_VERSION }}
- name: Cache Go Dependencies
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: Generate go.list file for Nancy
run: go list -json -deps ./... > go.list
- name: Run Nancy vulnerability scan
uses: sonatype-nexus-community/nancy-github-action@main
with:
nancyCommand: sleuth
goListFile: go.list
- name: Install and run govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

View File

@ -6,7 +6,6 @@ on:
- created
env:
# Common versions
GO_VERSION: '1.22'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -15,101 +14,137 @@ jobs:
runs-on: ubuntu-24.04
strategy:
matrix:
TARGETS: [ linux/amd64, darwin/amd64, windows/amd64, linux/arm64, darwin/arm64 ]
include:
- os: linux
arch: amd64
- os: linux
arch: arm64
- os: darwin
arch: amd64
- os: darwin
arch: arm64
- os: windows
arch: amd64
env:
GO_BUILD_ENV: GO111MODULE=on CGO_ENABLED=0
DIST_DIRS: find * -type d -exec
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # Needed for version.sh to work properly
- name: Setup Go
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ env.GO_VERSION }}
- name: Get release
id: get_release
uses: bruceadams/get-release@v1.2.2
- name: Get matrix
id: get_matrix
run: |
TARGETS=${{matrix.TARGETS}}
echo ::set-output name=OS::${TARGETS%/*}
echo ::set-output name=ARCH::${TARGETS#*/}
cache: true # Enable built-in Go caching
- name: Get ldflags
id: get_ldflags
run: |
LDFLAGS=$(./version.sh)
echo "LDFLAGS=${LDFLAGS}" >> $GITHUB_ENV
- name: Build
echo "LDFLAGS=${LDFLAGS}" >> $GITHUB_OUTPUT
- name: Build kubectl-kruise
run: |
${{ env.GO_BUILD_ENV }} GOOS=${{ steps.get_matrix.outputs.OS }} GOARCH=${{ steps.get_matrix.outputs.ARCH }} \
go build -ldflags "${{ env.LDFLAGS }}" \
-o _bin/kubectl-kruise/${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}/kubectl-kruise -v \
${{ env.GO_BUILD_ENV }} GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} \
go build -ldflags "${{ steps.get_ldflags.outputs.LDFLAGS }}" \
-o _bin/kubectl-kruise/${{ matrix.os }}-${{ matrix.arch }}/kubectl-kruise${{ matrix.os == 'windows' && '.exe' || '' }} \
./cmd/plugin/main.go
- name: Compress
run: |
cd _bin/kubectl-kruise && \
${{ env.DIST_DIRS }} cp ../../LICENSE {} \; && \
${{ env.DIST_DIRS }} cp ../../README.md {} \; && \
${{ env.DIST_DIRS }} tar -zcf kubectl-kruise-{}.tar.gz {} \; && \
cd .. && \
sha256sum kubectl-kruise/kubectl-kruise-* >> sha256-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.txt \
- name: Upload Kubectl-kruise tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./_bin/kubectl-kruise/kubectl-kruise-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz
asset_name: kubectl-kruise-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}-${{ steps.get_release.outputs.tag_name }}.tar.gz
asset_content_type: binary/octet-stream
- name: Build resourcedistribution-generator
run: |
${{ env.GO_BUILD_ENV }} GOOS=${{ steps.get_matrix.outputs.OS }} GOARCH=${{ steps.get_matrix.outputs.ARCH }} \
go build -ldflags "${{ env.LDFLAGS }}" \
-o _bin/resourcedistribution-generator/${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}/resourcedistributiongenerator -v \
${{ env.GO_BUILD_ENV }} GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} \
go build -ldflags "${{ steps.get_ldflags.outputs.LDFLAGS }}" \
-o _bin/resourcedistribution-generator/${{ matrix.os }}-${{ matrix.arch }}/resourcedistributiongenerator${{ matrix.os == 'windows' && '.exe' || '' }} \
./cmd/resourcedistributiongenerator/main.go
- name: Compress resourcedistribution-generator
- name: Package artifacts
run: |
cd _bin/resourcedistribution-generator && \
${{ env.DIST_DIRS }} tar -zcf resourcedistribution-generator-{}.tar.gz {} \; && \
cd .. && \
sha256sum resourcedistribution-generator/resourcedistribution-generator-* >> sha256-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.txt \
- name: Upload resourcedistribution-generator tar.gz
uses: actions/upload-release-asset@v1.0.2
# Package kubectl-kruise
cd _bin/kubectl-kruise/${{ matrix.os }}-${{ matrix.arch }}
cp ../../../LICENSE .
cp ../../../README.md .
if [ "${{ matrix.os }}" = "windows" ]; then
zip -r kubectl-kruise-${{ matrix.os }}-${{ matrix.arch }}.zip .
else
tar -czf kubectl-kruise-${{ matrix.os }}-${{ matrix.arch }}.tar.gz .
fi
cd ../../..
# Package resourcedistribution-generator
cd _bin/resourcedistribution-generator/${{ matrix.os }}-${{ matrix.arch }}
if [ "${{ matrix.os }}" = "windows" ]; then
zip -r resourcedistribution-generator-${{ matrix.os }}-${{ matrix.arch }}.zip .
else
tar -czf resourcedistribution-generator-${{ matrix.os }}-${{ matrix.arch }}.tar.gz .
fi
cd ../../..
- name: Generate checksums
run: |
cd _bin
find . -name "*.tar.gz" -o -name "*.zip" | xargs sha256sum > sha256-${{ matrix.os }}-${{ matrix.arch }}.txt
- name: Upload checksums artifact
uses: actions/upload-artifact@v4.4.3
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./_bin/resourcedistribution-generator/resourcedistribution-generator-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.tar.gz
asset_name: resourcedistribution-generator-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}-${{ steps.get_release.outputs.tag_name }}.tar.gz
asset_content_type: binary/octet-stream
- name: Post sha256
uses: actions/upload-artifact@v4
with:
name: sha256sums-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}
path: ./_bin/sha256-${{ steps.get_matrix.outputs.OS }}-${{ steps.get_matrix.outputs.ARCH }}.txt
name: sha256sums-${{ matrix.os }}-${{ matrix.arch }}
path: _bin/sha256-${{ matrix.os }}-${{ matrix.arch }}.txt
retention-days: 1
upload-sha256sums:
- name: Upload build artifacts
uses: actions/upload-artifact@v4.4.3
with:
name: binaries-${{ matrix.os }}-${{ matrix.arch }}
path: |
_bin/kubectl-kruise/${{ matrix.os }}-${{ matrix.arch }}/*.tar.gz
_bin/kubectl-kruise/${{ matrix.os }}-${{ matrix.arch }}/*.zip
_bin/resourcedistribution-generator/${{ matrix.os }}-${{ matrix.arch }}/*.tar.gz
_bin/resourcedistribution-generator/${{ matrix.os }}-${{ matrix.arch }}/*.zip
retention-days: 1
upload-release-assets:
needs: build_and_upload
runs-on: ubuntu-latest
name: upload-sha256sums
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get release
id: get_release
uses: bruceadams/get-release@v1.2.2
- name: Download artifacts
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download all artifacts
uses: actions/download-artifact@v4.1.7
with:
pattern: sha256sums-*
pattern: "*"
merge-multiple: true
path: sha256sums
- shell: bash
path: artifacts
- name: Prepare release assets
run: |
cat sha256sums/*.txt > sha256sums.txt
- name: Upload Checksums
uses: actions/upload-release-asset@v1.0.2
mkdir -p release-assets
# Move binary archives to release assets
find artifacts -name "*.tar.gz" -o -name "*.zip" | while read file; do
filename=$(basename "$file")
# Add version tag to filename
name_part="${filename%.*}"
ext="${filename##*.}"
if [[ "$filename" == *.tar.gz ]]; then
ext="tar.gz"
name_part="${filename%.tar.gz}"
fi
cp "$file" "release-assets/${name_part}-${GITHUB_REF_NAME}.${ext}"
done
# Combine all checksums
cat artifacts/sha256-*.txt > release-assets/sha256sums-${GITHUB_REF_NAME}.txt
- name: Upload release assets
uses: softprops/action-gh-release@v2.0.8
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: sha256sums.txt
asset_name: sha256sums-${{ steps.get_release.outputs.tag_name }}.txt
asset_content_type: text/plain
files: release-assets/*
fail_on_unmatched_files: true
- name: Update kubectl plugin version in krew-index
uses: rajatjindal/krew-release-bot@v0.0.43
uses: rajatjindal/krew-release-bot@v0.0.46

3
.nancy-ignore Normal file
View File

@ -0,0 +1,3 @@
# Temporary exclusion - proxy/tokenizer vulnerabilities don't affect our usage
CVE-2025-22870
CVE-2025-22872

View File

@ -198,6 +198,60 @@ kubectl kruise migrate CloneSet --from Deployment --src-name deployment-demo --d
#### kubectl kruise autoscale SUBCOMMAND [options]
* [ ] kubectl kruise autoscale
## Security
This project includes automated vulnerability scanning to ensure the security of dependencies.
### Vulnerability Scanning
We use two complementary tools to scan for vulnerabilities in our Go dependencies:
1. **Nancy by Sonatype** - Comprehensive dependency scanning against the Sonatype OSS Index
2. **govulncheck** - Official Go vulnerability scanner with call graph analysis to reduce false positives
### CI/CD Security Integration
Security scans are automatically run:
- On every push to `master` and `release*` branches
- On every pull request
- Daily at 2 AM UTC via scheduled workflow
### Handling Vulnerabilities
If vulnerabilities are found:
1. **Review the vulnerability report** - Check if the vulnerability affects your usage
2. **Update dependencies** - Upgrade to a non-vulnerable version if available
3. **Apply workarounds** - If no update is available, consider alternative approaches
4. **Temporary exclusions** - For false positives or accepted risks, add the CVE ID to `.nancy-ignore`
#### Excluding Vulnerabilities
To exclude specific vulnerabilities from Nancy scans, add the CVE ID or OSS Index ID to the `.nancy-ignore` file:
```
# Example: Exclude a specific CVE
CVE-2021-12345
# Example: Exclude by OSS Index ID
9eb9a5bc-8310-4104-bf85-3a820d28ba79
```
### Running Security Scans Locally
To run vulnerability scans locally:
```bash
# Install tools
go install github.com/sonatype-nexus-community/nancy@latest
go install golang.org/x/vuln/cmd/govulncheck@latest
# Run Nancy scan
go list -json -deps ./... > go.list
nancy sleuth --loud
# Run govulncheck
govulncheck ./...
```
### Contributing
We encourage you to help out by reporting issues, improving documentation, fixing bugs, or adding new features.

View File

@ -6,7 +6,8 @@ Restart a resource
Restart a resource.
Resource will be rollout restarted.
Resource will be rollout restarted. Supported kinds include:
CloneSet, DaemonSet, Deployment, StatefulSet, and UnitedDeployment.
```
kubectl-kruise rollout restart RESOURCE
@ -21,6 +22,9 @@ kubectl-kruise rollout restart RESOURCE
# Restart a daemonset
kubectl-kruise rollout restart daemonset/abc
# Restart a UnitedDeployment
kubectl-kruise rollout restart uniteddeployment/my-app
```
### Options

BIN
go.list Normal file

Binary file not shown.

4
go.mod
View File

@ -1,8 +1,8 @@
module github.com/openkruise/kruise-tools
go 1.22.0
go 1.23.0
toolchain go1.22.4
toolchain go1.23.4
require (
github.com/go-errors/errors v1.4.2

View File

@ -65,7 +65,10 @@ var (
kubectl-kruise rollout restart cloneset/abc
# Restart a daemonset
kubectl-kruise rollout restart daemonset/abc`)
kubectl-kruise rollout restart daemonset/abc
# Restart a UnitedDeployment
kubectl-kruise rollout restart uniteddeployment/my-app`)
)
// NewRolloutRestartOptions returns an initialized RestartOptions instance
@ -80,7 +83,7 @@ func NewRolloutRestartOptions(streams genericclioptions.IOStreams) *RestartOptio
func NewCmdRolloutRestart(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewRolloutRestartOptions(streams)
validArgs := []string{"deployment", "daemonset", "statefulset", "cloneset"}
validArgs := []string{"deployment", "daemonset", "statefulset", "cloneset", "uniteddeployment"}
cmd := &cobra.Command{
Use: "restart RESOURCE",

73
pkg/utils/math_test.go Normal file
View File

@ -0,0 +1,73 @@
/*
Copyright 2025 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import "testing"
func TestInt32Min(t *testing.T) {
testCases := []struct {
name string
a int32
items []int32
expected int32
}{
{
name: "No extra items",
a: 10,
items: []int32{},
expected: 10,
},
{
name: "All positive numbers",
a: 10,
items: []int32{5, 20, 12},
expected: 5,
},
{
name: "With negative numbers",
a: -5,
items: []int32{10, -2, -10},
expected: -10,
},
{
name: "With zero",
a: 1,
items: []int32{5, 0, 2},
expected: 0,
},
{
name: "All numbers are the same",
a: 7,
items: []int32{7, 7, 7},
expected: 7,
},
{
name: "Initial value 'a' is the minimum",
a: 3,
items: []int32{10, 5, 8},
expected: 3,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := Int32Min(tc.a, tc.items...); got != tc.expected {
t.Errorf("Int32Min() = %v, want %v", got, tc.expected)
}
})
}
}

132
pkg/utils/misc_test.go Normal file
View File

@ -0,0 +1,132 @@
/*
Copyright 2025 The Kruise Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"testing"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Helper function for string pointers
func strPtr(s string) *string { return &s }
func TestIsKruiseRolloutsAnnotation(t *testing.T) {
testCases := []struct {
name string
input *string
expected bool
}{
{name: "Nil string", input: nil, expected: false},
{name: "Kruise prefix", input: strPtr("rollouts.kruise.io/annotation"), expected: true},
{name: "Non-matching string", input: strPtr("other.domain/key"), expected: false},
{name: "Exact prefix", input: strPtr("rollouts.kruise.io/"), expected: true},
{name: "Empty string", input: strPtr(""), expected: false},
{name: "Substring match", input: strPtr("pre/rollouts.kruise.io/suffix"), expected: true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := IsKruiseRolloutsAnnotation(tc.input); got != tc.expected {
t.Errorf("expected %v, got %v", tc.expected, got)
}
})
}
}
func TestInCanaryProgress(t *testing.T) {
testCases := []struct {
name string
deployment *appsv1.Deployment
expected bool
}{
{
name: "Not paused",
deployment: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{Paused: false},
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{InRolloutProgressingAnnotation: "true"}},
},
expected: false,
},
{
name: "Missing progressing annotation",
deployment: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{Paused: true},
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
},
expected: false,
},
{
name: "Has strategy annotation",
deployment: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{Paused: true},
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
InRolloutProgressingAnnotation: "true",
DeploymentStrategyAnnotation: "partition",
},
},
},
expected: false,
},
{
name: "Valid canary state",
deployment: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{Paused: true},
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{InRolloutProgressingAnnotation: "true"},
},
},
expected: true,
},
{
name: "Nil annotations",
deployment: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{Paused: true},
ObjectMeta: metav1.ObjectMeta{},
},
expected: false,
},
{
name: "Explicit empty annotations",
deployment: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{Paused: true},
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
},
expected: false,
},
{
name: "Only strategy annotation present",
deployment: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{Paused: true},
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{DeploymentStrategyAnnotation: "partition"},
},
},
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := InCanaryProgress(tc.deployment); got != tc.expected {
t.Errorf("expected %v, got %v", tc.expected, got)
}
})
}
}