Compare commits
16 Commits
master
...
helm-chart
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
253e2a5c33 | ||
|
|
3798bcd452 | ||
|
|
4efc7c9fbe | ||
|
|
3abf00b7c1 | ||
|
|
31ee299312 | ||
|
|
3187fcc173 | ||
|
|
f0ab0b9856 | ||
|
|
7e703750e8 | ||
|
|
88db498bcd | ||
|
|
f290167267 | ||
|
|
0a06cd8a1b | ||
|
|
0a21a58582 | ||
|
|
de786c4b84 | ||
|
|
dad35f7389 | ||
|
|
7dcb5d9d3b | ||
|
|
14d48c9267 |
30
.github/workflows/build-multiarch.yaml
vendored
30
.github/workflows/build-multiarch.yaml
vendored
@@ -17,16 +17,16 @@ jobs:
|
|||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Login to Aliyun
|
- name: Login to HUAWEICLOUD
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: registry.cn-beijing.aliyuncs.com
|
registry: swr.cn-southwest-2.myhuaweicloud.com
|
||||||
username: ${{ secrets.ALIYUNCS_USERNAME }}
|
username: ${{ secrets.HUAWEICLOUD_USERNAME }}
|
||||||
password: ${{ secrets.ALIYUNCS_PASSWORD }}
|
password: ${{ secrets.HUAWEICLOUD_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to DOCKER
|
- name: Login to DOCKER
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -56,17 +56,18 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
if: steps.chose_registry.outputs.env == 'prod'
|
if: steps.chose_registry.outputs.env == 'prod'
|
||||||
with:
|
with:
|
||||||
|
context: ${{ github.workspace }}
|
||||||
file: build/ks-apiserver/Dockerfile
|
file: build/ks-apiserver/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
docker.io/kubesphere/ks-apiserver:${{ steps.chose_registry.outputs.tag }}
|
docker.io/kubesphere/ks-apiserver:${{ steps.chose_registry.outputs.tag }}
|
||||||
registry.cn-beijing.aliyuncs.com/kubesphereio/ks-apiserver:${{ steps.chose_registry.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Build and push ks-apiserver dev images
|
- name: Build and push ks-apiserver dev images
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
if: steps.chose_registry.outputs.env == 'dev'
|
if: steps.chose_registry.outputs.env == 'dev'
|
||||||
with:
|
with:
|
||||||
|
context: ${{ github.workspace }}
|
||||||
file: build/ks-apiserver/Dockerfile
|
file: build/ks-apiserver/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
@@ -77,19 +78,34 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
if: steps.chose_registry.outputs.env == 'prod'
|
if: steps.chose_registry.outputs.env == 'prod'
|
||||||
with:
|
with:
|
||||||
|
context: ${{ github.workspace }}
|
||||||
file: build/ks-controller-manager/Dockerfile
|
file: build/ks-controller-manager/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
docker.io/kubesphere/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
docker.io/kubesphere/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
||||||
registry.cn-beijing.aliyuncs.com/kubesphereio/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Build and push ks-controller-manager dev images
|
- name: Build and push ks-controller-manager dev images
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
if: steps.chose_registry.outputs.env == 'dev'
|
if: steps.chose_registry.outputs.env == 'dev'
|
||||||
with:
|
with:
|
||||||
|
context: ${{ github.workspace }}
|
||||||
file: build/ks-controller-manager/Dockerfile
|
file: build/ks-controller-manager/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
docker.io/kubespheredev/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
docker.io/kubespheredev/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Sync ks images to HUAWEICLOUD
|
||||||
|
if: steps.chose_registry.outputs.env == 'prod'
|
||||||
|
run: |
|
||||||
|
# apiserver
|
||||||
|
docker pull docker.io/kubesphere/ks-apiserver:${{ steps.chose_registry.outputs.tag }}
|
||||||
|
docker tag docker.io/kubesphere/ks-apiserver:${{ steps.chose_registry.outputs.tag }} swr.cn-southwest-2.myhuaweicloud.com/ks/kubesphere/ks-apiserver:${{ steps.chose_registry.outputs.tag }}
|
||||||
|
docker push swr.cn-southwest-2.myhuaweicloud.com/ks/kubesphere/ks-apiserver:${{ steps.chose_registry.outputs.tag }}
|
||||||
|
# controller-manager
|
||||||
|
docker pull docker.io/kubesphere/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
||||||
|
docker tag docker.io/kubesphere/ks-controller-manager:${{ steps.chose_registry.outputs.tag }} swr.cn-southwest-2.myhuaweicloud.com/ks/kubesphere/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
||||||
|
docker push swr.cn-southwest-2.myhuaweicloud.com/ks/kubesphere/ks-controller-manager:${{ steps.chose_registry.outputs.tag }}
|
||||||
|
|
||||||
|
|||||||
56
.github/workflows/sync-helm-chart.yaml
vendored
56
.github/workflows/sync-helm-chart.yaml
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
name: SyncHelmChart
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -5,36 +7,64 @@ on:
|
|||||||
- 'config/ks-core/**'
|
- 'config/ks-core/**'
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
|
- 'release-*'
|
||||||
|
tags:
|
||||||
|
- 'helm-chart-*'
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-chart:
|
sync-chart:
|
||||||
runs-on: self-runner-kubesphere
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Check out kubesphere/kubesphere
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
path: kubesphere
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check out kubesphere/helm-charts
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: ks-ci-bot/helm-charts
|
||||||
|
token: ${{ secrets.CIBOT_ACCESS_TOKEN }}
|
||||||
|
path: helm-charts
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup SSH
|
- name: Setup SSH
|
||||||
uses: MrSquaare/ssh-setup-action@v2
|
uses: MrSquaare/ssh-setup-action@v3
|
||||||
with:
|
with:
|
||||||
|
working-directory: helm-charts
|
||||||
host: github.com
|
host: github.com
|
||||||
private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
|
||||||
- run: |
|
- name: Sync charts
|
||||||
|
run: |
|
||||||
|
cd helm-charts
|
||||||
git config --global user.email "ci-bot@kubesphere.io"
|
git config --global user.email "ci-bot@kubesphere.io"
|
||||||
git config --global user.name "ks-ci-bot"
|
git config --global user.name "ks-ci-bot"
|
||||||
git clone git@github.com:kubesphere/helm-charts.git
|
git remote add ks git@github.com:kubesphere/helm-charts.git
|
||||||
rm -rf helm-charts/src/test/ks-core
|
git fetch ks master
|
||||||
cp -r config/ks-core helm-charts/src/test/
|
git checkout -b sync/ks-core/${GITHUB_REF#refs/*/} ks/master
|
||||||
cd helm-charts/
|
rm -rf src/test/ks-core
|
||||||
|
cp -r ../kubesphere/config/ks-core src/test/
|
||||||
|
if [[ ${GITHUB_REF#refs/*/} =~ ^helm-chart-[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
rm -rf src/main/ks-core
|
||||||
|
cp -r ../kubesphere/config/ks-core src/main/
|
||||||
|
fi
|
||||||
git add .
|
git add .
|
||||||
git commit -m "update ks-core helm chart"
|
git commit -m "update ks-core helm chart"
|
||||||
git push origin master:sync/ks-core --force
|
git push origin sync/ks-core/${GITHUB_REF#refs/*/} --force
|
||||||
|
|
||||||
- env:
|
- name: Create Pull Request
|
||||||
|
env:
|
||||||
GH_TOKEN: ${{ secrets.CIBOT_ACCESS_TOKEN }}
|
GH_TOKEN: ${{ secrets.CIBOT_ACCESS_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
cd helm-charts/
|
cd helm-charts
|
||||||
if [[ $(gh pr ls -H sync/ks-core -B master) == "" ]]; then
|
if [[ $(gh pr ls -R kubesphere/helm-charts -A ks-ci-bot -H sync/ks-core/${GITHUB_REF#refs/*/} -B master) == "" ]]; then
|
||||||
gh pr create -H sync/ks-core -B master --title "Update ks-core helm chart" --body "Update ks-core helm chart"
|
# Create a PR in the kubesphere/helm-charts repository
|
||||||
|
gh pr create -R kubesphere/helm-charts \
|
||||||
|
-B master \
|
||||||
|
-H ks-ci-bot:sync/ks-core/${GITHUB_REF#refs/*/} \
|
||||||
|
-t "Update ks-core helm chart from ${GITHUB_REF#refs/*/}" \
|
||||||
|
-b "Update ks-core helm chart"
|
||||||
fi
|
fi
|
||||||
|
|||||||
9
Makefile
9
Makefile
@@ -2,9 +2,6 @@
|
|||||||
CRD_OPTIONS ?= "crd:allowDangerousTypes=true"
|
CRD_OPTIONS ?= "crd:allowDangerousTypes=true"
|
||||||
MANIFESTS="cluster/v1alpha1 iam/... quota/v1alpha2 storage/v1alpha1 tenant/... extensions/v1alpha1 core/v1alpha1 gateway/v1alpha2 application/v2"
|
MANIFESTS="cluster/v1alpha1 iam/... quota/v1alpha2 storage/v1alpha1 tenant/... extensions/v1alpha1 core/v1alpha1 gateway/v1alpha2 application/v2"
|
||||||
|
|
||||||
# App Version
|
|
||||||
APP_VERSION = v3.2.0
|
|
||||||
|
|
||||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||||
ifeq (,$(shell go env GOBIN))
|
ifeq (,$(shell go env GOBIN))
|
||||||
GOBIN=$(shell go env GOPATH)/bin
|
GOBIN=$(shell go env GOPATH)/bin
|
||||||
@@ -112,19 +109,15 @@ container-cross-push: ; $(info $(M)...Begin to build and push.) @ ## Build and
|
|||||||
hack/docker_build_multiarch.sh
|
hack/docker_build_multiarch.sh
|
||||||
|
|
||||||
helm-package: ; $(info $(M)...Begin to helm-package.) @ ## Helm-package.
|
helm-package: ; $(info $(M)...Begin to helm-package.) @ ## Helm-package.
|
||||||
ls config/crds/ | xargs -i cp -r config/crds/{} config/ks-core/crds/
|
helm package config/ks-core -d ./bin
|
||||||
helm package config/ks-core --app-version=${APP_VERSION} --version=0.1.0 -d ./bin
|
|
||||||
|
|
||||||
helm-deploy: ; $(info $(M)...Begin to helm-deploy.) @ ## Helm-deploy.
|
helm-deploy: ; $(info $(M)...Begin to helm-deploy.) @ ## Helm-deploy.
|
||||||
ls config/crds/ | xargs -i cp -r config/crds/{} config/ks-core/crds/
|
|
||||||
- kubectl create ns kubesphere-controls-system
|
- kubectl create ns kubesphere-controls-system
|
||||||
helm upgrade --install ks-core ./config/ks-core -n kubesphere-system --create-namespace
|
helm upgrade --install ks-core ./config/ks-core -n kubesphere-system --create-namespace
|
||||||
kubectl apply -f https://raw.githubusercontent.com/kubesphere/ks-installer/master/roles/ks-core/prepare/files/ks-init/role-templates.yaml
|
|
||||||
|
|
||||||
helm-uninstall: ; $(info $(M)...Begin to helm-uninstall.) @ ## Helm-uninstall.
|
helm-uninstall: ; $(info $(M)...Begin to helm-uninstall.) @ ## Helm-uninstall.
|
||||||
- kubectl delete ns kubesphere-controls-system
|
- kubectl delete ns kubesphere-controls-system
|
||||||
helm uninstall ks-core -n kubesphere-system
|
helm uninstall ks-core -n kubesphere-system
|
||||||
kubectl delete -f https://raw.githubusercontent.com/kubesphere/ks-installer/master/roles/ks-core/prepare/files/ks-init/role-templates.yaml
|
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
test: vet test-env ;$(info $(M)...Begin to run tests.) @ ## Run tests.
|
test: vet test-env ;$(info $(M)...Begin to run tests.) @ ## Run tests.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build
|
# Build
|
||||||
FROM golang:1.20.7 AS build_context
|
FROM golang:1.21.5 AS build_context
|
||||||
|
|
||||||
ENV OUTDIR=/out
|
ENV OUTDIR=/out
|
||||||
RUN mkdir -p ${OUTDIR}/usr/local/bin/
|
RUN mkdir -p ${OUTDIR}/usr/local/bin/
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ RUN curl -LO https://github.com/kubesphere/telemetry/releases/download/v1.0.0/te
|
|||||||
COPY config/ks-core ${OUTDIR}/var/helm-charts/ks-core
|
COPY config/ks-core ${OUTDIR}/var/helm-charts/ks-core
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
FROM golang:1.20.7 AS build_context
|
FROM golang:1.21.5 AS build_context
|
||||||
|
|
||||||
ENV OUTDIR=/out
|
ENV OUTDIR=/out
|
||||||
RUN mkdir -p ${OUTDIR}/usr/local/bin/
|
RUN mkdir -p ${OUTDIR}/usr/local/bin/
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 1.1.0
|
version: 1.1.3
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
appVersion: "v4.1.1"
|
appVersion: "v4.1.2"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: redis-ha
|
- name: redis-ha
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
appVersionID:
|
appVersionID:
|
||||||
type: string
|
type: string
|
||||||
|
icon:
|
||||||
|
type: string
|
||||||
values:
|
values:
|
||||||
format: byte
|
format: byte
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -50,12 +50,18 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
caBundle:
|
caBundle:
|
||||||
description: if the caBundle is empty, use --insecure-skip-tls-verify.
|
description: The caBundle (base64 string) is used in helmExecutor
|
||||||
|
to verify the helm server.
|
||||||
type: string
|
type: string
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
image:
|
image:
|
||||||
|
description: 'DEPRECATED: the field will remove in future versions,
|
||||||
|
please use url.'
|
||||||
type: string
|
type: string
|
||||||
|
insecure:
|
||||||
|
description: --insecure-skip-tls-verify. default false
|
||||||
|
type: boolean
|
||||||
updateStrategy:
|
updateStrategy:
|
||||||
properties:
|
properties:
|
||||||
registryPoll:
|
registryPoll:
|
||||||
|
|||||||
48
config/ks-core/charts/ks-crds/scripts/post-delete.sh
Executable file
48
config/ks-core/charts/ks-crds/scripts/post-delete.sh
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# set -x
|
||||||
|
|
||||||
|
CRD_NAMES=$1
|
||||||
|
MAPPING_CONFIG=$2
|
||||||
|
|
||||||
|
for extension in `kubectl get installplan -o json | jq -r '.items[] | select(.status.state == "Installed") | .metadata.name'`
|
||||||
|
do
|
||||||
|
namespace=$(kubectl get installplan $extension -o=jsonpath='{.status.targetNamespace}')
|
||||||
|
version=$(kubectl get extension $extension -o=jsonpath='{.status.installedVersion}')
|
||||||
|
extensionversion=$extension-$version
|
||||||
|
echo "Found extension $extensionversion installed"
|
||||||
|
helm status $extension --namespace $namespace
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
helm mapkubeapis $extension --namespace $namespace --mapfile $MAPPING_CONFIG
|
||||||
|
fi
|
||||||
|
helm status $extension-agent --namespace $namespace
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
helm mapkubeapis $extension-agent --namespace $namespace --mapfile $MAPPING_CONFIG
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
# remove namespace's finalizers && ownerReferences
|
||||||
|
kubectl patch workspaces.tenant.kubesphere.io system-workspace -p '{"metadata":{"finalizers":[]}}' --type=merge
|
||||||
|
kubectl patch workspacetemplates.tenant.kubesphere.io system-workspace -p '{"metadata":{"finalizers":[]}}' --type=merge
|
||||||
|
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}' -l 'kubesphere.io/managed=true')
|
||||||
|
do
|
||||||
|
kubectl label ns $ns kubesphere.io/workspace- && \
|
||||||
|
kubectl patch ns $ns -p '{"metadata":{"ownerReferences":[]}}' --type=merge && \
|
||||||
|
echo "{\"kind\":\"Namespace\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"$ns\",\"finalizers\":null}}" | kubectl replace --raw "/api/v1/namespaces/$ns/finalize" -f -
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
# delete crds
|
||||||
|
for crd in `kubectl get crds -o jsonpath="{.items[*].metadata.name}"`
|
||||||
|
do
|
||||||
|
if [[ ${CRD_NAMES[@]/${crd}/} != ${CRD_NAMES[@]} ]]; then
|
||||||
|
scop=$(eval echo $(kubectl get crd ${crd} -o jsonpath="{.spec.scope}"))
|
||||||
|
if [[ $scop =~ "Namespaced" ]] ; then
|
||||||
|
kubectl get $crd -A --no-headers | awk '{print $1" "$2" ""'$crd'"}' | xargs -n 3 sh -c 'kubectl patch $2 -n $0 $1 -p "{\"metadata\":{\"finalizers\":null}}" --type=merge 2>/dev/null && kubectl delete $2 -n $0 $1 2>/dev/null'
|
||||||
|
else
|
||||||
|
kubectl get $crd -A --no-headers | awk '{print $1" ""'$crd'"}' | xargs -n 2 sh -c 'kubectl patch $1 $0 -p "{\"metadata\":{\"finalizers\":null}}" --type=merge 2>/dev/null && kubectl delete $1 $0 2>/dev/null'
|
||||||
|
fi
|
||||||
|
kubectl delete crd $crd 2>/dev/null;
|
||||||
|
fi
|
||||||
|
done
|
||||||
21
config/ks-core/charts/ks-crds/templates/_images.tpl
Normal file
21
config/ks-core/charts/ks-crds/templates/_images.tpl
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{{- define "kubectl.image" -}}
|
||||||
|
{{ include "common.images.image" (dict "imageRoot" .Values.kubectl.image "global" (default .Values.global (dict "imageRegistry" "docker.io"))) }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "common.images.image" -}}
|
||||||
|
{{- $registryName := .global.imageRegistry -}}
|
||||||
|
{{- $repositoryName := .imageRoot.repository -}}
|
||||||
|
{{- $separator := ":" -}}
|
||||||
|
{{- $termination := .global.tag | toString -}}
|
||||||
|
{{- if .imageRoot.registry }}
|
||||||
|
{{- $registryName = .imageRoot.registry -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if .imageRoot.tag }}
|
||||||
|
{{- $termination = .imageRoot.tag | toString -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if .imageRoot.digest }}
|
||||||
|
{{- $separator = "@" -}}
|
||||||
|
{{- $termination = .imageRoot.digest | toString -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- printf "%s/%s%s%s" $registryName $repositoryName $separator $termination -}}
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
{{- $kubeVersion := .Capabilities.KubeVersion }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-post-delete-crd-scripts"
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": post-delete
|
||||||
|
"helm.sh/hook-weight": "-3"
|
||||||
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
|
||||||
|
data:
|
||||||
|
map.yaml: |
|
||||||
|
mappings:
|
||||||
|
{{- range $path, $_ := .Files.Glob "crds/**" }}
|
||||||
|
{{- $crd := $.Files.Get $path | fromYaml }}
|
||||||
|
{{- range $_, $version := $crd.spec.versions }}
|
||||||
|
- deprecatedAPI: "apiVersion: {{ $crd.spec.group }}/{{ $version.name }}\nkind: {{ $crd.spec.names.kind }}\n"
|
||||||
|
removedInVersion: "{{ $kubeVersion }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{ (.Files.Glob "scripts/post-delete.sh").AsConfig | indent 2 }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-post-delete-crd"
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": post-delete
|
||||||
|
"helm.sh/hook-weight": "-3"
|
||||||
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-post-delete-crd"
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": post-delete
|
||||||
|
"helm.sh/hook-weight": "-3"
|
||||||
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: "{{ .Release.Name }}-post-delete-crd"
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
{{- $crdNameList := list }}
|
||||||
|
{{- range $path, $_ := .Files.Glob "crds/**" }}
|
||||||
|
{{- $crd := $.Files.Get $path | fromYaml }}
|
||||||
|
{{- $crdNameList = append $crdNameList $crd.metadata.name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-post-delete-crd"
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": post-delete
|
||||||
|
"helm.sh/hook-weight": "-2"
|
||||||
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
serviceAccountName: "{{ .Release.Name }}-post-delete-crd"
|
||||||
|
containers:
|
||||||
|
- name: post-delete-job
|
||||||
|
image: {{ template "kubectl.image" . }}
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- /scripts/post-delete.sh
|
||||||
|
- '{{ join " " $crdNameList }}'
|
||||||
|
- /scripts/map.yaml
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /scripts
|
||||||
|
name: scripts
|
||||||
|
resources: {{- toYaml .Values.kubectl.resources | nindent 12 }}
|
||||||
|
volumes:
|
||||||
|
- name: scripts
|
||||||
|
configMap:
|
||||||
|
name: "{{ .Release.Name }}-post-delete-crd-scripts"
|
||||||
|
defaultMode: 420
|
||||||
@@ -10,6 +10,36 @@ data:
|
|||||||
{{ (.Files.Glob "scripts/install.sh").AsConfig | indent 2 }}
|
{{ (.Files.Glob "scripts/install.sh").AsConfig | indent 2 }}
|
||||||
{{ (.Files.Glob "crds/*").AsConfig | indent 2 }}
|
{{ (.Files.Glob "crds/*").AsConfig | indent 2 }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-pre-upgrade-crd"
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": pre-upgrade
|
||||||
|
"helm.sh/hook-weight": "-1"
|
||||||
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Release.Name }}-pre-upgrade-crd"
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": pre-upgrade
|
||||||
|
"helm.sh/hook-weight": "-1"
|
||||||
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: "{{ .Release.Name }}-pre-upgrade-crd"
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: Job
|
kind: Job
|
||||||
@@ -23,10 +53,10 @@ spec:
|
|||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
restartPolicy: Never
|
restartPolicy: Never
|
||||||
serviceAccountName: {{ include "ks-core.serviceAccountName" . }}
|
serviceAccountName: "{{ .Release.Name }}-pre-upgrade-crd"
|
||||||
containers:
|
containers:
|
||||||
- name: crd-install
|
- name: crd-install
|
||||||
image: {{ template "preUpgrade.image" . }}
|
image: {{ template "kubectl.image" . }}
|
||||||
command:
|
command:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- /scripts/install.sh
|
- /scripts/install.sh
|
||||||
@@ -34,7 +64,7 @@ spec:
|
|||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /scripts
|
- mountPath: /scripts
|
||||||
name: scripts
|
name: scripts
|
||||||
resources: {{- toYaml .Values.preUpgrade.resources | nindent 12 }}
|
resources: {{- toYaml .Values.kubectl.resources | nindent 12 }}
|
||||||
volumes:
|
volumes:
|
||||||
- name: scripts
|
- name: scripts
|
||||||
configMap:
|
configMap:
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
# Default values for ks-crds.
|
# Default values for ks-crds.
|
||||||
# This is a YAML-formatted file.
|
# This is a YAML-formatted file.
|
||||||
# Declare variables to be passed into your templates.
|
# Declare variables to be passed into your templates.
|
||||||
|
kubectl:
|
||||||
preUpgrade:
|
|
||||||
image:
|
image:
|
||||||
registry: ""
|
registry: ""
|
||||||
repository: kubesphereio/kubectl
|
repository: kubesphere/kubectl
|
||||||
tag: "v1.27.12"
|
tag: "v1.27.16"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
@@ -14,4 +13,4 @@ preUpgrade:
|
|||||||
memory: 1024Mi
|
memory: 1024Mi
|
||||||
requests:
|
requests:
|
||||||
cpu: 20m
|
cpu: 20m
|
||||||
memory: 100Mi
|
memory: 100Mi
|
||||||
|
|||||||
@@ -2,55 +2,9 @@
|
|||||||
|
|
||||||
# set -x
|
# set -x
|
||||||
|
|
||||||
CRD_NAMES=$1
|
|
||||||
MAPPING_CONFIG=$2
|
|
||||||
|
|
||||||
for extension in `kubectl get installplan -o json | jq -r '.items[] | select(.status.state == "Installed") | .metadata.name'`
|
|
||||||
do
|
|
||||||
namespace=$(kubectl get installplan $extension -o=jsonpath='{.status.targetNamespace}')
|
|
||||||
version=$(kubectl get extension $extension -o=jsonpath='{.status.installedVersion}')
|
|
||||||
extensionversion=$extension-$version
|
|
||||||
echo "Found extension $extensionversion installed"
|
|
||||||
helm status $extension --namespace $namespace
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
helm mapkubeapis $extension --namespace $namespace --mapfile $MAPPING_CONFIG
|
|
||||||
fi
|
|
||||||
helm status $extension-agent --namespace $namespace
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
helm mapkubeapis $extension-agent --namespace $namespace --mapfile $MAPPING_CONFIG
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
# remove namespace's finalizers && ownerReferences
|
|
||||||
kubectl patch workspaces.tenant.kubesphere.io system-workspace -p '{"metadata":{"finalizers":[]}}' --type=merge
|
|
||||||
kubectl patch workspacetemplates.tenant.kubesphere.io system-workspace -p '{"metadata":{"finalizers":[]}}' --type=merge
|
|
||||||
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}' -l 'kubesphere.io/managed=true')
|
|
||||||
do
|
|
||||||
kubectl label ns $ns kubesphere.io/workspace- && \
|
|
||||||
kubectl patch ns $ns -p '{"metadata":{"ownerReferences":[]}}' --type=merge && \
|
|
||||||
echo "{\"kind\":\"Namespace\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"$ns\",\"finalizers\":null}}" | kubectl replace --raw "/api/v1/namespaces/$ns/finalize" -f -
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
# delete crds
|
|
||||||
for crd in `kubectl get crds -o jsonpath="{.items[*].metadata.name}"`
|
|
||||||
do
|
|
||||||
if [[ ${CRD_NAMES[@]/${crd}/} != ${CRD_NAMES[@]} ]]; then
|
|
||||||
scop=$(eval echo $(kubectl get crd ${crd} -o jsonpath="{.spec.scope}"))
|
|
||||||
if [[ $scop =~ "Namespaced" ]] ; then
|
|
||||||
kubectl get $crd -A --no-headers | awk '{print $1" "$2" ""'$crd'"}' | xargs -n 3 sh -c 'kubectl patch $2 -n $0 $1 -p "{\"metadata\":{\"finalizers\":null}}" --type=merge 2>/dev/null && kubectl delete $2 -n $0 $1 2>/dev/null'
|
|
||||||
else
|
|
||||||
kubectl get $crd -A --no-headers | awk '{print $1" ""'$crd'"}' | xargs -n 2 sh -c 'kubectl patch $1 $0 -p "{\"metadata\":{\"finalizers\":null}}" --type=merge 2>/dev/null && kubectl delete $1 $0 2>/dev/null'
|
|
||||||
fi
|
|
||||||
kubectl delete crd $crd 2>/dev/null;
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
EXTENSION_RELATED_RESOURCES='jobs.batch roles.rbac.authorization.k8s.io rolebindings.rbac.authorization.k8s.io clusterroles.rbac.authorization.k8s.io clusterrolebindings.rbac.authorization.k8s.io'
|
EXTENSION_RELATED_RESOURCES='jobs.batch roles.rbac.authorization.k8s.io rolebindings.rbac.authorization.k8s.io clusterroles.rbac.authorization.k8s.io clusterrolebindings.rbac.authorization.k8s.io'
|
||||||
|
|
||||||
for resource in $EXTENSION_RELATED_RESOURCES;do
|
for resource in $EXTENSION_RELATED_RESOURCES;do
|
||||||
echo "kubectl delete $resource -l kubesphere.io/extension-ref --all-namespaces"
|
echo "kubectl delete $resource -l kubesphere.io/extension-ref --all-namespaces"
|
||||||
kubectl delete $resource -l kubesphere.io/managed=true --all-namespaces
|
kubectl delete $resource -l kubesphere.io/managed=true --all-namespaces
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ Return the proper image name
|
|||||||
{{ include "common.images.image" (dict "imageRoot" .Values.redis.image "global" .Values.global) }}
|
{{ include "common.images.image" (dict "imageRoot" .Values.redis.image "global" .Values.global) }}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{- define "preUpgrade.image" -}}
|
{{- define "extensions_museum.image" -}}
|
||||||
{{ include "common.images.image" (dict "imageRoot" .Values.preUpgrade.image "global" .Values.global) }}
|
{{ include "common.images.image" (dict "imageRoot" .Values.ksExtensionRepository.image "global" .Values.global) }}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{- define "common.images.image" -}}
|
{{- define "common.images.image" -}}
|
||||||
@@ -70,6 +70,10 @@ Return the proper Docker Image Registry Secret Names
|
|||||||
{{- include "common.images.pullSecrets" (dict "images" (list .Values.controller.image) "global" .Values.global) -}}
|
{{- include "common.images.pullSecrets" (dict "images" (list .Values.controller.image) "global" .Values.global) -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "extensions_museum.imagePullSecrets" -}}
|
||||||
|
{{- include "common.images.pullSecrets" (dict "images" (list .Values.ksExtensionRepository.image) "global" .Values.global) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
{{- define "common.images.pullSecrets" -}}
|
{{- define "common.images.pullSecrets" -}}
|
||||||
{{- $pullSecrets := list }}
|
{{- $pullSecrets := list }}
|
||||||
|
|
||||||
|
|||||||
109
config/ks-core/templates/extension-museum.yaml
Normal file
109
config/ks-core/templates/extension-museum.yaml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{{- if .Values.ksExtensionRepository.enabled }}
|
||||||
|
|
||||||
|
{{- $ca := genCA "self-signed-ca" 3650 }}
|
||||||
|
{{- $cn := printf "%s-extensions-museum" .Release.Name }}
|
||||||
|
{{- $altName1 := printf "extensions-museum.%s" .Release.Namespace }}
|
||||||
|
{{- $altName2 := printf "extensions-museum.%s.svc" .Release.Namespace }}
|
||||||
|
{{- $cert := genSignedCert $cn nil (list $altName1 $altName2) 3650 $ca }}
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: extensions-museum
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
app: extensions-museum
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: extensions-museum
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: extensions-museum
|
||||||
|
annotations:
|
||||||
|
# force restart ks-apiserver after the upgrade is complete if kubesphere-config changes
|
||||||
|
checksum/cert: {{ sha256sum $cert.Cert }}
|
||||||
|
spec:
|
||||||
|
{{- include "extensions_museum.imagePullSecrets" . | nindent 6 }}
|
||||||
|
containers:
|
||||||
|
- name: extensions-museum
|
||||||
|
image: {{ template "extensions_museum.image" . }}
|
||||||
|
command:
|
||||||
|
- "/chartmuseum"
|
||||||
|
- "--storage-local-rootdir"
|
||||||
|
- "/charts"
|
||||||
|
- "--storage"
|
||||||
|
- "local"
|
||||||
|
- "--tls-cert"
|
||||||
|
- "/etc/certs/tls.crt"
|
||||||
|
- "--tls-key"
|
||||||
|
- "/etc/certs/tls.key"
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
volumeMounts:
|
||||||
|
- name: certs
|
||||||
|
mountPath: /etc/certs/
|
||||||
|
volumes:
|
||||||
|
- name: certs
|
||||||
|
secret:
|
||||||
|
secretName: extensions-museum-certs
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: extensions-museum-certs
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
type: kubernetes.io/tls
|
||||||
|
data:
|
||||||
|
ca.crt: {{ b64enc $ca.Cert }}
|
||||||
|
tls.crt: {{ b64enc $cert.Cert }}
|
||||||
|
tls.key: {{ b64enc $cert.Key }}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: extensions-museum
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: extensions-museum
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
|
targetPort: 8080
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: kubesphere.io/v1alpha1
|
||||||
|
kind: Repository
|
||||||
|
metadata:
|
||||||
|
name: extensions-museum
|
||||||
|
spec:
|
||||||
|
url: https://extensions-museum.{{ .Release.Namespace }}.svc
|
||||||
|
caBundle: {{ b64enc $ca.Cert }}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: {{ if semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version }}batch/v1{{ else }}batch/v1beta1{{end}}
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: restart-extensions-museum
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
schedule: "0 0 * * *"
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: restart-extensions-museum
|
||||||
|
image: {{ template "kubectl.image" . }}
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- "kubectl rollout restart deployment/extensions-museum -n {{ .Release.Namespace }}"
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
serviceAccountName: {{ template "ks-core.serviceAccountName" . }}
|
||||||
|
{{end}}
|
||||||
@@ -69,15 +69,9 @@ data:
|
|||||||
appSelector: {{ .Values.composedApp.appSelector | quote }}
|
appSelector: {{ .Values.composedApp.appSelector | quote }}
|
||||||
kubesphere:
|
kubesphere:
|
||||||
tls: {{ .Values.internalTLS }}
|
tls: {{ .Values.internalTLS }}
|
||||||
{{- if and .Values.cloud.enabled (eq (include "role" .) "host") }}
|
{{- if and .Values.telemetry.enabled (eq (include "role" .) "host") }}
|
||||||
telemetry:
|
telemetry:
|
||||||
{{- if eq .Values.cloud.env "clouddev.kubesphere.io" }}
|
|
||||||
ksCloudURL: "https://clouddev.kubesphere.io"
|
|
||||||
{{- else if eq .Values.cloud.env "kubesphere.cloud" }}
|
|
||||||
ksCloudURL: "https://kubesphere.cloud"
|
ksCloudURL: "https://kubesphere.cloud"
|
||||||
{{- else if and .Values.cloud.customEnv .Values.cloud.customEnv.url }}
|
|
||||||
ksCloudURL: {{ $.Values.cloud.customEnv.url | quote }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.ha.enabled -}}
|
{{- if .Values.ha.enabled -}}
|
||||||
{{- if .Values.ha.cache }}
|
{{- if .Values.ha.cache }}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{{- $kubeVersion := .Capabilities.KubeVersion }}
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
@@ -8,15 +7,6 @@ metadata:
|
|||||||
"helm.sh/hook-weight": "-1"
|
"helm.sh/hook-weight": "-1"
|
||||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
|
||||||
data:
|
data:
|
||||||
map.yaml: |
|
|
||||||
mappings:
|
|
||||||
{{- range $path, $_ := .Files.Glob "charts/ks-crds/crds/**" }}
|
|
||||||
{{- $crd := $.Files.Get $path | fromYaml }}
|
|
||||||
{{- range $_, $version := $crd.spec.versions }}
|
|
||||||
- deprecatedAPI: "apiVersion: {{ $crd.spec.group }}/{{ $version.name }}\nkind: {{ $crd.spec.names.kind }}\n"
|
|
||||||
removedInVersion: "{{ $kubeVersion }}"
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{ (.Files.Glob "scripts/post-delete.sh").AsConfig | indent 2 }}
|
{{ (.Files.Glob "scripts/post-delete.sh").AsConfig | indent 2 }}
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -51,12 +41,6 @@ subjects:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
{{- $crdNameList := list }}
|
|
||||||
{{- range $path, $_ := .Files.Glob "charts/ks-crds/crds/**" }}
|
|
||||||
{{- $crd := $.Files.Get $path | fromYaml }}
|
|
||||||
{{- $crdNameList = append $crdNameList $crd.metadata.name }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: Job
|
kind: Job
|
||||||
metadata:
|
metadata:
|
||||||
@@ -76,8 +60,6 @@ spec:
|
|||||||
command:
|
command:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- /scripts/post-delete.sh
|
- /scripts/post-delete.sh
|
||||||
- '{{ join " " $crdNameList }}'
|
|
||||||
- /scripts/map.yaml
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /scripts
|
- mountPath: /scripts
|
||||||
name: scripts
|
name: scripts
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
## @param global.tag Global Docker image tag
|
## @param global.tag Global Docker image tag
|
||||||
global:
|
global:
|
||||||
imageRegistry: docker.io
|
imageRegistry: docker.io
|
||||||
tag: v4.1.1
|
tag: v4.1.2
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
|
|
||||||
## @param nameOverride String to partially override common.names.fullname
|
## @param nameOverride String to partially override common.names.fullname
|
||||||
@@ -320,7 +320,10 @@ nodeShell:
|
|||||||
tag: "v1.27.16"
|
tag: "v1.27.16"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
cloud:
|
# Telemetry collects aggregated information about the versions of KubeSphere, Kubernetes, and the extensions used.
|
||||||
|
# KubeSphere Cloud uses this information to help improve the product and does not share it with third-parties.
|
||||||
|
# If you prefer not to share this data, you can keep this setting disabled.
|
||||||
|
telemetry:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
extension:
|
extension:
|
||||||
@@ -418,7 +421,7 @@ redisHA:
|
|||||||
- ""
|
- ""
|
||||||
|
|
||||||
ksCRDs:
|
ksCRDs:
|
||||||
preUpgrade:
|
kubectl:
|
||||||
image:
|
image:
|
||||||
registry: ""
|
registry: ""
|
||||||
repository: kubesphere/kubectl
|
repository: kubesphere/kubectl
|
||||||
@@ -431,3 +434,12 @@ ksCRDs:
|
|||||||
requests:
|
requests:
|
||||||
cpu: 20m
|
cpu: 20m
|
||||||
memory: 100Mi
|
memory: 100Mi
|
||||||
|
|
||||||
|
# add museum for all ks-extensions
|
||||||
|
ksExtensionRepository:
|
||||||
|
enabled: true
|
||||||
|
image:
|
||||||
|
registry: ""
|
||||||
|
repository: kubesphere/ks-extensions-museum
|
||||||
|
tag: "latest"
|
||||||
|
pullPolicy: Always
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ for PKG in "${PKGS[@]}"; do
|
|||||||
go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./staging/src/kubesphere.io/api/"${PKG}"
|
go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./staging/src/kubesphere.io/api/"${PKG}"
|
||||||
else
|
else
|
||||||
echo "Generating manifests for ${PKG}"
|
echo "Generating manifests for ${PKG}"
|
||||||
go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./staging/src/kubesphere.io/api/"${PKG}" rbac:roleName=controller-perms "${CRD_OPTIONS}" output:crd:artifacts:config=config/ks-core/crds
|
go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./staging/src/kubesphere.io/api/"${PKG}" rbac:roleName=controller-perms "${CRD_OPTIONS}" output:crd:artifacts:config=config/ks-core/charts/ks-crds/crds
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/apis/audit"
|
"k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||||
"kubesphere.io/api/iam/v1beta1"
|
"kubesphere.io/api/iam/v1beta1"
|
||||||
@@ -398,6 +399,9 @@ func (a *auditing) eventToBytes(events []*Event) [][]byte {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ http.ResponseWriter = &ResponseCapture{}
|
||||||
|
var _ responsewriter.UserProvidedDecorator = &ResponseCapture{}
|
||||||
|
|
||||||
type ResponseCapture struct {
|
type ResponseCapture struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
wroteHeader bool
|
wroteHeader bool
|
||||||
@@ -413,6 +417,10 @@ func NewResponseCapture(w http.ResponseWriter) *ResponseCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ResponseCapture) Unwrap() http.ResponseWriter {
|
||||||
|
return c.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ResponseCapture) Header() http.Header {
|
func (c *ResponseCapture) Header() http.Header {
|
||||||
return c.ResponseWriter.Header()
|
return c.ResponseWriter.Header()
|
||||||
}
|
}
|
||||||
@@ -425,9 +433,6 @@ func (c *ResponseCapture) Write(data []byte) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
if flusher, ok := c.ResponseWriter.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package filters
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/auditing"
|
"kubesphere.io/kubesphere/pkg/apiserver/auditing"
|
||||||
@@ -44,7 +45,7 @@ func (a *auditingFilter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
if event := a.LogRequestObject(req, info); event != nil {
|
if event := a.LogRequestObject(req, info); event != nil {
|
||||||
resp := auditing.NewResponseCapture(w)
|
resp := auditing.NewResponseCapture(w)
|
||||||
a.next.ServeHTTP(resp, req)
|
a.next.ServeHTTP(responsewriter.WrapForHTTP1Or2(resp), req)
|
||||||
go a.LogResponseObject(event, resp)
|
go a.LogResponseObject(event, resp)
|
||||||
} else {
|
} else {
|
||||||
a.next.ServeHTTP(w, req)
|
a.next.ServeHTTP(w, req)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/metrics"
|
"kubesphere.io/kubesphere/pkg/apiserver/metrics"
|
||||||
@@ -20,12 +21,19 @@ import (
|
|||||||
"kubesphere.io/kubesphere/pkg/utils/iputil"
|
"kubesphere.io/kubesphere/pkg/utils/iputil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ http.ResponseWriter = &metaResponseWriter{}
|
||||||
|
var _ responsewriter.UserProvidedDecorator = &metaResponseWriter{}
|
||||||
|
|
||||||
type metaResponseWriter struct {
|
type metaResponseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
statusCode int
|
statusCode int
|
||||||
size int
|
size int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *metaResponseWriter) Unwrap() http.ResponseWriter {
|
||||||
|
return r.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
func newMetaResponseWriter(w http.ResponseWriter) *metaResponseWriter {
|
func newMetaResponseWriter(w http.ResponseWriter) *metaResponseWriter {
|
||||||
return &metaResponseWriter{
|
return &metaResponseWriter{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
@@ -48,9 +56,6 @@ func (r *metaResponseWriter) Write(b []byte) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return size, err
|
return size, err
|
||||||
}
|
}
|
||||||
if flusher, ok := r.ResponseWriter.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
return size, nil
|
return size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +71,8 @@ func WithGlobalFilter(handler http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
wrapper := newMetaResponseWriter(w)
|
wrapper := newMetaResponseWriter(w)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
handler.ServeHTTP(wrapper, req)
|
|
||||||
|
handler.ServeHTTP(responsewriter.WrapForHTTP1Or2(wrapper), req)
|
||||||
elapsedTime := time.Since(start)
|
elapsedTime := time.Since(start)
|
||||||
|
|
||||||
// Record metrics for each request
|
// Record metrics for each request
|
||||||
@@ -94,7 +100,7 @@ func WithGlobalFilter(handler http.Handler) http.Handler {
|
|||||||
req.Proto,
|
req.Proto,
|
||||||
wrapper.statusCode,
|
wrapper.statusCode,
|
||||||
wrapper.size,
|
wrapper.size,
|
||||||
elapsedTime.Microseconds(),
|
elapsedTime.Milliseconds(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,8 +157,7 @@ func (h *Helper) AggregationRole(ctx context.Context, ruleOwner RuleOwner, recor
|
|||||||
if !cover {
|
if !cover {
|
||||||
needUpdate = true
|
needUpdate = true
|
||||||
newRule := append(ruleOwner.GetRules(), uncovered...)
|
newRule := append(ruleOwner.GetRules(), uncovered...)
|
||||||
squashedRules := SquashRules(len(newRule), newRule)
|
ruleOwner.SetRules(newRule)
|
||||||
ruleOwner.SetRules(squashedRules)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !templateNamesEqual {
|
if !templateNamesEqual {
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ func ruleCovers(ownerRule, subRule rbacv1.PolicyRule) bool {
|
|||||||
verbMatches := has(ownerRule.Verbs, rbacv1.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs)
|
verbMatches := has(ownerRule.Verbs, rbacv1.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs)
|
||||||
groupMatches := has(ownerRule.APIGroups, rbacv1.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups)
|
groupMatches := has(ownerRule.APIGroups, rbacv1.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups)
|
||||||
resourceMatches := resourceCoversAll(ownerRule.Resources, subRule.Resources)
|
resourceMatches := resourceCoversAll(ownerRule.Resources, subRule.Resources)
|
||||||
nonResourceURLMatches := nonResourceURLsCoversAll(ownerRule.NonResourceURLs, subRule.NonResourceURLs)
|
nonResourceURLMatches := (len(ownerRule.NonResourceURLs) == 0 && len(subRule.NonResourceURLs) == 0) || (len(ownerRule.Resources) == 0 &&
|
||||||
|
len(subRule.Resources) == 0 && nonResourceURLsCoversAll(ownerRule.NonResourceURLs, subRule.NonResourceURLs))
|
||||||
|
|
||||||
resourceNameMatches := false
|
resourceNameMatches := false
|
||||||
|
|
||||||
|
|||||||
@@ -265,12 +265,12 @@ func (r *AppReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
|||||||
if strings.Contains(release.Info.Description, "context deadline exceeded") && reCheck < timeoutMaxRecheck {
|
if strings.Contains(release.Info.Description, "context deadline exceeded") && reCheck < timeoutMaxRecheck {
|
||||||
|
|
||||||
if apprls.Status.State != appv2.StatusTimeout {
|
if apprls.Status.State != appv2.StatusTimeout {
|
||||||
err = r.updateStatus(ctx, apprls, appv2.StatusTimeout, "install time out")
|
err = r.updateStatus(ctx, apprls, appv2.StatusTimeout, "Installation timeout")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("failed to update apprelease %s status : %v", apprls.Name, err)
|
klog.Errorf("failed to update apprelease %s status : %v", apprls.Name, err)
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
klog.Infof("install time out, will check status again after %d second", timeoutVerificationAgain)
|
klog.Infof("Installation timeout, will check status again after %d second", timeoutVerificationAgain)
|
||||||
return ctrl.Result{RequeueAfter: timeoutVerificationAgain * time.Second}, nil
|
return ctrl.Result{RequeueAfter: timeoutVerificationAgain * time.Second}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ func repoParseRequest(cli client.Client, versions helmrepo.ChartVersions, helmRe
|
|||||||
FromRepo: true,
|
FromRepo: true,
|
||||||
}
|
}
|
||||||
url := ver.URLs[0]
|
url := ver.URLs[0]
|
||||||
methodList := []string{"https://", "http://", "s3://"}
|
methodList := []string{"https://", "http://", "s3://", "oci://"}
|
||||||
needContact := true
|
needContact := true
|
||||||
for _, method := range methodList {
|
for _, method := range methodList {
|
||||||
if strings.HasPrefix(url, method) {
|
if strings.HasPrefix(url, method) {
|
||||||
|
|||||||
@@ -456,26 +456,40 @@ func (r *InstallPlanReconciler) loadChartData(ctx context.Context) ([]byte, stri
|
|||||||
switch chartURL.Scheme {
|
switch chartURL.Scheme {
|
||||||
case registry.OCIScheme:
|
case registry.OCIScheme:
|
||||||
opts := make([]getter.Option, 0)
|
opts := make([]getter.Option, 0)
|
||||||
opts = append(opts, getter.WithInsecureSkipVerifyTLS(true))
|
if extensionVersion.Spec.Repository != "" {
|
||||||
|
opts = append(opts, getter.WithInsecureSkipVerifyTLS(repo.Spec.Insecure))
|
||||||
|
}
|
||||||
if repo.Spec.BasicAuth != nil {
|
if repo.Spec.BasicAuth != nil {
|
||||||
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
||||||
}
|
}
|
||||||
chartGetter, err = getter.NewOCIGetter(opts...)
|
chartGetter, err = getter.NewOCIGetter(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to create chart getter: %v", err)
|
||||||
|
}
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
opts := make([]getter.Option, 0)
|
opts := make([]getter.Option, 0)
|
||||||
|
if chartURL.Scheme == "https" && extensionVersion.Spec.Repository != "" {
|
||||||
|
opts = append(opts, getter.WithInsecureSkipVerifyTLS(repo.Spec.Insecure))
|
||||||
|
}
|
||||||
|
if repo.Spec.CABundle != "" {
|
||||||
|
caFile, err := storeCAFile(repo.Spec.CABundle, repo.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to store CABundle to local file: %s", err)
|
||||||
|
}
|
||||||
|
opts = append(opts, getter.WithTLSClientConfig("", "", caFile))
|
||||||
|
}
|
||||||
if chartURL.Scheme == "https" {
|
if chartURL.Scheme == "https" {
|
||||||
opts = append(opts, getter.WithInsecureSkipVerifyTLS(true))
|
opts = append(opts, getter.WithInsecureSkipVerifyTLS(repo.Spec.Insecure))
|
||||||
}
|
}
|
||||||
if repo.Spec.BasicAuth != nil {
|
if repo.Spec.BasicAuth != nil {
|
||||||
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
||||||
}
|
}
|
||||||
chartGetter, err = getter.NewHTTPGetter(opts...)
|
chartGetter, err = getter.NewHTTPGetter(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to create chart getter: %v", err)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unsupported scheme: %s", chartURL.Scheme)
|
return nil, "", fmt.Errorf("unsupported scheme: %s", chartURL.Scheme)
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("failed to create chart getter: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer, err := chartGetter.Get(chartURL.String())
|
buffer, err := chartGetter.Get(chartURL.String())
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -19,10 +18,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
kscontroller "kubesphere.io/kubesphere/pkg/controller"
|
|
||||||
|
|
||||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"helm.sh/helm/v3/pkg/chart/loader"
|
"helm.sh/helm/v3/pkg/chart/loader"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
@@ -35,6 +30,7 @@ import (
|
|||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||||
corev1alpha1 "kubesphere.io/api/core/v1alpha1"
|
corev1alpha1 "kubesphere.io/api/core/v1alpha1"
|
||||||
"kubesphere.io/utils/helm"
|
"kubesphere.io/utils/helm"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
@@ -43,6 +39,7 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/constants"
|
"kubesphere.io/kubesphere/pkg/constants"
|
||||||
|
kscontroller "kubesphere.io/kubesphere/pkg/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -52,6 +49,8 @@ const (
|
|||||||
defaultRequeueInterval = 15 * time.Second
|
defaultRequeueInterval = 15 * time.Second
|
||||||
generateNameFormat = "repository-%s"
|
generateNameFormat = "repository-%s"
|
||||||
extensionFileName = "extension.yaml"
|
extensionFileName = "extension.yaml"
|
||||||
|
// caTemplate store repository.spec.caBound in local dir.
|
||||||
|
caTemplate = "{{ .TempDIR }}/repository/{{ .RepositoryName }}/ssl/ca.crt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var extensionRepoConflict = fmt.Errorf("extension repo mismatch")
|
var extensionRepoConflict = fmt.Errorf("extension repo mismatch")
|
||||||
@@ -178,10 +177,10 @@ func (r *RepositoryReconciler) syncExtensionsFromURL(ctx context.Context, repo *
|
|||||||
logger := klog.FromContext(ctx)
|
logger := klog.FromContext(ctx)
|
||||||
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cred := helm.RepoCredential{}
|
|
||||||
if repo.Spec.BasicAuth != nil {
|
cred, err := newHelmCred(repo)
|
||||||
cred.Username = repo.Spec.BasicAuth.Username
|
if err != nil {
|
||||||
cred.Password = repo.Spec.BasicAuth.Password
|
return err
|
||||||
}
|
}
|
||||||
index, err := helm.LoadRepoIndex(ctx, repoURL, cred)
|
index, err := helm.LoadRepoIndex(ctx, repoURL, cred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -216,7 +215,7 @@ func (r *RepositoryReconciler) syncExtensionsFromURL(ctx context.Context, repo *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extensionVersionSpec, err := r.loadExtensionVersionSpecFrom(ctx, chartURL, repo)
|
extensionVersionSpec, err := r.loadExtensionVersionSpecFrom(ctx, chartURL, repo, cred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load extension version spec: %s", err)
|
return fmt.Errorf("failed to load extension version spec: %s", err)
|
||||||
}
|
}
|
||||||
@@ -435,37 +434,19 @@ func (r *RepositoryReconciler) deployRepository(ctx context.Context, repo *corev
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RepositoryReconciler) loadExtensionVersionSpecFrom(ctx context.Context, chartURL string, repo *corev1alpha1.Repository) (*corev1alpha1.ExtensionVersionSpec, error) {
|
func (r *RepositoryReconciler) loadExtensionVersionSpecFrom(ctx context.Context, chartURL string, repo *corev1alpha1.Repository, cred helm.RepoCredential) (*corev1alpha1.ExtensionVersionSpec, error) {
|
||||||
logger := klog.FromContext(ctx)
|
logger := klog.FromContext(ctx)
|
||||||
var result *corev1alpha1.ExtensionVersionSpec
|
var result *corev1alpha1.ExtensionVersionSpec
|
||||||
|
|
||||||
err := retry.OnError(retry.DefaultRetry, func(err error) bool {
|
err := retry.OnError(retry.DefaultRetry, func(err error) bool {
|
||||||
return true
|
return true
|
||||||
}, func() error {
|
}, func() error {
|
||||||
req, err := http.NewRequest(http.MethodGet, chartURL, nil)
|
data, err := helm.LoadData(ctx, chartURL, cred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.Spec.BasicAuth != nil {
|
files, err := loader.LoadArchiveFiles(data)
|
||||||
req.SetBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := loader.LoadArchiveFiles(resp.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,18 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
goerrors "errors"
|
goerrors "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
yaml3 "gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
|
yaml3 "gopkg.in/yaml.v3"
|
||||||
"helm.sh/helm/v3/pkg/chart"
|
"helm.sh/helm/v3/pkg/chart"
|
||||||
"helm.sh/helm/v3/pkg/storage/driver"
|
"helm.sh/helm/v3/pkg/storage/driver"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
@@ -23,9 +26,9 @@ import (
|
|||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||||
corev1alpha1 "kubesphere.io/api/core/v1alpha1"
|
corev1alpha1 "kubesphere.io/api/core/v1alpha1"
|
||||||
|
"kubesphere.io/utils/helm"
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/utils/hashutil"
|
"kubesphere.io/kubesphere/pkg/utils/hashutil"
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/version"
|
"kubesphere.io/kubesphere/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -264,3 +267,58 @@ func configChanged(sub *corev1alpha1.InstallPlan, cluster string) bool {
|
|||||||
}
|
}
|
||||||
return newConfigHash != oldConfigHash
|
return newConfigHash != oldConfigHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newHelmCred from Repository
|
||||||
|
func newHelmCred(repo *corev1alpha1.Repository) (helm.RepoCredential, error) {
|
||||||
|
cred := helm.RepoCredential{
|
||||||
|
InsecureSkipTLSVerify: repo.Spec.Insecure,
|
||||||
|
}
|
||||||
|
if repo.Spec.CABundle != "" {
|
||||||
|
caFile, err := storeCAFile(repo.Spec.CABundle, repo.Name)
|
||||||
|
if err != nil {
|
||||||
|
return cred, err
|
||||||
|
}
|
||||||
|
cred.CAFile = caFile
|
||||||
|
}
|
||||||
|
if repo.Spec.BasicAuth != nil {
|
||||||
|
cred.Username = repo.Spec.BasicAuth.Username
|
||||||
|
cred.Password = repo.Spec.BasicAuth.Password
|
||||||
|
}
|
||||||
|
return cred, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeCAFile in local file from caTemplate.
|
||||||
|
func storeCAFile(caBundle string, repoName string) (string, error) {
|
||||||
|
var buff = &bytes.Buffer{}
|
||||||
|
tmpl, err := template.New("repositoryCABundle").Parse(caTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(buff, map[string]string{
|
||||||
|
"TempDIR": os.TempDir(),
|
||||||
|
"RepositoryName": repoName,
|
||||||
|
}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
caFile := buff.String()
|
||||||
|
if _, err := os.Stat(filepath.Dir(caFile)); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(caFile), os.ModePerm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := base64.StdEncoding.DecodeString(caBundle)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(caFile, data, os.ModePerm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return caFile, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func (r *Reconciler) reconcileWorkspaceOwnerReference(ctx context.Context, names
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !metav1.IsControlledBy(namespace, workspace) {
|
if !metav1.IsControlledBy(namespace, workspace) && namespace.Labels[constants.KubeSphereManagedLabel] == "true" {
|
||||||
namespace = namespace.DeepCopy()
|
namespace = namespace.DeepCopy()
|
||||||
if err := controllerutil.SetControllerReference(workspace, namespace, scheme.Scheme); err != nil {
|
if err := controllerutil.SetControllerReference(workspace, namespace, scheme.Scheme); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -42,8 +42,11 @@ var _ = Describe("Namespace", func() {
|
|||||||
It("Should create successfully", func() {
|
It("Should create successfully", func() {
|
||||||
namespace := &corev1.Namespace{
|
namespace := &corev1.Namespace{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-namespace",
|
Name: "test-namespace",
|
||||||
Labels: map[string]string{tenantv1beta1.WorkspaceLabel: workspace.Name},
|
Labels: map[string]string{
|
||||||
|
tenantv1beta1.WorkspaceLabel: workspace.Name,
|
||||||
|
constants.KubeSphereManagedLabel: "true",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,19 +40,19 @@ func (c *Controller) WatchOpenAPIChanges(ctx context.Context, cache runtimecache
|
|||||||
AddFunc: func(obj interface{}) {
|
AddFunc: func(obj interface{}) {
|
||||||
apiService := obj.(*extensionsv1alpha1.APIService)
|
apiService := obj.(*extensionsv1alpha1.APIService)
|
||||||
if err := openAPIV2Service.AddUpdateApiService(apiService); err != nil {
|
if err := openAPIV2Service.AddUpdateApiService(apiService); err != nil {
|
||||||
klog.Error(err)
|
klog.V(4).Infof("add openapi v2 service failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := openAPIV3Service.AddUpdateApiService(apiService); err != nil {
|
if err := openAPIV3Service.AddUpdateApiService(apiService); err != nil {
|
||||||
klog.Error(err)
|
klog.V(4).Infof("add openapi v3 service failed: %v", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpdateFunc: func(old, new interface{}) {
|
UpdateFunc: func(old, new interface{}) {
|
||||||
apiService := new.(*extensionsv1alpha1.APIService)
|
apiService := new.(*extensionsv1alpha1.APIService)
|
||||||
if err := openAPIV2Service.AddUpdateApiService(apiService); err != nil {
|
if err := openAPIV2Service.AddUpdateApiService(apiService); err != nil {
|
||||||
klog.Error(err)
|
klog.V(4).Infof("update openapi v2 service failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := openAPIV3Service.AddUpdateApiService(apiService); err != nil {
|
if err := openAPIV3Service.AddUpdateApiService(apiService); err != nil {
|
||||||
klog.Error(err)
|
klog.V(4).Infof("update openapi v3 service failed: %v", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DeleteFunc: func(obj interface{}) {
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
|||||||
@@ -96,9 +96,9 @@ func (r *Reconciler) syncToKubernetes(ctx context.Context, role *iamv1beta1.Role
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.Error(err, "sync role failed", "role", role.Name)
|
r.logger.Error(err, "sync role failed", "namespace", role.Namespace, "role", role.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.logger.V(4).Info("sync role to K8s", "role", role.Name, "op", op)
|
r.logger.V(4).Info("sync role to K8s", "namespace", role.Namespace, "role", role.Name, "op", op)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ package v1alpha1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/v3"
|
"github.com/emicklei/go-restful/v3"
|
||||||
"helm.sh/helm/v3/pkg/chart/loader"
|
"helm.sh/helm/v3/pkg/chart/loader"
|
||||||
@@ -22,6 +26,8 @@ import (
|
|||||||
"kubesphere.io/kubesphere/pkg/api"
|
"kubesphere.io/kubesphere/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var caTemplate = "{{ .TempDIR }}/repository/{{ .RepositoryName }}/ssl/ca.crt"
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
cache runtimeclient.Reader
|
cache runtimeclient.Reader
|
||||||
}
|
}
|
||||||
@@ -71,23 +77,40 @@ func (h *handler) ListFiles(request *restful.Request, response *restful.Response
|
|||||||
switch chartURL.Scheme {
|
switch chartURL.Scheme {
|
||||||
case registry.OCIScheme:
|
case registry.OCIScheme:
|
||||||
opts := make([]getter.Option, 0)
|
opts := make([]getter.Option, 0)
|
||||||
opts = append(opts, getter.WithInsecureSkipVerifyTLS(true))
|
if extensionVersion.Spec.Repository != "" {
|
||||||
|
opts = append(opts, getter.WithInsecureSkipVerifyTLS(repo.Spec.Insecure))
|
||||||
|
}
|
||||||
if repo.Spec.BasicAuth != nil {
|
if repo.Spec.BasicAuth != nil {
|
||||||
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
||||||
}
|
}
|
||||||
chartGetter, err = getter.NewOCIGetter(opts...)
|
chartGetter, err = getter.NewOCIGetter(opts...)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleInternalError(response, request, fmt.Errorf("failed to create chart getter: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
options := make([]getter.Option, 0)
|
opts := make([]getter.Option, 0)
|
||||||
if chartURL.Scheme == "https" {
|
if chartURL.Scheme == "https" && extensionVersion.Spec.Repository != "" {
|
||||||
options = append(options, getter.WithInsecureSkipVerifyTLS(true))
|
opts = append(opts, getter.WithInsecureSkipVerifyTLS(repo.Spec.Insecure))
|
||||||
|
}
|
||||||
|
if repo.Spec.CABundle != "" {
|
||||||
|
caFile, err := storeCAFile(repo.Spec.CABundle, repo.Name)
|
||||||
|
if err != nil {
|
||||||
|
api.HandleInternalError(response, request, fmt.Errorf("failed to store CABundle to local file: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts = append(opts, getter.WithTLSClientConfig("", "", caFile))
|
||||||
}
|
}
|
||||||
if repo.Spec.BasicAuth != nil {
|
if repo.Spec.BasicAuth != nil {
|
||||||
options = append(options, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
||||||
}
|
}
|
||||||
chartGetter, err = getter.NewHTTPGetter(options...)
|
chartGetter, err = getter.NewHTTPGetter(opts...)
|
||||||
}
|
if err != nil {
|
||||||
if err != nil {
|
api.HandleInternalError(response, request, fmt.Errorf("failed to create chart getter: %v", err))
|
||||||
api.HandleInternalError(response, request, fmt.Errorf("failed to create chart getter: %v", err))
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
api.HandleInternalError(response, request, fmt.Errorf("cannot support chartURL %s, it's schame should be: oci,http,https", extensionVersion.Spec.ChartURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,3 +128,39 @@ func (h *handler) ListFiles(request *restful.Request, response *restful.Response
|
|||||||
|
|
||||||
_ = response.WriteEntity(files)
|
_ = response.WriteEntity(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// storeCAFile in local file from caTemplate.
|
||||||
|
func storeCAFile(caBundle string, repoName string) (string, error) {
|
||||||
|
var buff = &bytes.Buffer{}
|
||||||
|
tmpl, err := template.New("repositoryCABundle").Parse(caTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(buff, map[string]string{
|
||||||
|
"TempDIR": os.TempDir(),
|
||||||
|
"RepositoryName": repoName,
|
||||||
|
}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
caFile := buff.String()
|
||||||
|
if _, err := os.Stat(filepath.Dir(caFile)); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(caFile), os.ModePerm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := base64.StdEncoding.DecodeString(caBundle)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(caFile, data, os.ModePerm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return caFile, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"helm.sh/helm/v3/pkg/action"
|
"helm.sh/helm/v3/pkg/action"
|
||||||
"helm.sh/helm/v3/pkg/kube"
|
"helm.sh/helm/v3/pkg/kube"
|
||||||
|
"helm.sh/helm/v3/pkg/registry"
|
||||||
helmrelease "helm.sh/helm/v3/pkg/release"
|
helmrelease "helm.sh/helm/v3/pkg/release"
|
||||||
"helm.sh/helm/v3/pkg/storage"
|
"helm.sh/helm/v3/pkg/storage"
|
||||||
"helm.sh/helm/v3/pkg/storage/driver"
|
"helm.sh/helm/v3/pkg/storage/driver"
|
||||||
@@ -340,6 +341,10 @@ func HelmPull(u string, cred appv2.RepoCredential) (*bytes.Buffer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadRepoIndex(u string, cred appv2.RepoCredential) (idx helmrepo.IndexFile, err error) {
|
func LoadRepoIndex(u string, cred appv2.RepoCredential) (idx helmrepo.IndexFile, err error) {
|
||||||
|
if registry.IsOCI(u) {
|
||||||
|
return LoadRepoIndexFromOci(u, cred)
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(u, "/") {
|
if !strings.HasSuffix(u, "/") {
|
||||||
u = fmt.Sprintf("%s/index.yaml", u)
|
u = fmt.Sprintf("%s/index.yaml", u)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
199
pkg/simple/client/application/oci.go
Normal file
199
pkg/simple/client/application/oci.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/pkg/registry"
|
||||||
|
helmrepo "helm.sh/helm/v3/pkg/repo"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
appv2 "kubesphere.io/api/application/v2"
|
||||||
|
|
||||||
|
"kubesphere.io/kubesphere/pkg/simple/client/oci"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HelmPullFromOci(u string, cred appv2.RepoCredential) ([]byte, error) {
|
||||||
|
if !registry.IsOCI(u) {
|
||||||
|
return nil, fmt.Errorf("invalid oci URL format: %s", u)
|
||||||
|
}
|
||||||
|
_, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("invalid oci chart URL format: %s, err:%v", u, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := newOCIRegistryClient(u, cred)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pullRef := strings.TrimPrefix(u, fmt.Sprintf("%s://", registry.OCIScheme))
|
||||||
|
pullResult, err := client.Pull(pullRef)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("An error occurred to pull chart from repository: %s,err:%v", pullRef, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pullResult.Chart.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadRepoIndexFromOci(u string, cred appv2.RepoCredential) (idx helmrepo.IndexFile, err error) {
|
||||||
|
if !registry.IsOCI(u) {
|
||||||
|
return idx, fmt.Errorf("invalid oci URL format: %s", u)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("invalid repo URL format: %s, err:%v", u, err)
|
||||||
|
return idx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoCharts, err := GetRepoChartsFromOci(parsedURL, cred)
|
||||||
|
if err != nil {
|
||||||
|
return idx, err
|
||||||
|
}
|
||||||
|
if len(repoCharts) == 0 {
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := newOCIRegistryClient(u, cred)
|
||||||
|
if err != nil {
|
||||||
|
return idx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
index := helmrepo.NewIndexFile()
|
||||||
|
for _, repoChart := range repoCharts {
|
||||||
|
tags, err := client.Tags(fmt.Sprintf("%s/%s", parsedURL.Host, repoChart))
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("An error occurred to load tags from repository: %s/%s,err:%v", parsedURL.Host, repoChart, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(tags) == 0 {
|
||||||
|
klog.Errorf("Unable to locate any tags in provided repository: %s/%s,err:%v", parsedURL.Host, repoChart, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
pullRef := fmt.Sprintf("%s/%s:%s", parsedURL.Host, repoChart, tag)
|
||||||
|
pullResult, err := client.Pull(pullRef)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("An error occurred to pull chart from repository: %s,err:%v", pullRef, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
baseUrl := fmt.Sprintf("%s://%s", registry.OCIScheme, pullRef)
|
||||||
|
hash := strings.TrimPrefix(pullResult.Chart.Digest, "sha256:")
|
||||||
|
if err := index.MustAdd(pullResult.Chart.Meta, "", baseUrl, hash); err != nil {
|
||||||
|
klog.Errorf("failed adding chart metadata to index with repository: %s,err:%v", pullRef, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index.SortEntries()
|
||||||
|
|
||||||
|
return *index, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRepoChartsFromOci(parsedURL *url.URL, cred appv2.RepoCredential) ([]string, error) {
|
||||||
|
if parsedURL == nil {
|
||||||
|
return nil, errors.New("missing parsedURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
skipTLS := true
|
||||||
|
if cred.InsecureSkipTLSVerify != nil && !*cred.InsecureSkipTLSVerify {
|
||||||
|
skipTLS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := oci.NewRegistry(parsedURL.Host,
|
||||||
|
oci.WithTimeout(5*time.Second),
|
||||||
|
oci.WithBasicAuth(cred.Username, cred.Password),
|
||||||
|
oci.WithInsecureSkipVerifyTLS(skipTLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
repoPath := strings.TrimSuffix(parsedURL.Path, "/")
|
||||||
|
repoPath = strings.TrimPrefix(repoPath, "/")
|
||||||
|
var repoCharts []string
|
||||||
|
err = reg.Repositories(ctx, "", func(repos []string) error {
|
||||||
|
cutPrefix := repoPath
|
||||||
|
if cutPrefix != "" {
|
||||||
|
cutPrefix = cutPrefix + "/"
|
||||||
|
}
|
||||||
|
for _, repo := range repos {
|
||||||
|
if subRepo, found := strings.CutPrefix(repo, cutPrefix); found && subRepo != "" {
|
||||||
|
if !strings.Contains(subRepo, "/") {
|
||||||
|
repoCharts = append(repoCharts, fmt.Sprintf("%s/%s", repoPath, subRepo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repoCharts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOCIRegistryClient(u string, cred appv2.RepoCredential) (*registry.Client, error) {
|
||||||
|
parsedURL, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("invalid oci repo URL format: %s, err:%v", u, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
skipTLS := true
|
||||||
|
if cred.InsecureSkipTLSVerify != nil && !*cred.InsecureSkipTLSVerify {
|
||||||
|
skipTLS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := oci.NewRegistry(parsedURL.Host,
|
||||||
|
oci.WithTimeout(5*time.Second),
|
||||||
|
oci.WithBasicAuth(cred.Username, cred.Password),
|
||||||
|
oci.WithInsecureSkipVerifyTLS(skipTLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTLS},
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []registry.ClientOption{registry.ClientOptHTTPClient(&http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
})}
|
||||||
|
|
||||||
|
if reg.PlainHTTP {
|
||||||
|
opts = append(opts, registry.ClientOptPlainHTTP())
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := registry.NewClient(opts...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cred.Username != "" || cred.Password != "" {
|
||||||
|
err = client.Login(parsedURL.Host,
|
||||||
|
registry.LoginOptBasicAuth(cred.Username, cred.Password),
|
||||||
|
registry.LoginOptInsecure(reg.PlainHTTP))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
65
pkg/simple/client/application/oci_test.go
Normal file
65
pkg/simple/client/application/oci_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
appv2 "kubesphere.io/api/application/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadRepoIndexFromOci(t *testing.T) {
|
||||||
|
testRepos := []string{"helmcharts/nginx", "helmcharts/test-api", "helmcharts/test-ui", "helmcharts/demo-app"}
|
||||||
|
testTags := []string{"1.0.0", "1.2.0", "1.0.3"}
|
||||||
|
testRepo := testRepos[1]
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet && (r.URL.Path == "/v2" || r.URL.Path == "/v2/") {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method == http.MethodGet && r.URL.Path == "/v2/_catalog" {
|
||||||
|
result := struct {
|
||||||
|
Repositories []string `json:"repositories"`
|
||||||
|
}{
|
||||||
|
Repositories: testRepos,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||||
|
t.Errorf("failed to write response: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method == http.MethodGet && r.URL.Path == fmt.Sprintf("/v2/%s/tags/list", testRepo) {
|
||||||
|
result := struct {
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}{
|
||||||
|
Tags: testTags,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||||
|
t.Errorf("failed to write response: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("unexpected access: %s %s", r.Method, r.URL)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
uri, err := url.Parse(ts.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid test http server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("oci://%s/helmcharts", uri.Host)
|
||||||
|
cred := appv2.RepoCredential{
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
}
|
||||||
|
index, err := LoadRepoIndexFromOci(url, cred)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("LoadRepoIndexFromOci() error: %s", err)
|
||||||
|
}
|
||||||
|
t.Log(len(index.Entries))
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/pkg/registry"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
@@ -145,6 +146,9 @@ func DownLoadChart(cli runtimeclient.Client, pullUrl, repoName string) (data []b
|
|||||||
klog.Errorf("failed to get app repo, err: %v", err)
|
klog.Errorf("failed to get app repo, err: %v", err)
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
if registry.IsOCI(pullUrl) {
|
||||||
|
return HelmPullFromOci(pullUrl, repo.Spec.Credential)
|
||||||
|
}
|
||||||
buf, err := HelmPull(pullUrl, repo.Spec.Credential)
|
buf, err := HelmPull(pullUrl, repo.Spec.Credential)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("load chart failed, error: %s", err)
|
klog.Errorf("load chart failed, error: %s", err)
|
||||||
|
|||||||
64
pkg/simple/client/oci/errors.go
Normal file
64
pkg/simple/client/oci/errors.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxErrorBytes int64 = 8 * 1024 // 8 KiB
|
||||||
|
|
||||||
|
// requestError contains a single error.
|
||||||
|
type requestError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a error string describing the error.
|
||||||
|
func (e requestError) Error() string {
|
||||||
|
code := strings.Map(func(r rune) rune {
|
||||||
|
if r == '_' {
|
||||||
|
return ' '
|
||||||
|
}
|
||||||
|
return unicode.ToLower(r)
|
||||||
|
}, e.Code)
|
||||||
|
if e.Message == "" {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: %s", code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestErrors is a bundle of requestError.
|
||||||
|
type requestErrors []requestError
|
||||||
|
|
||||||
|
// Error returns a error string describing the error.
|
||||||
|
func (errs requestErrors) Error() string {
|
||||||
|
switch len(errs) {
|
||||||
|
case 0:
|
||||||
|
return "<nil>"
|
||||||
|
case 1:
|
||||||
|
return errs[0].Error()
|
||||||
|
}
|
||||||
|
var errmsgs []string
|
||||||
|
for _, err := range errs {
|
||||||
|
errmsgs = append(errmsgs, err.Error())
|
||||||
|
}
|
||||||
|
return strings.Join(errmsgs, "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseErrorResponse(resp *http.Response) error {
|
||||||
|
var errmsg string
|
||||||
|
var body struct {
|
||||||
|
Errors requestErrors `json:"errors"`
|
||||||
|
}
|
||||||
|
lr := io.LimitReader(resp.Body, maxErrorBytes)
|
||||||
|
if err := json.NewDecoder(lr).Decode(&body); err == nil && len(body.Errors) > 0 {
|
||||||
|
errmsg = body.Errors.Error()
|
||||||
|
} else {
|
||||||
|
errmsg = http.StatusText(resp.StatusCode)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s %q: unexpected status code %d: %s", resp.Request.Method, resp.Request.URL, resp.StatusCode, errmsg)
|
||||||
|
}
|
||||||
96
pkg/simple/client/oci/oci_test.go
Normal file
96
pkg/simple/client/oci/oci_test.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"oras.land/oras-go/pkg/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegistry_Api(t *testing.T) {
|
||||||
|
testRepos := []string{"helmcharts/nginx", "helmcharts/test-api", "helmcharts/test-ui", "helmcharts/demo-app"}
|
||||||
|
testTags := []string{"1.0.0", "1.2.0", "1.0.3"}
|
||||||
|
testRepo := testRepos[1]
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet && (r.URL.Path == "/v2" || r.URL.Path == "/v2/") {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method == http.MethodGet && r.URL.Path == "/v2/_catalog" {
|
||||||
|
result := struct {
|
||||||
|
Repositories []string `json:"repositories"`
|
||||||
|
}{
|
||||||
|
Repositories: testRepos,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||||
|
t.Errorf("failed to write response: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method == http.MethodGet && r.URL.Path == fmt.Sprintf("/v2/%s/tags/list", testRepo) {
|
||||||
|
result := struct {
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}{
|
||||||
|
Tags: testTags,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||||
|
t.Errorf("failed to write response: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("unexpected access: %s %s", r.Method, r.URL)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
uri, err := url.Parse(ts.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid test http server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := NewRegistry(uri.Host,
|
||||||
|
WithTimeout(5*time.Second),
|
||||||
|
WithBasicAuth("", ""),
|
||||||
|
WithInsecureSkipVerifyTLS(true))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewRegistry() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
err = reg.Ping(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Registry.Ping() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var registryTags []string
|
||||||
|
|
||||||
|
repo, err := reg.Repository(ctx, testRepo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Registry.Repository() error = %v", err)
|
||||||
|
}
|
||||||
|
registryTags, err = registry.Tags(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Registry.Repository().Tags() error = %v", err)
|
||||||
|
}
|
||||||
|
t.Log(len(registryTags))
|
||||||
|
|
||||||
|
err = reg.Repositories(ctx, "", func(repos []string) error {
|
||||||
|
for _, repo := range repos {
|
||||||
|
if subRepo, found := strings.CutPrefix(repo, ""); found {
|
||||||
|
t.Log(subRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Registry.Repositories() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
221
pkg/simple/client/oci/registry.go
Normal file
221
pkg/simple/client/oci/registry.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"oras.land/oras-go/pkg/registry"
|
||||||
|
"oras.land/oras-go/pkg/registry/remote"
|
||||||
|
"oras.land/oras-go/pkg/registry/remote/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RepositoryOptions remote.Repository
|
||||||
|
type RegistryOption func(*Registry)
|
||||||
|
|
||||||
|
// Registry is an HTTP client to a remote registry by oras-go 2.x.
|
||||||
|
// Registry with authentication requires an administrator account.
|
||||||
|
type Registry struct {
|
||||||
|
RepositoryOptions
|
||||||
|
|
||||||
|
RepositoryListPageSize int
|
||||||
|
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
timeout time.Duration
|
||||||
|
insecureSkipVerifyTLS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry(name string, options ...RegistryOption) (*Registry, error) {
|
||||||
|
ref := registry.Reference{
|
||||||
|
Registry: name,
|
||||||
|
}
|
||||||
|
if err := ref.ValidateRegistry(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reg := &Registry{RepositoryOptions: RepositoryOptions{
|
||||||
|
Reference: ref,
|
||||||
|
}}
|
||||||
|
for _, option := range options {
|
||||||
|
option(reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := http.Header{}
|
||||||
|
headers.Set("User-Agent", "kubesphere.io")
|
||||||
|
reg.Client = &auth.Client{
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: reg.timeout,
|
||||||
|
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: reg.insecureSkipVerifyTLS}},
|
||||||
|
},
|
||||||
|
Header: headers,
|
||||||
|
Credential: func(_ context.Context, _ string) (auth.Credential, error) {
|
||||||
|
if reg.username == "" && reg.password == "" {
|
||||||
|
return auth.EmptyCredential, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.Credential{
|
||||||
|
Username: reg.username,
|
||||||
|
Password: reg.password,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := reg.IsPlainHttp()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBasicAuth(username, password string) RegistryOption {
|
||||||
|
return func(reg *Registry) {
|
||||||
|
reg.username = username
|
||||||
|
reg.password = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTimeout(timeout time.Duration) RegistryOption {
|
||||||
|
return func(reg *Registry) {
|
||||||
|
reg.timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) RegistryOption {
|
||||||
|
return func(reg *Registry) {
|
||||||
|
reg.insecureSkipVerifyTLS = insecureSkipVerifyTLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) client() remote.Client {
|
||||||
|
if r.Client == nil {
|
||||||
|
return auth.DefaultClient
|
||||||
|
}
|
||||||
|
return r.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) do(req *http.Request) (*http.Response, error) {
|
||||||
|
return r.client().Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) IsPlainHttp() (bool, error) {
|
||||||
|
schemaProbeList := []bool{false, true}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, probe := range schemaProbeList {
|
||||||
|
r.PlainHTTP = probe
|
||||||
|
err = r.Ping(context.Background())
|
||||||
|
if err == nil {
|
||||||
|
return probe, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.PlainHTTP, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Ping(ctx context.Context) error {
|
||||||
|
url := buildRegistryBaseURL(r.PlainHTTP, r.Reference)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := r.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
return nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return errors.New("not found")
|
||||||
|
default:
|
||||||
|
return ParseErrorResponse(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Repositories(ctx context.Context, last string, fn func(repos []string) error) error {
|
||||||
|
url := buildRegistryCatalogURL(r.PlainHTTP, r.Reference)
|
||||||
|
var err error
|
||||||
|
for err == nil {
|
||||||
|
url, err = r.repositories(ctx, last, fn, url)
|
||||||
|
// clear `last` for subsequent pages
|
||||||
|
last = ""
|
||||||
|
}
|
||||||
|
if err != errNoLink {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) repositories(ctx context.Context, last string, fn func(repos []string) error, url string) (string, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if r.RepositoryListPageSize > 0 || last != "" {
|
||||||
|
q := req.URL.Query()
|
||||||
|
if r.RepositoryListPageSize > 0 {
|
||||||
|
q.Set("n", strconv.Itoa(r.RepositoryListPageSize))
|
||||||
|
}
|
||||||
|
if last != "" {
|
||||||
|
q.Set("last", last)
|
||||||
|
}
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
resp, err := r.do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", ParseErrorResponse(resp)
|
||||||
|
}
|
||||||
|
var page struct {
|
||||||
|
Repositories []string `json:"repositories"`
|
||||||
|
}
|
||||||
|
lr := limitReader(resp.Body, r.MaxMetadataBytes)
|
||||||
|
if err := json.NewDecoder(lr).Decode(&page); err != nil {
|
||||||
|
return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err)
|
||||||
|
}
|
||||||
|
if err := fn(page.Repositories); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseLink(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Repository(ctx context.Context, name string) (registry.Repository, error) {
|
||||||
|
ref := registry.Reference{
|
||||||
|
Registry: r.Reference.Registry,
|
||||||
|
Repository: name,
|
||||||
|
}
|
||||||
|
if err := ref.ValidateRepository(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repo := r.repository((*remote.Repository)(&r.RepositoryOptions))
|
||||||
|
repo.Reference = ref
|
||||||
|
return repo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) repository(repo *remote.Repository) *remote.Repository {
|
||||||
|
return &remote.Repository{
|
||||||
|
Client: repo.Client,
|
||||||
|
Reference: repo.Reference,
|
||||||
|
PlainHTTP: repo.PlainHTTP,
|
||||||
|
ManifestMediaTypes: slices.Clone(repo.ManifestMediaTypes),
|
||||||
|
TagListPageSize: repo.TagListPageSize,
|
||||||
|
ReferrerListPageSize: repo.ReferrerListPageSize,
|
||||||
|
MaxMetadataBytes: repo.MaxMetadataBytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
22
pkg/simple/client/oci/url.go
Normal file
22
pkg/simple/client/oci/url.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"oras.land/oras-go/pkg/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildScheme(plainHTTP bool) string {
|
||||||
|
if plainHTTP {
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRegistryBaseURL(plainHTTP bool, ref registry.Reference) string {
|
||||||
|
return fmt.Sprintf("%s://%s/v2/", buildScheme(plainHTTP), ref.Host())
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRegistryCatalogURL(plainHTTP bool, ref registry.Reference) string {
|
||||||
|
return fmt.Sprintf("%s://%s/v2/_catalog", buildScheme(plainHTTP), ref.Host())
|
||||||
|
}
|
||||||
48
pkg/simple/client/oci/utils.go
Normal file
48
pkg/simple/client/oci/utils.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package oci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultMaxMetadataBytes specifies the default limit on how many response
|
||||||
|
// bytes are allowed in the server's response to the metadata APIs.
|
||||||
|
// See also: Repository.MaxMetadataBytes
|
||||||
|
var defaultMaxMetadataBytes int64 = 4 * 1024 * 1024 // 4 MiB
|
||||||
|
|
||||||
|
// errNoLink is returned by parseLink() when no Link header is present.
|
||||||
|
var errNoLink = errors.New("no Link header in response")
|
||||||
|
|
||||||
|
// parseLink returns the URL of the response's "Link" header, if present.
|
||||||
|
func parseLink(resp *http.Response) (string, error) {
|
||||||
|
link := resp.Header.Get("Link")
|
||||||
|
if link == "" {
|
||||||
|
return "", errNoLink
|
||||||
|
}
|
||||||
|
if link[0] != '<' {
|
||||||
|
return "", fmt.Errorf("invalid next link %q: missing '<'", link)
|
||||||
|
}
|
||||||
|
if i := strings.IndexByte(link, '>'); i == -1 {
|
||||||
|
return "", fmt.Errorf("invalid next link %q: missing '>'", link)
|
||||||
|
} else {
|
||||||
|
link = link[1:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
linkURL, err := resp.Request.URL.Parse(link)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return linkURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// limitReader returns a Reader that reads from r but stops with EOF after n
|
||||||
|
// bytes. If n is zero, defaultMaxMetadataBytes is used.
|
||||||
|
func limitReader(r io.Reader, n int64) io.Reader {
|
||||||
|
if n == 0 {
|
||||||
|
n = defaultMaxMetadataBytes
|
||||||
|
}
|
||||||
|
return io.LimitReader(r, n)
|
||||||
|
}
|
||||||
@@ -18,14 +18,17 @@ type BasicAuth struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RepositorySpec struct {
|
type RepositorySpec struct {
|
||||||
|
// DEPRECATED: the field will remove in future versions, please use url.
|
||||||
Image string `json:"image,omitempty"`
|
Image string `json:"image,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
|
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
|
||||||
UpdateStrategy *UpdateStrategy `json:"updateStrategy,omitempty"`
|
UpdateStrategy *UpdateStrategy `json:"updateStrategy,omitempty"`
|
||||||
// +optional The caBundle (base64 string) is used in helmExecutor to verify the helm server.
|
// The caBundle (base64 string) is used in helmExecutor to verify the helm server.
|
||||||
// if the caBundle is empty, use --insecure-skip-tls-verify.
|
// +optional
|
||||||
CABundle string `json:"caBundle,omitempty"`
|
CABundle string `json:"caBundle,omitempty"`
|
||||||
|
// --insecure-skip-tls-verify. default false
|
||||||
|
Insecure bool `json:"insecure,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepositoryStatus struct {
|
type RepositoryStatus struct {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func LoadRepoIndex(ctx context.Context, u string, cred RepoCredential) (*helmrep
|
|||||||
u = fmt.Sprintf("%s%s", u, IndexYaml)
|
u = fmt.Sprintf("%s%s", u, IndexYaml)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := loadData(ctx, u, cred)
|
resp, err := LoadData(ctx, u, cred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ func loadIndex(data []byte) (*helmrepo.IndexFile, error) {
|
|||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadData(ctx context.Context, u string, cred RepoCredential) (*bytes.Buffer, error) {
|
func LoadData(ctx context.Context, u string, cred RepoCredential) (*bytes.Buffer, error) {
|
||||||
parsedURL, err := url.Parse(u)
|
parsedURL, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -81,18 +81,11 @@ func loadData(ctx context.Context, u string, cred RepoCredential) (*bytes.Buffer
|
|||||||
|
|
||||||
resp = bytes.NewBuffer(data)
|
resp = bytes.NewBuffer(data)
|
||||||
} else {
|
} else {
|
||||||
skipTLS := true
|
|
||||||
if cred.InsecureSkipTLSVerify != nil && !*cred.InsecureSkipTLSVerify {
|
|
||||||
skipTLS = false
|
|
||||||
}
|
|
||||||
|
|
||||||
indexURL := parsedURL.String()
|
|
||||||
// TODO add user-agent
|
// TODO add user-agent
|
||||||
g, _ := getter.NewHTTPGetter()
|
g, _ := getter.NewHTTPGetter()
|
||||||
resp, err = g.Get(indexURL,
|
resp, err = g.Get(parsedURL.String(),
|
||||||
getter.WithTimeout(5*time.Minute),
|
getter.WithTimeout(5*time.Minute),
|
||||||
getter.WithURL(u),
|
getter.WithInsecureSkipVerifyTLS(cred.InsecureSkipTLSVerify),
|
||||||
getter.WithInsecureSkipVerifyTLS(skipTLS),
|
|
||||||
getter.WithTLSClientConfig(cred.CertFile, cred.KeyFile, cred.CAFile),
|
getter.WithTLSClientConfig(cred.CertFile, cred.KeyFile, cred.CAFile),
|
||||||
getter.WithBasicAuth(cred.Username, cred.Password),
|
getter.WithBasicAuth(cred.Username, cred.Password),
|
||||||
)
|
)
|
||||||
@@ -135,7 +128,7 @@ type RepoCredential struct {
|
|||||||
// verify certificates of HTTPS-enabled servers using this CA bundle
|
// verify certificates of HTTPS-enabled servers using this CA bundle
|
||||||
CAFile string `json:"caFile,omitempty"`
|
CAFile string `json:"caFile,omitempty"`
|
||||||
// skip tls certificate checks for the repository, default is ture
|
// skip tls certificate checks for the repository, default is ture
|
||||||
InsecureSkipTLSVerify *bool `json:"insecureSkipTLSVerify,omitempty"`
|
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
|
||||||
|
|
||||||
S3Config `json:",inline"`
|
S3Config `json:",inline"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user