Compare commits

...

71 Commits

Author SHA1 Message Date
Yuxing Deng
004e4751c8 fix: aliyun image is not built 2024-07-18 20:37:08 +08:00
Yuxing Deng
8bf22555dd fix: only release kube-explorer binary in release 2024-07-18 19:56:31 +08:00
Yuxing Deng
896e03e279 feat(ci): Added release step for tag
Skip compress process for PR build
Add commit message check
Add release-note
2024-07-18 17:19:41 +08:00
Yuxing Deng
979b4991fa feat: switch to GHA 2024-07-18 10:08:15 +08:00
Yuxing Deng
aec9926ed8 fix: Add dockerignore file to clean up each ci build 2024-07-17 15:02:39 +08:00
Yuxing Deng
2f3c1e6ab5 feat: Support specifying shell pod image and registry 2024-07-17 15:02:39 +08:00
Yuxing Deng
faa83722a0 refactor: No need to customize steve for explorer 2024-07-16 16:30:23 +08:00
Yuxing Deng
cd955243b6 dep(kube-explorer): Bump steve
Rebase to rancher v2.8.0 baseline
Upgrade ke-steve to ke/v0.4
Upgrade DASHBOARD UI version to v2.8.0
2024-01-05 15:00:39 +08:00
Yuxing Deng
2b39db9f07 fix(ci): Fix multi-arch build problem
Added build linux/arm64 binary to default ci
2023-11-15 16:30:57 +08:00
Yuxing Deng
4dc1acb1f2 feat(ci): Improve drone pipeline configuration
- Separate push and tag pipeline
- Use buildx to build and push multi-arch image
2023-11-15 16:09:58 +08:00
Yuxing Deng
989d087b99 No need to run hack_fs
As the embed fs packaged all files.
And bump upx version.
2023-11-13 16:29:48 +08:00
Yuxing Deng
c214e6ba6a Add windows amd64 support
Bump steve customized logic to fix serving embed assets problem
2023-11-13 15:46:03 +08:00
Yuxing Deng
390b11caef Bump steve
And upgrade builder image to v1.21.
2023-11-10 15:24:31 +08:00
niusmallnan
e016261c4b Bump steve and dashboard 2023-07-14 13:36:54 +08:00
niusmallnan
c43288964a Bump dashboard 2023-07-13 15:20:20 +08:00
niusmallnan
70e586976d Bump dashboard 2023-07-13 12:16:04 +08:00
niusmallnan
d0ce0e28bf Bump dashboard 2023-07-13 10:46:03 +08:00
niusmallnan
ad0a0c0cb3 Bump steve and dashboard for v2.7.5 2023-07-13 09:02:30 +08:00
niusmallnan
651d499086 Bumo bci 15.5 2023-07-11 11:35:10 +08:00
niusmallnan
8e592b1a3c Bump steve and dashboard for Rancher v2.7.2 2023-04-18 12:20:04 +08:00
niusmallnan
c1f5fda228 [CI SKIP] Update README for basic auth 2023-04-11 11:07:14 +08:00
niusmallnan
10e5323c95 [CI SKIP] Use ingress v1 for nginx-ingress basic auth demo 2023-04-10 17:18:22 +08:00
niusmallnan
ea49f9d3b4 [CI SKIP] Add basic-auth manifests for traefik v2 2023-04-10 17:09:30 +08:00
niusmallnan
b0b81ba87d [CI SKIP] switch to sslip.io for ingress demos 2023-04-10 16:20:43 +08:00
niusmallnan
e757347def Disable compress for darwin releases 2023-04-04 09:08:50 +08:00
niusmallnan
f4970b85a2 Bump steve and dashboard for Rancher v2.7.1 2023-03-20 15:13:04 +08:00
niusmallnan
bfae192748 Use --scanners instead of --security-checks 2023-03-20 11:24:18 +08:00
niusmallnan
3810cd702f Bump upx 4.0.2 2023-03-20 11:23:47 +08:00
niusmallnan
f898c559e0 Bump steve and dashboard 2022-12-20 15:16:25 +08:00
niusmallnan
f0effa7f09 Bump upx 4.0.1 2022-12-20 15:12:34 +08:00
niusmallnan
2838ceb34a Add image scan pipeline in drone 2022-12-09 09:29:18 +08:00
niusmallnan
40a972eeef Use BCI minimal image 2022-12-09 08:59:34 +08:00
niusmallnan
88c924a816 Use BCI image 2022-12-09 08:51:39 +08:00
niusmallnan
d24282849f Bump dashboard v2.6.9-kube-explorer-ui-rc2 2022-11-15 13:47:25 +08:00
niusmallnan
92aaca7407 Bump steve and dashboard based on Rancher v2.6.9 2022-10-19 11:58:11 +08:00
niusmallnan
c278dbb810 Bump golang 1.19 2022-10-19 11:57:10 +08:00
niusmallnan
5c2ecdfb97 Bump dashboard v2.6.9-rc4 [CI SKIP] 2022-10-12 15:58:46 +08:00
bagechashu
ecf6faba80 fix: text incorrect at deploy use kubectl
fix text incorrect
2022-10-07 13:24:45 +08:00
niusmallnan
a89b9b46bf Bump dashboard v2.6.9-rc1 [CI SKIP] 2022-09-26 11:10:32 +08:00
niusmallnan
30c0ceef73 Bump dashboard v2.6.8 2022-08-30 17:37:39 +08:00
Yuxing Deng
f6536c289e Update Dockerfile.dapper
Using `$GOPATH` instead of static path `/go`
2022-08-25 15:55:51 +08:00
niusmallnan
5347d02990 Bump dashboard 2022-08-22 13:27:48 +08:00
niusmallnan
663e8d7682 Bump steve [CI SKIP] 2022-08-19 17:30:07 +08:00
niusmallnan
dd8291f9e3 Bump steve and dashboard based on Rancher v2.6.7 2022-08-19 17:13:15 +08:00
niusmallnan
55e5418bd0 Support darwin arm64 binary [CI SKIP] 2022-08-10 10:45:56 +08:00
niusmallnan
7c3f7b5401 Bump steve to clean up resources created by kubectl shell [CI SKIP] 2022-08-09 09:34:39 +08:00
niusmallnan
98ae5fe7c9 Bump ui v2.6.7-rc5-kube-explorer-ui-rc2 [CI SKIP] 2022-08-04 14:50:49 +08:00
niusmallnan
f91c8df6e2 Bump steve and dashboard 2022-08-03 09:54:03 +08:00
niusmallnan
6344c0767a Bump steve to support --insecure-skip-tls-verify like kubectl 2022-07-22 16:49:05 +08:00
niusmallnan
bbb5db5fb5 Update drone CI [CI SKIP] 2022-05-24 12:04:11 +08:00
niusmallnan
a81fc996c9 Bump ui v2.6.5-kube-explorer-ui-rc1 [CI SKIP] 2022-05-17 14:34:58 +08:00
niusmallnan
a0c6f0edb7 Bump ui v2.6.5-kube-explorer-ui-rc1 [CI SKIP] 2022-05-07 14:37:46 +08:00
niusmallnan
e986d891ef Bump steve based on rancher 2.6.5-rc8 2022-05-07 14:12:57 +08:00
niusmallnan
0857f01c77 Bump steve for formatting cluster display name by strings.Title 2022-02-09 16:35:17 +08:00
niusmallnan
ec1152aba1 UPX is not required 2022-02-09 16:28:35 +08:00
niusmallnan
5846ee6155 Bump steve for formatting cluster display name 2022-02-09 16:06:19 +08:00
niusmallnan
3e9b2fe7f5 Update readme [CI SKIP] 2022-02-08 17:59:10 +08:00
niusmallnan
c4e05aa13d Add GIF record [CI SKIP] 2022-02-07 14:35:15 +08:00
niusmallnan
ae72fa4141 Enable tags in drone ci triggers [CI SKIP] 2022-01-21 17:32:52 +08:00
niusmallnan
3cfd0e2a32 Bump ui v2.6.3-kube-explorer-ui-rc2 2022-01-21 17:20:12 +08:00
niusmallnan
179d7aa95e Enable drone CI 2022-01-21 16:05:02 +08:00
niusmallnan
c29a7505b2 Bump golangci-lint 2022-01-21 14:58:25 +08:00
niusmallnan
71ace76cf7 Bump steve 2021-12-24 15:51:50 +08:00
niusmallnan
57d6d420b8 Bump ke dashboard 2.6.3-rc1 2021-12-24 15:16:16 +08:00
niusmallnan
b46ddc4c8e Bump dashboard 2021-11-15 20:05:01 +08:00
niusmallnan
b5200596f9 Update README 2021-10-15 13:37:09 +08:00
niusmallnan
a90dd6cafc Update README 2021-10-15 13:30:58 +08:00
niusmallnan
b227ab5257 Bump dashboard and steve 2021-10-15 13:16:59 +08:00
niusmallnan
db3c9e207d Bump dashboard 2021-09-07 17:17:18 +08:00
niusmallnan
ac0ab3d84c Bump dashboard 2021-09-06 16:08:34 +08:00
niusmallnan
41cfdac948 Bump dashboard 2021-08-24 15:24:35 +08:00
43 changed files with 3533 additions and 152 deletions

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
/bin
/dist
/internal/ui/ui

165
.drone_backup.yml Normal file
View File

@@ -0,0 +1,165 @@
type: docker
kind: pipeline
name: push
platform:
os: linux
arch: amd64
trigger:
event:
exclude:
- promote
include:
- push
- pull_request
volumes:
- name: docker
host:
path: /var/run/docker.sock
node:
instance: agent-amd64
steps:
- name: build
image: rancher/dapper:v0.6.0
commands:
- dapper ci
environment:
CROSS: "${DRONE_BUILD_EVENT}"
privileged: true
volumes:
- name: docker
path: /var/run/docker.sock
- name: image-scan-head
image: aquasec/trivy
commands:
- trivy image --no-progress --ignore-unfixed --severity HIGH,CRITICAL --scanners vuln --exit-code 1 cnrancher/kube-explorer:${DRONE_COMMIT:0:7}
volumes:
- name: docker
path: /var/run/docker.sock
when:
event:
- push
ref:
include:
- "refs/heads/main"
- "refs/heads/v*"
- name: install-buildx-support
image: tonistiigi/binfmt
privileged: true
entrypoint:
- /usr/bin/binfmt
command:
- --install
- all
when:
event:
- push
ref:
include:
- "refs/heads/main"
- "refs/heads/v*"
- name: docker-publish
image: thegeeklab/drone-docker-buildx
privileged: true
settings:
platforms: linux/amd64,linux/arm64/v8
dockerfile: package/Dockerfile
repo: cnrancher/kube-explorer
tag: latest
username:
from_secret: docker_username
password:
from_secret: docker_password
volumes:
- name: docker
path: /var/run/docker.sock
when:
event:
- push
ref:
include:
- "refs/heads/main"
- "refs/heads/v*"
---
type: docker
kind: pipeline
name: tag
platform:
os: linux
arch: amd64
trigger:
event:
exclude:
- promote
include:
- tag
ref:
include:
- "refs/tags/*"
volumes:
- name: docker
host:
path: /var/run/docker.sock
node:
instance: agent-amd64
steps:
- name: release
image: rancher/dapper:v0.6.0
commands:
- dapper ci
privileged: true
environment:
CROSS: "${DRONE_BUILD_EVENT}"
volumes:
- name: docker
path: /var/run/docker.sock
- name: install-buildx-support
image: tonistiigi/binfmt
privileged: true
entrypoint:
- /usr/bin/binfmt
command:
- --install
- all
- name: docker-publish
image: thegeeklab/drone-docker-buildx
privileged: true
settings:
platforms: linux/amd64,linux/arm64/v8
dockerfile: package/Dockerfile
repo: cnrancher/kube-explorer
tag: ${DRONE_TAG}
username:
from_secret: docker_username
password:
from_secret: docker_password
volumes:
- name: docker
path: /var/run/docker.sock
- name: github_binary_release
image: plugins/github-release
settings:
api_key:
from_secret: github_token
checksum:
- sha256
files:
- "bin/*"
title: "${DRONE_TAG}"
overwrite: true

21
.github/workflows/pr.yaml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: pull request
on:
pull_request:
types:
- opened
- reopened
- synchronize
jobs:
pr-build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Commitsar check
uses: aevea/commitsar@v0.20.2
- name: Build to test
env:
SKIP_COMPRESS: "true"
run: make ci

81
.github/workflows/push.yaml vendored Normal file
View File

@@ -0,0 +1,81 @@
name: Push to Master
on:
push:
branches:
- main
tags:
- 'v*.*.*' # Matches any tag that starts with 'v' and follows semantic versioning
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Login to Aliyun ACR
uses: docker/login-action@v3
with:
registry: registry.cn-shenzhen.aliyuncs.com
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_TOKEN }}
if: ${{ vars.ALIYUN == 'true' }}
- name: Login to Dockerhub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: CI
if: startsWith(github.ref, 'refs/heads/')
env:
CROSS: push
run: make github_ci
- name: CI
if: startsWith(github.ref, 'refs/tags/')
env:
CROSS: tag
run: |
make github_ci
make release-note
- name: Prepare for packaging image
run: cp dist/* package/
- name: Set docker iamge name
id: image-name
env:
REPO_OVERRIDE: ${{ vars.REPO || 'cnrancher' }}
IMAGE_OVERRIDE: ${{ vars.IMAGE || 'kube-explorer' }}
run: |
tag_name=latest;
if [[ ${GITHUB_REF} == refs/tags/* ]]; then tag_name=${GITHUB_REF#refs/tags/}; fi;
echo "image_name=${REPO_OVERRIDE}/${IMAGE_OVERRIDE}:${tag_name}" >> $GITHUB_OUTPUT;
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build to Dockerhub
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64/v8
tags: "${{ steps.image-name.outputs.image_name }}"
context: package
push: true
- name: Build to Aliyun
uses: docker/build-push-action@v6
with:
tags: registry.cn-shenzhen.aliyuncs.com/${{ steps.image-name.outputs.image_name }}
context: package
push: true
if: ${{ vars.ALIYUN == 'true' }}
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: dist/kube-explorer-*
body_path: dist/release-note
draft: true

4
.gitignore vendored
View File

@@ -19,3 +19,7 @@
/dist /dist
/build /build
*.swp *.swp
/.vscode
/vendor
/internal/ui/ui/

68
.golangci.json Normal file
View File

@@ -0,0 +1,68 @@
{
"linters": {
"disable-all": true,
"enable": [
"govet",
"revive",
"goimports",
"misspell",
"ineffassign",
"gofmt"
]
},
"linters-settings": {
"govet": {
"check-shadowing": false
},
"gofmt": {
"simplify": false
}
},
"run": {
"skip-dirs": [
"vendor",
"tests",
"pkg/client",
"pkg/generated",
"scripts"
],
"tests": false,
"timeout": "10m"
},
"issues": {
"exclude-rules": [
{
"linters": "govet",
"text": "^(nilness|structtag)"
},
{
"path":"pkg/apis/management.cattle.io/v3/globaldns_types.go",
"text":".*lobalDns.*"
},
{
"path": "pkg/apis/management.cattle.io/v3/zz_generated_register.go",
"text":".*lobalDns.*"
},
{
"path":"pkg/apis/management.cattle.io/v3/zz_generated_list_types.go",
"text":".*lobalDns.*"
},
{
"linters": "revive",
"text": "should have comment"
},
{
"linters": "revive",
"text": "should be of the form"
},
{
"linters": "revive",
"text": "by other packages, and that stutters"
},
{
"linters": "typecheck",
"text": "imported but not used as apierrors"
}
]
}
}

View File

@@ -1,31 +1,28 @@
FROM golang:1.16 FROM aevea/release-notary:0.9.2 as tools
FROM registry.suse.com/bci/golang:1.22
ARG PROXY
ARG GOPROXY
ARG DAPPER_HOST_ARCH ARG DAPPER_HOST_ARCH
ENV HOST_ARCH=${DAPPER_HOST_ARCH} ARCH=${DAPPER_HOST_ARCH} ENV HOST_ARCH=${DAPPER_HOST_ARCH} ARCH=${DAPPER_HOST_ARCH}
ENV https_proxy=${PROXY} \
http_proxy=${PROXY}
RUN apt-get update && \ RUN zypper -n install ca-certificates git-core wget curl unzip tar vim less file xz
apt-get install -y ca-certificates git wget curl xz-utils && \ RUN zypper install -y -f docker
rm -f /bin/sh && ln -s /bin/bash /bin/sh && \
curl -sL https://github.com/upx/upx/releases/download/v3.96/upx-3.96-${ARCH}_linux.tar.xz | tar xvJf - --strip-components=1 -C /tmp && \ ENV UPX_VERSION 4.2.1
RUN curl -sL https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-${ARCH}_linux.tar.xz | tar xvJf - --strip-components=1 -C /tmp && \
mv /tmp/upx /usr/bin/ mv /tmp/upx /usr/bin/
RUN if [ "${ARCH}" == "amd64" ]; then \ RUN if [ "${ARCH}" == "amd64" ]; then \
curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.27.0; \ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.54.2; \
fi fi
COPY --from=tools /app/release-notary /usr/local/bin/
ENV DOCKER_URL_amd64=https://get.docker.com/builds/Linux/x86_64/docker-1.10.3 \ ENV CATTLE_DASHBOARD_UI_VERSION="v2.8.0-kube-explorer-ui-rc3"
DOCKER_URL_arm=https://github.com/rancher/docker/releases/download/v1.10.3-ros1/docker-1.10.3_arm \
DOCKER_URL_arm64=https://github.com/rancher/docker/releases/download/v1.10.3-ros1/docker-1.10.3_arm64 \ ENV DAPPER_ENV REPO TAG DRONE_TAG CROSS GOPROXY SKIP_COMPRESS GITHUB_REPOSITORY GITHUB_TOKEN
DOCKER_URL=DOCKER_URL_${ARCH} ENV DAPPER_SOURCE /go/src/github.com/cnrancher/kube-explorer
RUN wget -O - ${!DOCKER_URL} > /usr/bin/docker && chmod +x /usr/bin/docker
ENV GIT_COMMIT="68b9d2e74a0ab2053ae2a0a50e1476db96653225" \
GIT_BRANCH="ke/v0.2" \
GIT_SOURCE="/go/src/github.com/rancher/steve" \
CATTLE_DASHBOARD_UI_VERSION=v2.6.0-rc5-kube-explorer-ui-rc1
ENV DAPPER_ENV REPO TAG DRONE_TAG CROSS
ENV DAPPER_SOURCE /opt/kube-explorer
ENV DAPPER_OUTPUT ./bin ./dist ENV DAPPER_OUTPUT ./bin ./dist
ENV DAPPER_DOCKER_SOCKET true ENV DAPPER_DOCKER_SOCKET true
ENV DAPPER_RUN_ARGS "-v ke-pkg:/go/pkg -v ke-cache:/root/.cache/go-build --privileged" ENV DAPPER_RUN_ARGS "-v ke-pkg:/go/pkg -v ke-cache:/root/.cache/go-build --privileged"

View File

@@ -1,12 +1,12 @@
# kube-explorer # kube-explorer
kube-explorer is portable explorer for Kubernetes, without any dependency. kube-explorer is a portable explorer for Kubernetes without any dependency.
It integrates the Rancher steve framework and its dashboard, and is recompiled, packaged, compressed, and provides an almost completely stateless Kubernetes resource manager. It integrates the Rancher steve framework and its dashboard, and is recompiled, packaged, compressed, and provides an almost completely stateless Kubernetes resource manager.
## Usage ✅ ## Usage ✅
Please download the binary from the [release page](https://github.com/niusmallnan/kube-explorer/releases). Please download the binary from the [release page](https://github.com/cnrancher/kube-explorer/releases).
To run an HTTP only server: To run an HTTP only server:
@@ -15,3 +15,27 @@ To run an HTTP only server:
``` ```
Then, open the browser to visit http://x.x.x.x:9898 . Then, open the browser to visit http://x.x.x.x:9898 .
![](docs/assets/kube-explorer-record.gif)
## Build ✅
To debug on an AMD64 Linux host:
```
make dev
# $basedir=/opt/ui/dist/
# prepare the file trees like this
# $basedir/dashboard/
# $basedir/index.html
# good to go!
./kube-explorer --debug --ui-path /opt/ui/dist/ --http-listen-port=9898 --https-listen-port=0
```
To build all cross-platform binaries:
```
CROSS=1 make
```

12
deploy/kubectl/README.md Normal file
View File

@@ -0,0 +1,12 @@
## Access Control Via Basic Auth
Deploy the kube-explorer workload:
```
kubectl create -f .
```
Configure for different IngressClass:
- [Nginx Ingress](./nginx-auth)
- [Traefik Ingress](./traefik-v2-auth)

View File

@@ -18,7 +18,7 @@ spec:
spec: spec:
serviceAccountName: kube-explorer serviceAccountName: kube-explorer
containers: containers:
- image: niusmallnan/kube-explorer - image: cnrancher/kube-explorer
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
name: kube-explorer name: kube-explorer
ports: ports:

View File

@@ -1,4 +1,4 @@
## Traefik Auth ## Ingress-Nginx Basic Auth
This can be used in the cluster which uses the nginx-ingress. This can be used in the cluster which uses the nginx-ingress.
@@ -13,9 +13,9 @@ htpasswd -nb username password | base64
To install this mode, just run this script: To install this mode, just run this script:
``` ```
kubectl apply -f ./secret.yaml kubectl create -f ./secret.yaml
export MY_XIP_IO=$(curl -sL ipinfo.io/ip) export MY_IP=$(curl -sL ipinfo.io/ip)
envsubst < ./ingress.yaml.tpl | kubectl apply -f - envsubst < ./ingress.yaml.tpl | kubectl create -f -
``` ```
For more infos: https://kubernetes.github.io/ingress-nginx/examples/auth/basic/ For more infos: https://kubernetes.github.io/ingress-nginx/examples/auth/basic/

View File

@@ -1,8 +1,8 @@
# Note: please replace the host first # Note: please replace the host first
# To use xip.io: http://xip.io/ # To use sslip.io: https://sslip.io/
# To get your public IP: curl ipinfo.io/ip # To get your public IP: curl ipinfo.io/ip
apiVersion: networking.k8s.io/v1beta1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: kube-explorer name: kube-explorer
@@ -10,16 +10,18 @@ metadata:
labels: labels:
app: kube-explorer app: kube-explorer
annotations: annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: kube-explorer nginx.ingress.kubernetes.io/auth-secret: kube-explorer
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - kube-explorer' nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - kube-explorer'
spec: spec:
rules: rules:
- host: "${MY_XIP_IO}.xip.io" - host: "${MY_IP}.sslip.io"
http: http:
paths: paths:
- path: / - path: /
pathType: Prefix
backend: backend:
serviceName: kube-explorer service:
servicePort: 8989 name: kube-explorer
port:
number: 8989

View File

@@ -13,9 +13,9 @@ htpasswd -nb username password | base64
To install this mode, just run this script: To install this mode, just run this script:
``` ```
kubectl apply -f ./secret.yaml kubectl create -f ./secret.yaml
export MY_XIP_IO=$(curl -sL ipinfo.io/ip) export MY_IP=$(curl -sL ipinfo.io/ip)
envsubst < ./ingress.yaml.tpl | kubectl apply -f - envsubst < ./ingress.yaml.tpl | kubectl create -f -
``` ```
For more infos: https://doc.traefik.io/traefik/v1.7/configuration/backends/kubernetes/ For more infos: https://doc.traefik.io/traefik/v1.7/configuration/backends/kubernetes/

View File

@@ -1,5 +1,5 @@
# Note: please replace the host first # Note: please replace the host first
# To use xip.io: http://xip.io/ # To use sslip.io: https://sslip.io/
# To get your public IP: curl ipinfo.io/ip # To get your public IP: curl ipinfo.io/ip
apiVersion: networking.k8s.io/v1beta1 apiVersion: networking.k8s.io/v1beta1
@@ -16,7 +16,7 @@ metadata:
ingress.kubernetes.io/auth-remove-header: "true" ingress.kubernetes.io/auth-remove-header: "true"
spec: spec:
rules: rules:
- host: "${MY_XIP_IO}.xip.io" - host: "${MY_IP}.sslip.io"
http: http:
paths: paths:
- path: / - path: /

View File

@@ -0,0 +1,21 @@
## Traefik Auth
This can be used in K3s, as K3s use traefik as the default ingress class.
We use `basic-auth` to control the access of kube-explorer. The auth token is stored in the secret.
The default user is `niusmallnan`, and password is `dagedddd`. You can replace to another value with `htpasswd` tool.
```
htpasswd -nb username password | base64
```
To install this mode, just run this script:
```
kubectl create -f ./middleware.yaml
export MY_IP=$(curl -sL ipinfo.io/ip)
envsubst < ./ingress.yaml.tpl | kubectl create -f -
```
For more infos: https://doc.traefik.io/traefik/middlewares/http/basicauth/

View File

@@ -0,0 +1,25 @@
# Note: please replace the host first
# To use sslip.io.io: https://sslip.io.io/
# To get your public IP: curl ipinfo.io/ip
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kube-explorer
namespace: kube-system
labels:
app: kube-explorer
annotations:
traefik.ingress.kubernetes.io/router.middlewares: kube-system-kube-explorer@kubernetescrd
spec:
rules:
- host: "${MY_IP}.sslip.io"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kube-explorer
port:
number: 8989

View File

@@ -0,0 +1,28 @@
# The definitions below require the definitions for the Middleware and IngressRoute kinds.
# https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: kube-explorer
namespace: kube-system
labels:
app: kube-explorer
spec:
basicAuth:
secret: kube-explorer
removeHeader: true
---
# To create an encoded user:password pair, the following command can be used:
# htpasswd -nb user password | base64
apiVersion: v1
kind: Secret
metadata:
name: kube-explorer
namespace: kube-system
labels:
app: kube-explorer
data:
auth: bml1c21hbGxuYW46JGFwcjEkbDdUZjJOdWskbmNXajYubHYvMGNkcXM0NFoyelVQLgoK
type: Opaque

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 KiB

117
go.mod Normal file
View File

@@ -0,0 +1,117 @@
module github.com/cnrancher/kube-explorer
go 1.22.0
replace k8s.io/client-go => k8s.io/client-go v0.30.1
require (
github.com/gorilla/mux v1.8.1
github.com/rancher/apiserver v0.0.0-20240708202538-39a6f2535146
github.com/rancher/steve v0.0.0-20240709130809-47871606146c
github.com/rancher/wrangler/v3 v3.0.0
github.com/sirupsen/logrus v1.9.3
github.com/urfave/cli v1.22.15
golang.org/x/text v0.14.0
k8s.io/api v0.30.1
k8s.io/apimachinery v0.30.1
k8s.io/apiserver v0.30.1
k8s.io/client-go v12.0.0+incompatible
)
require (
github.com/adrg/xdg v0.4.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rancher/dynamiclistener v0.6.0-rc2 // indirect
github.com/rancher/kubernetes-provider-detector v0.1.5 // indirect
github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 // indirect
github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 // indirect
github.com/rancher/remotedialer v0.3.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/urfave/cli/v2 v2.27.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.30.1 // indirect
k8s.io/component-base v0.30.1 // indirect
k8s.io/klog v1.0.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-aggregator v0.30.1 // indirect
k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.49.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.29.10 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect
sigs.k8s.io/cli-utils v0.35.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

2003
go.sum Normal file

File diff suppressed because it is too large Load Diff

28
internal/config/flags.go Normal file
View File

@@ -0,0 +1,28 @@
package config
import (
"github.com/urfave/cli"
)
var InsecureSkipTLSVerify bool
var SystemDefaultRegistry string
var ShellPodImage string
func Flags() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: "insecure-skip-tls-verify",
Destination: &InsecureSkipTLSVerify,
},
cli.StringFlag{
Name: "system-default-registry",
Destination: &SystemDefaultRegistry,
},
cli.StringFlag{
Name: "pod-image",
Destination: &ShellPodImage,
Value: "rancher/shell:v0.2.1-rc.7",
},
}
}

View File

@@ -0,0 +1,80 @@
package cluster
import (
"context"
"errors"
"fmt"
"net/http"
"time"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/steve/pkg/podimpersonation"
"github.com/rancher/steve/pkg/resources/cluster"
"github.com/rancher/steve/pkg/server"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func Register(_ context.Context, server *server.Server, displayName string) error {
cg := server.ClientFactory
shell := &shell{
cg: cg,
namespace: shellPodNS,
impersonator: podimpersonation.New("shell", cg, time.Hour, getShellPodImage),
}
clusterSchema := server.BaseSchemas.LookupSchema("management.cattle.io.cluster")
if clusterSchema == nil {
return errors.New("failed to find management.cattle.io.cluster in base schema")
}
if clusterSchema.LinkHandlers == nil {
clusterSchema.LinkHandlers = make(map[string]http.Handler)
}
clusterSchema.LinkHandlers["shell"] = shell
clusterSchema.Store = func() types.Store {
return &displaynameWrapper{Store: clusterSchema.Store, displayName: displayName}
}()
return nil
}
type displaynameWrapper struct {
types.Store
displayName string
}
func (s *displaynameWrapper) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
obj, err := s.Store.ByID(apiOp, schema, id)
if err != nil {
return obj, err
}
if obj.ID != "local" {
return obj, nil
}
if c, ok := obj.Object.(*cluster.Cluster); ok {
c.Spec.DisplayName = getDisplayNameWithContext(s.displayName)
}
return obj, nil
}
func (s *displaynameWrapper) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
rtn, err := s.Store.List(apiOp, schema)
if err != nil {
return rtn, err
}
for _, obj := range rtn.Objects {
if obj.ID != "local" {
continue
}
if c, ok := obj.Object.(*cluster.Cluster); ok {
c.Spec.DisplayName = getDisplayNameWithContext(s.displayName)
}
}
return rtn, nil
}
func getDisplayNameWithContext(CurrentKubeContext string) string {
if CurrentKubeContext != "" {
return fmt.Sprintf("%s Cluster", cases.Title(language.English).String(CurrentKubeContext))
}
return "Local Cluster"
}

View File

@@ -0,0 +1,162 @@
package cluster
import (
"context"
"fmt"
"net/http"
"net/http/httputil"
"time"
"github.com/cnrancher/kube-explorer/internal/config"
"github.com/rancher/steve/pkg/podimpersonation"
"github.com/rancher/steve/pkg/stores/proxy"
"github.com/rancher/wrangler/v3/pkg/schemas/validation"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
)
var (
shellPodNS = "kube-system"
)
type shell struct {
namespace string
impersonator *podimpersonation.PodImpersonation
cg proxy.ClientGetter
}
func (s *shell) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
ctx, user, client, err := s.contextAndClient(req)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
pod, err := s.impersonator.CreatePod(ctx, user, s.createPod(), &podimpersonation.PodOptions{
Wait: true,
})
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
client.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{})
s.impersonator.DeleteRole(ctx, *pod)
}()
s.proxyRequest(rw, req, pod, client)
}
func (s *shell) proxyRequest(rw http.ResponseWriter, req *http.Request, pod *v1.Pod, client kubernetes.Interface) {
attachURL := client.CoreV1().RESTClient().
Get().
Namespace(pod.Namespace).
Resource("pods").
Name(pod.Name).
SubResource("exec").
VersionedParams(&v1.PodExecOptions{
Stdin: true,
Stdout: true,
Stderr: true,
TTY: true,
Container: "shell",
Command: []string{"welcome"},
}, scheme.ParameterCodec).URL()
httpClient := client.CoreV1().RESTClient().(*rest.RESTClient).Client
p := httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL = attachURL
req.Host = attachURL.Host
delete(req.Header, "Impersonate-Group")
delete(req.Header, "Impersonate-User")
delete(req.Header, "Authorization")
delete(req.Header, "Cookie")
},
Transport: httpClient.Transport,
FlushInterval: time.Millisecond * 100,
}
p.ServeHTTP(rw, req)
}
func (s *shell) contextAndClient(req *http.Request) (context.Context, user.Info, kubernetes.Interface, error) {
ctx := req.Context()
client, err := s.cg.AdminK8sInterface()
if err != nil {
return ctx, nil, nil, err
}
user, ok := request.UserFrom(ctx)
if !ok {
return ctx, nil, nil, validation.Unauthorized
}
return ctx, user, client, nil
}
func (s *shell) createPod() *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "dashboard-shell-",
Namespace: s.namespace,
},
Spec: v1.PodSpec{
TerminationGracePeriodSeconds: new(int64),
RestartPolicy: v1.RestartPolicyNever,
NodeSelector: map[string]string{
"kubernetes.io/os": "linux",
},
Tolerations: []v1.Toleration{
{
Key: "cattle.io/os",
Operator: "Equal",
Value: "linux",
Effect: "NoSchedule",
},
{
Key: "node-role.kubernetes.io/controlplane",
Operator: "Equal",
Value: "true",
Effect: "NoSchedule",
},
{
Key: "node-role.kubernetes.io/etcd",
Operator: "Equal",
Value: "true",
Effect: "NoExecute",
},
},
Containers: []v1.Container{
{
Name: "shell",
TTY: true,
Stdin: true,
StdinOnce: true,
Env: []v1.EnvVar{
{
Name: "KUBECONFIG",
Value: "/home/shell/.kube/config",
},
},
Image: getShellPodImage(),
ImagePullPolicy: v1.PullIfNotPresent,
},
},
},
}
}
func getShellPodImage() string {
if config.SystemDefaultRegistry == "" {
return config.ShellPodImage
}
return fmt.Sprintf("%s/%s", config.SystemDefaultRegistry, config.ShellPodImage)
}

93
internal/server/config.go Normal file
View File

@@ -0,0 +1,93 @@
package server
import (
"context"
"net/http"
"strings"
"github.com/rancher/apiserver/pkg/types"
steveauth "github.com/rancher/steve/pkg/auth"
"github.com/rancher/steve/pkg/schema"
"github.com/rancher/steve/pkg/server"
"github.com/rancher/steve/pkg/server/cli"
"github.com/rancher/steve/pkg/server/router"
"github.com/rancher/wrangler/v3/pkg/kubeconfig"
"github.com/rancher/wrangler/v3/pkg/ratelimit"
"github.com/cnrancher/kube-explorer/internal/config"
"github.com/cnrancher/kube-explorer/internal/resources/cluster"
"github.com/cnrancher/kube-explorer/internal/ui"
)
func ToServer(ctx context.Context, c *cli.Config, sqlCache bool) (*server.Server, error) {
var (
auth steveauth.Middleware
)
restConfig, err := kubeconfig.GetNonInteractiveClientConfigWithContext(c.KubeConfig, c.Context).ClientConfig()
if err != nil {
return nil, err
}
restConfig.RateLimiter = ratelimit.None
restConfig.Insecure = config.InsecureSkipTLSVerify
if restConfig.Insecure {
restConfig.CAData = nil
restConfig.CAFile = ""
}
if c.WebhookConfig.WebhookAuthentication {
auth, err = c.WebhookConfig.WebhookMiddleware()
if err != nil {
return nil, err
}
}
controllers, err := server.NewController(restConfig, nil)
if err != nil {
return nil, err
}
steveServer, err := server.New(ctx, restConfig, &server.Options{
AuthMiddleware: auth,
Controllers: controllers,
Next: ui.New(c.UIPath),
SQLCache: sqlCache,
// router needs to hack here
Router: func(h router.Handlers) http.Handler {
return rewriteLocalCluster(router.Routes(h))
},
})
if err != nil {
return nil, err
}
// registrer local cluster
if err := cluster.Register(ctx, steveServer, c.Context); err != nil {
return steveServer, err
}
// wrap default store
steveServer.SchemaFactory.AddTemplate(schema.Template{
Customize: func(a *types.APISchema) {
if a.Store == nil {
return
}
a.Store = &deleteOptionStore{
Store: a.Store,
}
},
})
return steveServer, controllers.Start(ctx)
}
func rewriteLocalCluster(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if strings.HasPrefix(req.URL.Path, "/k8s/clusters/local") {
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/k8s/clusters/local")
if req.URL.Path == "" {
req.URL.Path = "/"
}
}
next.ServeHTTP(rw, req)
})
}

View File

@@ -0,0 +1,16 @@
package server
import (
"github.com/rancher/apiserver/pkg/types"
)
type deleteOptionStore struct {
types.Store
}
func (s *deleteOptionStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
query := apiOp.Request.URL.Query()
query.Add("propagationPolicy", "Background")
apiOp.Request.URL.RawQuery = query.Encode()
return s.Store.Delete(apiOp, schema, id)
}

91
internal/ui/embed.go Normal file
View File

@@ -0,0 +1,91 @@
//go:build embed
package ui
import (
"embed"
"io"
"io/fs"
"net/http"
"path/filepath"
"github.com/sirupsen/logrus"
)
// content holds our static web server content.
//
//go:embed all:ui/*
var staticContent embed.FS
type fsFunc func(name string) (fs.File, error)
func (f fsFunc) Open(name string) (fs.File, error) {
return f(name)
}
func pathExist(path string) bool {
_, err := staticContent.Open(path)
return err == nil
}
func openFile(path string) (fs.File, error) {
file, err := staticContent.Open(path)
if err != nil {
logrus.Errorf("openEmbedFile %s err: %v", path, err)
}
return file, err
}
func serveEmbed(basePaths ...string) http.Handler {
handler := fsFunc(func(name string) (fs.File, error) {
logrus.Debugf("serveEmbed name: %s", name)
assetPath := joinEmbedFilepath(append(basePaths, name)...)
logrus.Debugf("serveEmbed final path: %s", assetPath)
return openFile(assetPath)
})
return http.FileServer(http.FS(handler))
}
func serveEmbedIndex(basePath string) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
path := joinEmbedFilepath(basePath, "dashboard", "index.html")
logrus.Debugf("serveEmbedIndex : %s", path)
f, _ := staticContent.Open(path)
io.Copy(rw, f)
f.Close()
})
}
func (u *Handler) ServeAsset() http.Handler {
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
serveEmbed(u.pathSetting()).ServeHTTP(rw, req)
}))
}
func (u *Handler) ServeFaviconDashboard() http.Handler {
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
serveEmbed(u.pathSetting(), "dashboard").ServeHTTP(rw, req)
}))
}
func (u *Handler) IndexFileOnNotFound() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
path := joinEmbedFilepath(u.pathSetting(), req.URL.Path)
if pathExist(path) {
u.ServeAsset().ServeHTTP(rw, req)
} else {
u.IndexFile().ServeHTTP(rw, req)
}
})
}
func (u *Handler) IndexFile() http.Handler {
return u.indexMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
serveEmbedIndex(u.pathSetting()).ServeHTTP(rw, req)
}))
}
func joinEmbedFilepath(paths ...string) string {
return filepath.ToSlash(filepath.Join(paths...))
}

42
internal/ui/external.go Normal file
View File

@@ -0,0 +1,42 @@
//go:build !embed
package ui
import (
"net/http"
"os"
"path/filepath"
)
func (u *Handler) ServeAsset() http.Handler {
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
http.FileServer(http.Dir(u.pathSetting())).ServeHTTP(rw, req)
}))
}
func (u *Handler) ServeFaviconDashboard() http.Handler {
return u.middleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
http.FileServer(http.Dir(filepath.Join(u.pathSetting(), "dashboard"))).ServeHTTP(rw, req)
}))
}
func (u *Handler) IndexFileOnNotFound() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// we ignore directories here because we want those to come from the CDN when running in that mode
if stat, err := os.Stat(filepath.Join(u.pathSetting(), req.URL.Path)); err == nil && !stat.IsDir() {
u.ServeAsset().ServeHTTP(rw, req)
} else {
u.IndexFile().ServeHTTP(rw, req)
}
})
}
func (u *Handler) IndexFile() http.Handler {
return u.indexMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if path, isURL := u.path(); isURL {
_ = serveIndex(rw, path)
} else {
http.ServeFile(rw, req, filepath.Join(path, "index.html"))
}
}))
}

141
internal/ui/handler.go Normal file
View File

@@ -0,0 +1,141 @@
package ui
import (
"crypto/tls"
"io"
"net/http"
"sync"
"github.com/rancher/apiserver/pkg/middleware"
"github.com/sirupsen/logrus"
)
const (
defaultPath = "./ui"
)
var (
insecureClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
)
type StringSetting func() string
type BoolSetting func() bool
type Handler struct {
pathSetting func() string
indexSetting func() string
releaseSetting func() bool
offlineSetting func() string
middleware func(http.Handler) http.Handler
indexMiddleware func(http.Handler) http.Handler
downloadOnce sync.Once
downloadSuccess bool
}
type Options struct {
// The location on disk of the UI files
Path StringSetting
// The HTTP URL of the index file to download
Index StringSetting
// Whether or not to run the UI offline, should return true/false/dynamic
Offline StringSetting
// Whether or not is it release, if true UI will run offline if set to dynamic
ReleaseSetting BoolSetting
}
func NewUIHandler(opts *Options) *Handler {
if opts == nil {
opts = &Options{}
}
h := &Handler{
indexSetting: opts.Index,
offlineSetting: opts.Offline,
pathSetting: opts.Path,
releaseSetting: opts.ReleaseSetting,
middleware: middleware.Chain{
middleware.Gzip,
middleware.FrameOptions,
middleware.CacheMiddleware("json", "js", "css"),
}.Handler,
indexMiddleware: middleware.Chain{
middleware.Gzip,
middleware.NoCache,
middleware.FrameOptions,
middleware.ContentType,
}.Handler,
}
if h.indexSetting == nil {
h.indexSetting = func() string {
return "https://releases.rancher.com/dashboard/latest/index.html"
}
}
if h.offlineSetting == nil {
h.offlineSetting = func() string {
return "dynamic"
}
}
if h.pathSetting == nil {
h.pathSetting = func() string {
return defaultPath
}
}
if h.releaseSetting == nil {
h.releaseSetting = func() bool {
return false
}
}
return h
}
func (u *Handler) path() (path string, isURL bool) {
switch u.offlineSetting() {
case "dynamic":
if u.releaseSetting() {
return u.pathSetting(), false
}
if u.canDownload(u.indexSetting()) {
return u.indexSetting(), true
}
return u.pathSetting(), false
case "true":
return u.pathSetting(), false
default:
return u.indexSetting(), true
}
}
func (u *Handler) canDownload(url string) bool {
u.downloadOnce.Do(func() {
if err := serveIndex(io.Discard, url); err == nil {
u.downloadSuccess = true
} else {
logrus.Errorf("Failed to download %s, falling back to packaged UI", url)
}
})
return u.downloadSuccess
}
func serveIndex(resp io.Writer, url string) error {
r, err := insecureClient.Get(url)
if err != nil {
return err
}
defer r.Body.Close()
_, err = io.Copy(resp, r.Body)
return err
}

48
internal/ui/routers.go Normal file
View File

@@ -0,0 +1,48 @@
package ui
import (
"net/http"
"strings"
"github.com/cnrancher/kube-explorer/internal/version"
"github.com/gorilla/mux"
)
func New(path string) http.Handler {
vue := NewUIHandler(&Options{
Path: func() string {
if path == "" {
return defaultPath
}
return path
},
Offline: func() string {
if path != "" {
return "true"
}
return "dynamic"
},
ReleaseSetting: func() bool {
return version.IsRelease()
},
})
router := mux.NewRouter()
router.UseEncodedPath()
router.Handle("/", http.RedirectHandler("/dashboard/", http.StatusFound))
router.Handle("/dashboard", http.RedirectHandler("/dashboard/", http.StatusFound))
router.Handle("/dashboard/", vue.IndexFile())
router.Handle("/favicon.png", vue.ServeFaviconDashboard())
router.Handle("/favicon.ico", vue.ServeFaviconDashboard())
router.PathPrefix("/dashboard/").Handler(vue.IndexFileOnNotFound())
router.PathPrefix("/k8s/clusters/local").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
url := strings.TrimPrefix(req.URL.Path, "/k8s/clusters/local")
if url == "" {
url = "/"
}
http.Redirect(rw, req, url, http.StatusFound)
})
return router
}

View File

@@ -0,0 +1,23 @@
package version
import (
"fmt"
"regexp"
"strings"
)
var (
Version = "dev"
GitCommit = "HEAD"
// K-EXPLORER
releasePattern = regexp.MustCompile("^v[0-9]")
)
func FriendlyVersion() string {
return fmt.Sprintf("%s (%s)", Version, GitCommit)
}
func IsRelease() bool {
return !strings.Contains(Version, "dev") && releasePattern.MatchString(Version)
}

55
main.go Normal file
View File

@@ -0,0 +1,55 @@
package main
import (
"os"
"github.com/rancher/steve/pkg/debug"
stevecli "github.com/rancher/steve/pkg/server/cli"
"github.com/rancher/steve/pkg/version"
"github.com/rancher/wrangler/v3/pkg/signals"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
keconfig "github.com/cnrancher/kube-explorer/internal/config"
"github.com/cnrancher/kube-explorer/internal/server"
)
var (
config stevecli.Config
debugconfig debug.Config
)
func main() {
app := cli.NewApp()
app.Name = "kube-explorer"
app.Version = version.FriendlyVersion()
app.Usage = ""
app.Flags = joinFlags(
stevecli.Flags(&config),
debug.Flags(&debugconfig),
keconfig.Flags(),
)
app.Action = run
if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
}
func run(_ *cli.Context) error {
ctx := signals.SetupSignalContext()
debugconfig.MustSetupDebug()
s, err := server.ToServer(ctx, &config, false)
if err != nil {
return err
}
return s.ListenAndServe(ctx, config.HTTPSListenPort, config.HTTPListenPort, nil)
}
func joinFlags(flags ...[]cli.Flag) []cli.Flag {
var rtn []cli.Flag
for _, flag := range flags {
rtn = append(rtn, flag...)
}
return rtn
}

View File

@@ -1,6 +1,7 @@
FROM alpine:3.13 FROM registry.suse.com/bci/bci-minimal:15.6
ARG TARGETARCH
COPY kube-explorer entrypoint.sh /usr/bin/ ARG TARGETOS
# Hack to make golang do files,dns search order ENV ARCH=${TARGETARCH:-"amd64"} OS=${TARGETOS:-"linux"}
ENV LOCALDOMAIN="" COPY entrypoint.sh /usr/bin/
COPY kube-explorer-${OS}-${ARCH} /usr/bin/kube-explorer
ENTRYPOINT ["entrypoint.sh"] ENTRYPOINT ["entrypoint.sh"]

View File

@@ -1,46 +1,81 @@
#!/bin/bash #!/bin/bash
set -e set -e
source $(dirname $0)/version source "$(dirname $0)/version"
cd "$(dirname $0)/.."
rm -rf ./bin/* ./dist/*
OS_ARCH_ARG_LINUX="amd64 arm arm64" OS_ARCH_ARG_LINUX="amd64 arm arm64"
OS_ARCH_ARG_DARWIN="amd64" OS_ARCH_ARG_DARWIN="amd64 arm64"
OS_ARCH_ARG_WINDOWS="amd64"
LD_INJECT_VALUES="-X github.com/rancher/steve/pkg/version.Version=$VERSION LD_INJECT_VALUES="-X github.com/cnrancher/kube-explorer/internal/version.Version=$VERSION
-X github.com/rancher/steve/pkg/version.GitCommit=$COMMIT" -X github.com/cnrancher/kube-explorer/internal/version.GitCommit=$COMMIT"
[ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s" [ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s"
pushd $GIT_SOURCE case "$CROSS" in
"push")
for ARCH in ${OS_ARCH_ARG_LINUX}; do
OUTPUT_BIN="bin/kube-explorer-linux-$ARCH"
echo "Building binary for linux/$ARCH..."
GOARCH=$ARCH GOOS=linux CGO_ENABLED=0 go build -tags embed \
-ldflags \
"$LD_INJECT_VALUES $LINKFLAGS" \
-o ${OUTPUT_BIN}
done
;;
"tag")
for ARCH in ${OS_ARCH_ARG_LINUX}; do
OUTPUT_BIN="bin/kube-explorer-linux-$ARCH"
echo "Building binary for linux/$ARCH..."
GOARCH=$ARCH GOOS=linux CGO_ENABLED=0 go build -tags embed \
-ldflags \
"$LD_INJECT_VALUES $LINKFLAGS" \
-o ${OUTPUT_BIN}
done
if [ -n "$CROSS" ]; then for ARCH in ${OS_ARCH_ARG_DARWIN}; do
for ARCH in ${OS_ARCH_ARG_LINUX}; do OUTPUT_BIN="bin/kube-explorer-darwin-$ARCH"
OUTPUT_BIN="bin/kube-explorer-linux-$ARCH" echo "Building binary for darwin/$ARCH..."
echo "Building binary for linux/$ARCH..." GOARCH=$ARCH GOOS=darwin CGO_ENABLED=0 go build -tags embed \
GOARCH=$ARCH GOOS=linux CGO_ENABLED=0 go build -tags embed \ -ldflags \
-ldflags \ "$LD_INJECT_VALUES" \
"$LD_INJECT_VALUES $LINKFLAGS" \ -o ${OUTPUT_BIN}
-o ${OUTPUT_BIN} done
done
for ARCH in ${OS_ARCH_ARG_DARWIN}; do for ARCH in ${OS_ARCH_ARG_WINDOWS}; do
OUTPUT_BIN="bin/kube-explorer-darwin-$ARCH" OUTPUT_BIN="bin/kube-explorer-windows-$ARCH.exe"
echo "Building binary for darwin/$ARCH..." echo "Building binary for windows/$ARCH..."
GOARCH=$ARCH GOOS=darwin CGO_ENABLED=0 go build -tags embed \ GOARCH=$ARCH GOOS=windows CGO_ENABLED=0 go build -tags embed \
-ldflags \ -ldflags \
"$LD_INJECT_VALUES" \ "$LD_INJECT_VALUES" \
-o ${OUTPUT_BIN} -o ${OUTPUT_BIN}
done done
else ;;
# only build one for current platform *)
CGO_ENABLED=0 go build -tags embed \ # only build one for current platform
-ldflags \ CGO_ENABLED=0 go build -tags embed \
"$LD_INJECT_VALUES $LINKFLAGS" \ -ldflags \
-o bin/kube-explorer "$LD_INJECT_VALUES $LINKFLAGS" \
fi -o "bin/kube-explorer-$(uname | tr '[:upper:]' '[:lower:]')-${ARCH}"
;;
esac
for f in $(ls ./bin/); do mkdir -p "./bin"
upx -o $DAPPER_SOURCE/bin/$f bin/$f mkdir -p "./dist"
for f in ./bin/*; do
filename=$(basename "$f")
if [[ $filename != *darwin* && -z "$SKIP_COMPRESS" ]]; then
if upx -o "./dist/$filename" "$f"; then
echo "UPX done for $filename!"
else
echo "UPX failed for $filename, copying original file."
cp "$f" "./dist/$filename"
fi
else
cp "$f" "./dist/$filename"
fi
done done
popd

View File

@@ -6,13 +6,8 @@ cd $(dirname $0)
[ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s" [ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s"
pushd $GIT_SOURCE
CGO_ENABLED=0 go build \ CGO_ENABLED=0 go build \
-ldflags \ -ldflags \
"$LINKFLAGS" \ "$LINKFLAGS" \
-o bin/kube-explorer -o bin/kube-explorer
mv bin/kube-explorer $DAPPER_SOURCE/bin/
popd

View File

@@ -1,18 +1,18 @@
#!/bin/bash #!/bin/bash
mkdir -p $(dirname $GIT_SOURCE) source $(dirname $0)/version
pushd $(dirname $GIT_SOURCE) cd "$(dirname $0)/.." || exit 1;
git clone --depth=1 --branch ${GIT_BRANCH} https://github.com/niusmallnan/steve.git if [[ "$(uname)" == "Darwin" ]]; then
cd steve TAR_CMD="gtar"
git reset --hard ${GIT_COMMIT} else
TAR_CMD="tar"
fi
mkdir -p pkg/ui/ui/dashboard mkdir -p internal/ui/ui/dashboard
cd pkg/ui/ui/dashboard cd internal/ui/ui/dashboard || exit 1;
curl -sL https://rancher-dashboard-ui.s3.ap-southeast-2.amazonaws.com/release-2.6-cn/kube-explorer-ui/${CATTLE_DASHBOARD_UI_VERSION}.tar.gz | tar xvzf - --strip-components=2 curl -sL https://pandaria-dashboard-ui.s3.ap-southeast-2.amazonaws.com/release-2.8-cn/kube-explorer-ui/${CATTLE_DASHBOARD_UI_VERSION}.tar.gz | $TAR_CMD xvzf - --strip-components=2
cp index.html ../index.html cp index.html ../index.html
popd
$(dirname $0)/hack_fs $GIT_SOURCE/pkg/ui/ui/

View File

@@ -2,10 +2,11 @@
set -e set -e
mkdir -p bin dist mkdir -p bin dist
if [ -e ./scripts/$1 ]; then if [ -e "./scripts/$1" ]; then
./scripts/"$@" ./scripts/"$@"
else else
exec "$@" exec "$@"
fi fi
chown -R $DAPPER_UID:$DAPPER_GID .
chown -R "$DAPPER_UID:$DAPPER_GID" .

9
scripts/github_ci Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -e
cd $(dirname $0)
./download
./validate
./build

View File

@@ -1,42 +0,0 @@
#!/bin/bash
set -ex
#
# find . -type f -name "_*"
#
function hack_files() {
for f in $(find $1 -type f -name "_*"); do
name=$(basename $f)
updir=$(dirname $f)
new_path=$updir/${name:1}
echo "move $f $new_path"
mv $f $new_path
done
}
#
# find . -type d -name "_*"
#
function hack_dirs() {
for d in $(find $1 -mindepth 1 -maxdepth 1 -type d); do
if [[ ! -d $d ]]; then
continue
fi
name=$(basename $d)
if [[ ${name:0:1} == "_" ]]; then
updir=$(dirname $d)
new_path=$updir/${name:1}
echo "move $d $new_path"
mv $d $new_path
hack_dirs $new_path
continue
fi
hack_dirs $d
done
}
pushd $1
hack_files .
hack_dirs .
popd

View File

@@ -2,17 +2,8 @@
set -e set -e
source $(dirname $0)/version source $(dirname $0)/version
cd "$(dirname $0)/.."
pushd $DAPPER_SOURCE cp dist/* package/
docker build -f package/Dockerfile -t "cnrancher/kube-explorer:$VERSION" package
if [ -f bin/kube-explorer-linux-${ARCH} ]; then
# For cross mode
cp bin/kube-explorer-linux-${ARCH} package/kube-explorer
else
# For common mode
cp bin/kube-explorer package/
fi
cd package
docker build -f Dockerfile -t niusmallnan/kube-explorer:$VERSION .
popd

42
scripts/release-note Executable file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env sh
set -e
source "$(dirname $0)/version"
cd "$(dirname $0)/.."
mkdir -p dist
TARGET_PATH="dist/release-note"
if [ -z "$(command -v release-notary)" ]; then
echo "release-notary is not found, skip generating release notes."
exit 0
fi
if [ -z "${GIT_TAG}" ]; then
echo "running this scrpit without tag, skip generating release notes."
exit 0
fi
GIT_TAG=$(echo "${GIT_TAG}" | grep -E "^v([0-9]+)\.([0-9]+)(\.[0-9]+)?(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$") || true
if [ "${GIT_TAG}" = "" ]; then
echo "git GIT_TAG is not validated, skip generating release notes."
exit 0
fi
for tag in $(git tag -l --sort=-v:refname); do
if [ "${tag}" = "${GIT_TAG}" ]; then
continue
fi
filterred=$(echo "${tag}" | grep -E "^v([0-9]+)\.([0-9]+)(\.[0-9]+)?(-rc[0-9]*)$") || true
if [ "${filterred}" = "" ]; then
echo "get real release tag ${tag}, stopping untag"
break
fi
git tag -d ${tag}
done
echo "following release notes will be published..."
release-notary publish -d 2>/dev/null | sed '1d' | sed '$d' > $TARGET_PATH
cat "$TARGET_PATH"

View File

@@ -1,7 +1,8 @@
#!/bin/bash #!/bin/bash
set -e set -e
source $(dirname $0)/version
pushd $GIT_SOURCE cd "$(dirname $0)/.."
if ! command -v golangci-lint; then if ! command -v golangci-lint; then
echo Running: go fmt echo Running: go fmt
@@ -9,13 +10,11 @@ if ! command -v golangci-lint; then
exit exit
fi fi
#echo Running: golangci-lint echo Running: golangci-lint
#golangci-lint run golangci-lint run
echo Tidying up modules echo Tidying up modules
go mod tidy go mod tidy
echo Verifying modules echo Verifying modules
go mod verify go mod verify
popd