Compare commits

...

22 Commits

Author SHA1 Message Date
Yuxing Deng
5f51b892a9 fix: the usage is not compatible with old version 2024-11-25 09:43:22 +08:00
Yuxing Deng
a434be5b81 fix: wrong branch name 2024-10-21 21:52:17 +08:00
Yuxing Deng
a7787ce013 fix: add release-v branch to push workflow 2024-10-21 21:52:17 +08:00
Yuxing Deng
b8f1ad6f9e fix: CROSS push 2024-10-21 21:45:17 +08:00
Yuxing Deng
1633838017 fix: aliyun-metadata should have registry name for image 2024-10-21 21:14:13 +08:00
Yuxing Deng
4d17a53d3e feat: use goreleaser to build binaries 2024-10-21 16:29:04 +08:00
Yuxing Deng
4c1db385fc fix: Should handle stale socket file
Added a lock file to hold make sure that it's safe to rebuild socket file
2024-10-10 21:21:35 +08:00
Yuxing Deng
8d1433b07d feat: Support to serve http via socket 2024-10-09 22:53:08 +08:00
Yuxing Deng
db2728f0ed fix(docs): enhance Readme 2024-10-09 09:12:48 +08:00
Yuxing Deng
4f18ac4ae8 feat(ui): bump ui version to v2.9.2 2024-09-27 17:37:50 +08:00
Yuxing Deng
faee269cc1 feat(deps): bump golang & steve version 2024-09-27 17:37:50 +08:00
Yuxing Deng
9ce631d30f fix: use local version package 2024-09-26 10:29:40 +08:00
Yuxing Deng
2d512c0a72 fix: version is not parsed into build info 2024-09-26 09:49:16 +08:00
Yuxing Deng
5c987cd193 fix: Bump steve to fix the crd issue 2024-08-07 09:08:22 +08:00
Yuxing Deng
85925bbac7 fix: no need to new workflow file for release branch 2024-07-30 10:34:57 +08:00
Yuxing Deng
f435a24814 fix: Rename release branch ci name 2024-07-30 10:32:06 +08:00
Yuxing Deng
fb1f38e1ef feat: Add release branch ci 2024-07-30 10:31:00 +08:00
Yuxing Deng
67923822f5 fix: Container doesn't stop with signal 2024-07-29 17:24:10 +08:00
Yuxing Deng
1540341550 docs: Add docs for path prefix deploy example 2024-07-29 15:07:34 +08:00
Yuxing Deng
a5e53f2b17 feat: Support for nginx ingress path prefix
And validate should failed if git tree is dirty
2024-07-29 15:07:34 +08:00
Yuxing Deng
8f069c3b38 feat: Full support for proxy deploy
The index page will be handled correctly if the `XFF` and `X-API-URL-PREFIX` are set properly.
2024-07-25 17:32:01 +08:00
Yuxing Deng
568eda3e52 fix: Index content-type missing problem 2024-07-25 17:32:01 +08:00
29 changed files with 651 additions and 149 deletions

View File

@ -16,6 +16,4 @@ jobs:
- name: Commitsar check
uses: aevea/commitsar@v0.20.2
- name: Build to test
env:
SKIP_COMPRESS: "true"
run: make ci

View File

@ -3,9 +3,14 @@ name: Push to Master
on:
push:
branches:
- release/v*
- main
- "release/v*"
tags:
- 'v*.*.*' # Matches any tag that starts with 'v' and follows semantic versioning
- "v*.*.*" # Matches any tag that starts with 'v' and follows semantic versioning
env:
ALIYUN_REGISTRY: ${{ vars.ALIYUN_REGISTRY || '' }}
jobs:
build-and-deploy:
@ -15,67 +20,73 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: CI
env:
SKIP_PACKAGE: "true"
run: make ci
- name: Prepare for packaging image
run: cp dist/* package/
# aliyun image to test the docker build is ok
- name: Login to Aliyun ACR
if: ${{ vars.ALIYUN_REGISTRY != '' }}
uses: docker/login-action@v3
with:
registry: registry.cn-shenzhen.aliyuncs.com
registry: ${{ env.ALIYUN_REGISTRY }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_TOKEN }}
if: ${{ vars.ALIYUN == 'true' }}
- name: Aliyun image docker meta
if: ${{ vars.ALIYUN_REGISTRY != '' }}
id: aliyun-meta
uses: docker/metadata-action@v5
with:
images: ${{ env.ALIYUN_REGISTRY }}/${{ vars.REPO || 'cnrancher' }}/${{ vars.IMAGE || 'kube-explorer' }}
tags: |
type=ref,event=tag
type=ref,event=branch,suffix=-head
- name: Build to Aliyun
if: ${{ vars.ALIYUN_REGISTRY != '' }}
uses: docker/build-push-action@v6
with:
labels: ${{ steps.aliyun-meta.outputs.labels }}
tags: "${{ steps.aliyun-meta.outputs.tags }}"
context: package
push: true
# docker hub multi-arch image
- 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
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ vars.REPO || 'cnrancher' }}/${{ vars.IMAGE || 'kube-explorer' }}
tags: |
type=ref,event=tag
type=ref,event=branch,suffix=-head
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
- 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 }}"
labels: ${{ steps.meta.outputs.labels }}
tags: "${{ steps.meta.outputs.tags }}"
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: Make release note
if: startsWith(github.ref, 'refs/tags/')
run: |
make release-note
- 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
draft: true

4
.gitignore vendored
View File

@ -23,3 +23,7 @@
/.vscode
/vendor
/internal/ui/ui/
**/Dockerfile.dapper*
!**/Dockerfile.dapper
dist/

75
.goreleaser.yaml Normal file
View File

@ -0,0 +1,75 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 2
dist: bin
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
builds:
- id: prod
env:
- CGO_ENABLED=0
targets:
- "darwin_amd64"
- "darwin_arm64"
- "linux_amd64"
- "linux_arm64"
- "linux_arm"
- "windows_amd64"
flags:
- -tags=embed
binary: '{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}'
ldflags: |
{{ if ne .Os "darwin" }}
-extldflags -static -s
{{ else }}
-s -w
{{ end }}
-X github.com/cnrancher/kube-explorer/internal/version.Version={{ .Env.VERSION }}
-X github.com/cnrancher/kube-explorer/internal/version.GitCommit={{ .Env.COMMIT }}
-X github.com/cnrancher/kube-explorer/internal/config.APIUIVersion={{ .Env.CATTLE_API_UI_VERSION }}
no_unique_dist_dir: true
- id: dev
env:
- CGO_ENABLED=0
targets:
- "linux_amd64"
- "linux_arm64"
flags:
- -tags=embed
binary: '{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}'
ldflags: |
{{ if ne .Os "darwin" }}
-extldflags -static -s
{{ else }}
-s -w
{{ end }}
-X github.com/cnrancher/kube-explorer/internal/version.Version={{ .Env.VERSION }}
-X github.com/cnrancher/kube-explorer/internal/version.GitCommit={{ .Env.COMMIT }}
-X github.com/cnrancher/kube-explorer/internal/config.APIUIVersion={{ .Env.CATTLE_API_UI_VERSION }}
no_unique_dist_dir: true
upx:
- compress: "5"
ids:
- prod
enabled: true
goos:
- linux
- windows
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"

View File

@ -1,6 +1,7 @@
FROM goreleaser/goreleaser:v2.3.2 as goreleaser
FROM aevea/release-notary:0.9.2 as tools
FROM registry.suse.com/bci/golang:1.22
FROM registry.suse.com/bci/golang:1.23
ARG PROXY
ARG GOPROXY
ARG DAPPER_HOST_ARCH
@ -8,21 +9,21 @@ ENV HOST_ARCH=${DAPPER_HOST_ARCH} ARCH=${DAPPER_HOST_ARCH}
ENV https_proxy=${PROXY} \
http_proxy=${PROXY}
RUN zypper -n install ca-certificates git-core wget curl unzip tar vim less file xz
RUN zypper install -y -f docker
RUN zypper -n install ca-certificates git-core wget curl unzip tar vim less file xz cosign docker
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/
RUN if [ "${ARCH}" == "amd64" ]; then \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.54.2; \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.61.0; \
fi
COPY --from=goreleaser /usr/bin/goreleaser /usr/bin/goreleaser
COPY --from=tools /app/release-notary /usr/local/bin/
ENV CATTLE_DASHBOARD_UI_VERSION="v2.8.0-kube-explorer-ui-rc3"
ENV CATTLE_DASHBOARD_UI_VERSION="v2.9.2-kube-explorer-ui-rc1"
ENV CATTLE_API_UI_VERSION="1.1.11"
ENV DAPPER_ENV REPO TAG DRONE_TAG CROSS GOPROXY SKIP_COMPRESS GITHUB_REPOSITORY GITHUB_TOKEN
ENV DAPPER_ENV REPO TAG DRONE_TAG CROSS GOPROXY GITHUB_TOKEN GITHUB_REF GITHUB_REF_NAME BUILD_TARGET SKIP_PACKAGE
ENV DAPPER_SOURCE /go/src/github.com/cnrancher/kube-explorer
ENV DAPPER_OUTPUT ./bin ./dist
ENV DAPPER_DOCKER_SOCKET true

View File

@ -10,7 +10,7 @@ Please download the binary from the [release page](https://github.com/cnrancher/
To run an HTTP only server:
```
```bash
./kube-explorer --kubeconfig=xxxx --http-listen-port=9898 --https-listen-port=0
```
@ -22,20 +22,47 @@ Then, open the browser to visit http://x.x.x.x:9898 .
To debug on an AMD64 Linux host:
```
```bash
make dev
# $basedir=/opt/ui/dist/
# prepare the file trees like this
# $basedir/dashboard/
# $basedir/api-ui/
# $basedir/index.html
# good to go!
./kube-explorer --debug --ui-path /opt/ui/dist/ --http-listen-port=9898 --https-listen-port=0
./bin/kube-explorer --debug --ui-path /opt/ui/dist/ --http-listen-port=9898 --https-listen-port=0
```
To build all cross-platform binaries:
```bash
CROSS=tag make
```
CROSS=1 make
```
## Supported features
- Specified system default registry for shell image, e.g. `--system-default-registry`
- Specified shell image name, e.g. `--pod-image`
- Deployed behind proxy
- [Behind ingress with dns name](./deploy/kubectl/README.md)
- [Behind ingress with dns name and path prefix](./deploy/kubectl/path-prefix/Readme.md)
- Base auth via ingress such as [nginx](./deploy/kubectl/nginx-auth/README.md), [traefik-v1](./deploy/kubectl/traefik-v1-auth/README.md) and [traefik-v2](./deploy/kubectl/traefik-v2-auth/README.md)
## Support Matrix
Currently, there are several major versions under maintenance, each tailored to different Kubernetes version ranges due to the use of varying steve and client-go versions.
| Major | Target Rancher Branch | K8s version range |
| ----- | --------------------- | ----------------- |
| v0.4 | v2.8.x | >= 1.25 <= 1.28 |
| v0.5 | v2.9.x | >= 1.27 <= 1.30 |
Please use the proper kube-explorer version for your k8s setup.
## Related Projects
- kube-explorer ui([https://github.com/cnrancher/kube-explorer-ui](https://github.com/cnrancher/kube-explorer-ui))
- autok3s([https://github.com/cnrancher/autok3s](https://github.com/cnrancher/autok3s))
- api-ui([https://github.com/rancher/api-ui](https://github.com/rancher/api-ui))

View File

@ -0,0 +1,34 @@
# Deploy kube-explorer behind proxy with path prefix
> Supported since v0.5.0
The kube-explorer dashboard can be exposed behind a proxy and path prefix like `http://your-domain.com/kube-explorer`.
The deployment examples in this folder are:
- `nginx ingress`
- `traefik ingress`
## Serve with ingress
When serving with nginx/traefik ingress controller, the template ingress file needs to be modified. In the `*.tpl` file, you can spot the missing hostname like:
```yaml
spec:
rules:
- host: "${MY_IP}.sslip.io" # Replace with your actual domain
```
Replace your ip to `${MY_UP}`, this will use the [sslip.io](https://sslip.io/) dns service to resolve the hostname to the ingress ip address.
For the traefik ingress, it is using `v2` version of the traefik ingress schema which use middlewares to modify the proxy request. Both `stripPrefix` and `headers` are used.
For the nginx ingress, the annotations `nginx.ingress.kubernetes.io/x-forwarded-prefix` and `nginx.ingress.kubernetes.io/rewrite-target` are used to strip prefix and to add proxy request header.
## Serve with self-hosted proxy
If serving the kube-explorer with self-hosted proxy, following modifications are required when proxying:
- Rewrite the proxy request to strip the path prefix like `rewrite "(?i)/kube-explorer(/|$)(.*)" /$2 break;` in nginx configuration.
- Add header `X-API-URL-Prefix` or `X-Forwarded-Prefix` with the path prefix when proxying request like `proxy_set_header X-Forwarded-Prefix "/kube-explorer";` in nginx configuration.
Then kube-explorer will response the index.html with modified content with path prefix to the browser.

View File

@ -0,0 +1,24 @@
# Note: please replace the host first
# To use sslip.io: https://sslip.io/
# To get your public IP: curl ipinfo.io/ip
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/x-forwarded-prefix: "/kube-explorer"
nginx.ingress.kubernetes.io/rewrite-target: /$2
name: kube-explorer-ingress
namespace: kube-system
spec:
rules:
- host: "${MY_IP}.sslip.io" # Replace with your actual domain
http:
paths:
- backend:
service:
name: kube-explorer
port:
name: http
path: /kube-explorer(/|$)(.*)
pathType: ImplementationSpecific

View File

@ -0,0 +1,42 @@
# Note: please replace the host first
# To use sslip.io: https://sslip.io/
# To get your public IP: curl ipinfo.io/ip
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kube-explorer-ingress
namespace: kube-system
annotations:
traefik.ingress.kubernetes.io/router.middlewares: kube-system-prefix@kubernetescrd,kube-system-add-header@kubernetescrd
spec:
rules:
- host: "${MY_IP}.sslip.io" # Replace with your actual domain
http:
paths:
- path: /kube-explorer
pathType: Prefix
backend:
service:
name: kube-explorer
port:
name: http
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: prefix
namespace: kube-system
spec:
stripPrefix:
prefixes:
- /kube-explorer
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: add-header
namespace: kube-system
spec:
headers:
customRequestHeaders:
X-Forwarded-Prefix: "/kube-explorer" # Adds

5
go.mod
View File

@ -5,9 +5,11 @@ go 1.22.0
replace k8s.io/client-go => k8s.io/client-go v0.30.1
require (
github.com/Microsoft/go-winio v0.6.2
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/dynamiclistener v0.6.0-rc2
github.com/rancher/steve v0.0.0-20240911190153-79304d93b49b
github.com/rancher/wrangler/v3 v3.0.0
github.com/sirupsen/logrus v1.9.3
github.com/urfave/cli v1.22.15
@ -62,7 +64,6 @@ require (
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

6
go.sum
View File

@ -601,6 +601,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -1029,8 +1031,8 @@ github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 h1:AlRMRs5mHJcdiK83
github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9/go.mod h1:dyjfXBsNiroPWOdUZe7diUOUSLf6HQ/r2kEpwH/8zas=
github.com/rancher/remotedialer v0.3.2 h1:kstZbRwPS5gPWpGg8VjEHT2poHtArs+Fc317YM8JCzU=
github.com/rancher/remotedialer v0.3.2/go.mod h1:Ys004RpJuTLSm+k4aYUCoFiOOad37ubYev3TkOFg/5w=
github.com/rancher/steve v0.0.0-20240709130809-47871606146c h1:PIaN0/KUyGcqEcT6GyjUidld2lgGkGxS4dmC3Je3dFs=
github.com/rancher/steve v0.0.0-20240709130809-47871606146c/go.mod h1:Za4nSt0V6kIHRfUo6jTXKkv6ABMMCHINA8EzhzygCfk=
github.com/rancher/steve v0.0.0-20240911190153-79304d93b49b h1:2DhkNKDgKPI2PcJRGacpJ9dX9SWgKuhwbz5GlpHS1No=
github.com/rancher/steve v0.0.0-20240911190153-79304d93b49b/go.mod h1:cXF0TFURkjN98p/0nt7Y8F1IEtT1nZTSYrQu6HQqL14=
github.com/rancher/wrangler/v3 v3.0.0 h1:IHHCA+vrghJDPxjtLk4fmeSCFhNe9fFzLFj3m2B0YpA=
github.com/rancher/wrangler/v3 v3.0.0/go.mod h1:Dfckuuq7MJk2JWVBDywRlZXMxEyPxHy4XqGrPEzu5Eg=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=

View File

@ -7,8 +7,8 @@ import (
var InsecureSkipTLSVerify bool
var SystemDefaultRegistry string
var APIUIVersion = "1.1.11"
var ShellPodImage string
var BindAddress string
func Flags() []cli.Flag {
return []cli.Flag{
@ -31,5 +31,10 @@ func Flags() []cli.Flag {
Destination: &APIUIVersion,
Value: APIUIVersion,
},
cli.StringFlag{
Name: "bind-address",
Destination: &BindAddress,
Usage: `Bind address with url format. The supported schemes are unix, tcp and namedpipe, e.g. unix:///path/to/kube-explorer.sock or namedpipe:/\.\pipe\kube-explorer`,
},
}
}

View File

@ -6,6 +6,7 @@ import (
"strings"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/apiserver/pkg/urlbuilder"
steveauth "github.com/rancher/steve/pkg/auth"
"github.com/rancher/steve/pkg/schema"
"github.com/rancher/steve/pkg/server"
@ -61,7 +62,11 @@ func ToServer(ctx context.Context, c *cli.Config, sqlCache bool) (*server.Server
SQLCache: sqlCache,
// router needs to hack here
Router: func(h router.Handlers) http.Handler {
return rewriteLocalCluster(router.Routes(h))
return handleProxyHeader(
rewriteLocalCluster(
router.Routes(h),
),
)
},
})
if err != nil {
@ -99,3 +104,12 @@ func rewriteLocalCluster(next http.Handler) http.Handler {
next.ServeHTTP(rw, req)
})
}
func handleProxyHeader(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if value := req.Header.Get("X-Forwarded-Prefix"); value != "" {
req.Header.Set(urlbuilder.PrefixHeader, value)
}
next.ServeHTTP(rw, req)
})
}

View File

@ -0,0 +1,52 @@
package server
import (
"context"
"log"
"net"
"net/http"
"github.com/cnrancher/kube-explorer/internal/config"
dynamicserver "github.com/rancher/dynamiclistener/server"
"github.com/rancher/steve/pkg/server"
"github.com/sirupsen/logrus"
)
func Serve(ctx context.Context, server *server.Server) error {
listener, ipOrPath, err := ensureListener(ctx)
if err != nil {
return err
}
if listener != nil {
defer listener.Close()
return serveSocket(ctx, ipOrPath, listener, server)
}
return server.ListenAndServe(ctx, config.Steve.HTTPSListenPort, config.Steve.HTTPListenPort, &dynamicserver.ListenOpts{
BindHost: ipOrPath,
})
}
func serveSocket(ctx context.Context, socketPath string, listener net.Listener, handler http.Handler) error {
logger := logrus.StandardLogger()
errorLog := log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags)
socketServer := &http.Server{
Handler: handler,
ErrorLog: errorLog,
BaseContext: func(_ net.Listener) context.Context {
return ctx
},
}
go func() {
logrus.Infof("Listening on %s", socketPath)
err := socketServer.Serve(listener)
if err != http.ErrServerClosed && err != nil {
logrus.Fatalf("https server failed: %v", err)
}
}()
go func() {
<-ctx.Done()
_ = socketServer.Shutdown(context.Background())
}()
<-ctx.Done()
return ctx.Err()
}

View File

@ -0,0 +1,99 @@
//go:build unix
// +build unix
package server
import (
"context"
"errors"
"fmt"
"net"
"net/url"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/cnrancher/kube-explorer/internal/config"
"github.com/sirupsen/logrus"
)
var _ net.Listener = &closerListener{}
type closerListener struct {
listener net.Listener
lockFile *os.File
}
func (l *closerListener) Accept() (net.Conn, error) {
return l.listener.Accept()
}
func (l *closerListener) Close() error {
return errors.Join(
l.listener.Close(),
l.lockFile.Close(),
os.RemoveAll(l.lockFile.Name()),
)
}
func (l *closerListener) Addr() net.Addr {
return l.listener.Addr()
}
func ensureListener(ctx context.Context) (net.Listener, string, error) {
if config.BindAddress == "" {
return nil, "", nil
}
u, err := url.Parse(config.BindAddress)
if err != nil {
return nil, "", err
}
switch u.Scheme {
case "":
return nil, config.BindAddress, nil
case "tcp":
return nil, u.Host, nil
case "unix":
listener, err := createCloserListener(ctx, u.Path)
if err != nil {
return nil, "", err
}
return listener, u.Path, err
default:
return nil, "", fmt.Errorf("Unsupported scheme %s, only tcp and unix are supported in UNIX OS", u.Scheme)
}
}
func createCloserListener(ctx context.Context, socketPath string) (net.Listener, error) {
lockFilePath := getLockFileName(socketPath)
lockFile, err := os.OpenFile(lockFilePath, os.O_RDONLY|os.O_CREATE, 0600)
if err != nil {
return nil, err
}
lockErr := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if lockErr != nil {
return nil, fmt.Errorf("Socket file %s is in use, exiting", socketPath)
}
if _, err := os.Stat(socketPath); err == nil {
logrus.Infof("Removing stale socket file %s", socketPath)
_ = os.Remove(socketPath)
}
var lc net.ListenConfig
listener, err := lc.Listen(ctx, "unix", socketPath)
if err != nil {
return nil, err
}
return &closerListener{
listener: listener,
lockFile: lockFile,
}, nil
}
func getLockFileName(socketPath string) string {
return strings.TrimSuffix(socketPath, filepath.Ext(socketPath)) + ".lock"
}

View File

@ -0,0 +1,35 @@
//go:build windows
// +build windows
package server
import (
"context"
"fmt"
"net"
"net/url"
"github.com/Microsoft/go-winio"
"github.com/cnrancher/kube-explorer/internal/config"
)
func ensureListener(_ context.Context) (net.Listener, string, error) {
if config.BindAddress == "" {
return nil, "", nil
}
u, err := url.Parse(config.BindAddress)
if err != nil {
return nil, "", err
}
switch u.Scheme {
case "":
return nil, config.BindAddress, nil
case "tcp":
return nil, u.Host, nil
case "namedpipe":
listener, err := winio.ListenPipe(u.Path, nil)
return listener, u.Path, err
default:
return nil, "", fmt.Errorf("Unsupported scheme %s, only tcp and namedpipe are supported in windows", u.Scheme)
}
}

View File

@ -132,7 +132,6 @@ func (h *Handler) IndexFile() http.Handler {
http.NotFoundHandler().ServeHTTP(w, r)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(rtn)
}))
}

116
internal/ui/proxy.go Normal file
View File

@ -0,0 +1,116 @@
package ui
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"net/http"
"strconv"
"github.com/rancher/apiserver/pkg/urlbuilder"
"k8s.io/apimachinery/pkg/util/proxy"
)
type RoundTripFunc func(*http.Request) (*http.Response, error)
func (r RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return r(req)
}
func proxyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
scheme := urlbuilder.GetScheme(r)
host := urlbuilder.GetHost(r, scheme)
pathPrepend := r.Header.Get(urlbuilder.PrefixHeader)
if scheme == r.URL.Scheme && host == r.URL.Host && pathPrepend == "" {
next.ServeHTTP(w, r)
return
}
proxyRoundtrip := proxy.Transport{
Scheme: scheme,
Host: host,
PathPrepend: pathPrepend,
RoundTripper: RoundTripFunc(func(r *http.Request) (*http.Response, error) {
rw := &dummyResponseWriter{
next: w,
header: make(http.Header),
}
next.ServeHTTP(rw, r)
return rw.getResponse(r), nil
}),
}
//proxyRoundtripper will write the response in RoundTrip func
resp, _ := proxyRoundtrip.RoundTrip(r)
responseToWriter(resp, w)
})
}
var _ http.ResponseWriter = &dummyResponseWriter{}
var _ http.Hijacker = &dummyResponseWriter{}
type dummyResponseWriter struct {
next http.ResponseWriter
header http.Header
body bytes.Buffer
statusCode int
}
// Hijack implements http.Hijacker.
func (drw *dummyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if h, ok := drw.next.(http.Hijacker); ok {
return h.Hijack()
}
return nil, nil, fmt.Errorf("")
}
// Header implements the http.ResponseWriter interface.
func (drw *dummyResponseWriter) Header() http.Header {
return drw.header
}
// Write implements the http.ResponseWriter interface.
func (drw *dummyResponseWriter) Write(b []byte) (int, error) {
return drw.body.Write(b)
}
// WriteHeader implements the http.ResponseWriter interface.
func (drw *dummyResponseWriter) WriteHeader(statusCode int) {
drw.statusCode = statusCode
}
// GetStatusCode returns the status code written to the response.
func (drw *dummyResponseWriter) GetStatusCode() int {
if drw.statusCode == 0 {
return 200
}
return drw.statusCode
}
func (drw *dummyResponseWriter) getResponse(req *http.Request) *http.Response {
return &http.Response{
Status: strconv.Itoa(drw.GetStatusCode()),
StatusCode: drw.GetStatusCode(),
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Request: req,
Header: drw.header,
Body: io.NopCloser(&drw.body),
}
}
func responseToWriter(resp *http.Response, writer http.ResponseWriter) {
for k, v := range resp.Header {
writer.Header()[k] = v
}
if resp.StatusCode != http.StatusOK {
writer.WriteHeader(resp.StatusCode)
}
_, _ = io.Copy(writer, resp.Body)
}

View File

@ -27,5 +27,5 @@ func New(opt *Options) (http.Handler, APIUI) {
http.Redirect(rw, req, url, http.StatusFound)
})
return router, apiUI(opt)
return proxyMiddleware(router), apiUI(opt)
}

View File

@ -3,9 +3,9 @@ package main
import (
"os"
"github.com/cnrancher/kube-explorer/internal/version"
"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"
@ -38,7 +38,7 @@ func run(_ *cli.Context) error {
if err != nil {
return err
}
return s.ListenAndServe(ctx, keconfig.Steve.HTTPSListenPort, keconfig.Steve.HTTPListenPort, nil)
return server.Serve(ctx, s)
}
func joinFlags(flags ...[]cli.Flag) []cli.Flag {

View File

@ -1,7 +1,7 @@
FROM registry.suse.com/bci/bci-minimal:15.6
FROM registry.suse.com/bci/bci-base:15.6
ARG TARGETARCH
ARG TARGETOS
ENV ARCH=${TARGETARCH:-"amd64"} OS=${TARGETOS:-"linux"}
COPY entrypoint.sh /usr/bin/
RUN zypper install -y catatonit
COPY kube-explorer-${OS}-${ARCH} /usr/bin/kube-explorer
ENTRYPOINT ["entrypoint.sh"]
ENTRYPOINT [ "/usr/bin/catatonit", "--", "kube-explorer" ]

View File

@ -1,3 +0,0 @@
#!/bin/sh
kube-explorer "${@}"

View File

@ -6,77 +6,33 @@ cd "$(dirname $0)/.."
rm -rf ./bin/* ./dist/*
OS_ARCH_ARG_LINUX="amd64 arm arm64"
OS_ARCH_ARG_DARWIN="amd64 arm64"
OS_ARCH_ARG_WINDOWS="amd64"
BUILD_TARGET="${BUILD_TARGET:-dev}"
CROSS=${CROSS:-}
LD_INJECT_VALUES="-X github.com/cnrancher/kube-explorer/internal/version.Version=$VERSION
-X github.com/cnrancher/kube-explorer/internal/version.GitCommit=$COMMIT
-X github.com/cnrancher/kube-explorer/internal/config.APIUIVersion=$CATTLE_API_UI_VERSION"
if [[ ${GITHUB_REF} == refs/tags/* ]]; then
CROSS=tag
elif [ -n "${GITHUB_REF}" ]; then
CROSS=push
fi
[ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s"
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
;;
case "$CROSS" in
"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
for ARCH in ${OS_ARCH_ARG_DARWIN}; do
OUTPUT_BIN="bin/kube-explorer-darwin-$ARCH"
echo "Building binary for darwin/$ARCH..."
GOARCH=$ARCH GOOS=darwin CGO_ENABLED=0 go build -tags embed \
-ldflags \
"$LD_INJECT_VALUES" \
-o ${OUTPUT_BIN}
done
for ARCH in ${OS_ARCH_ARG_WINDOWS}; do
OUTPUT_BIN="bin/kube-explorer-windows-$ARCH.exe"
echo "Building binary for windows/$ARCH..."
GOARCH=$ARCH GOOS=windows CGO_ENABLED=0 go build -tags embed \
-ldflags \
"$LD_INJECT_VALUES" \
-o ${OUTPUT_BIN}
done
;;
BUILD_TARGET="prod"
;;
"push")
;;
*)
# only build one for current platform
CGO_ENABLED=0 go build -tags embed \
-ldflags \
"$LD_INJECT_VALUES $LINKFLAGS" \
-o "bin/kube-explorer-$(uname | tr '[:upper:]' '[:lower:]')-${ARCH}"
;;
BUILD_ARG="${BUILD_ARG} --single-target"
;;
esac
BUILD_ARG="${BUILD_ARG:-} --skip validate --id ${BUILD_TARGET}"
mkdir -p "./bin"
# upx is handled by goreleaser
VERSION=${VERSION} COMMIT=${COMMIT} goreleaser build $BUILD_ARG
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
cp -r bin/kube-explorer-* dist/

View File

@ -6,4 +6,6 @@ cd $(dirname $0)
./download
./validate
./build
./package
if [ -z "${SKIP_PACKAGE}" ]; then
./package
fi

View File

@ -1,8 +1,10 @@
#!/bin/bash
set -e
cd $(dirname $0)
./download
cd "$(dirname $0)/.."
./scripts/download
source $(dirname $0)/version
[ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s"

View File

@ -14,7 +14,7 @@ rm -rf internal/ui/ui/*
mkdir -p internal/ui/ui/dashboard
cd internal/ui/ui/dashboard || exit 1;
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
curl -sL https://pandaria-dashboard-ui.s3.ap-southeast-2.amazonaws.com/release-2.9-cn/kube-explorer-ui/${CATTLE_DASHBOARD_UI_VERSION}.tar.gz | $TAR_CMD xvzf - --strip-components=2
cp index.html ../index.html
mkdir ../api-ui

View File

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

View File

@ -18,3 +18,14 @@ go mod tidy
echo Verifying modules
go mod verify
dirty_files="$(git status --porcelain --untracked-files=no)"
if [ -n "$dirty_files" ]; then
echo "Encountered dirty repo! Aborting."
echo "If you're seeing this, it means there are uncommitted changes in the repo."
echo "If you're seeing this in CI, it probably means that your Go modules aren't tidy, or more generally that running"
echo "validation would result in changes to the repo. Make sure you're up to date with the upstream branch and run"
echo "'go mod tidy' and commit the changes, if any. The offending changed files are as follows:"
echo "$dirty_files"
exit 1
fi

View File

@ -1,11 +1,15 @@
#!/bin/bash
if [[ ${GITHUB_REF} == refs/tags/* ]]; then
GIT_TAG=${GIT_TAG:-${GITHUB_REF_NAME}}
fi
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
DIRTY="-dirty"
fi
COMMIT=$(git rev-parse --short HEAD)
GIT_TAG=${DRONE_TAG:-$(git tag -l --contains HEAD | head -n 1)}
GIT_TAG=${GIT_TAG:-$(git tag -l --contains HEAD | head -n 1)}
if [[ -z "$DIRTY" && -n "$GIT_TAG" ]]; then
VERSION=$GIT_TAG