Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Login to Aliyun
|
||||
- name: Login to HUAWEICLOUD
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.cn-beijing.aliyuncs.com
|
||||
username: ${{ secrets.ALIYUNCS_USERNAME }}
|
||||
password: ${{ secrets.ALIYUNCS_PASSWORD }}
|
||||
registry: swr.cn-southwest-2.myhuaweicloud.com
|
||||
username: ${{ secrets.HUAWEICLOUD_USERNAME }}
|
||||
password: ${{ secrets.HUAWEICLOUD_PASSWORD }}
|
||||
|
||||
- name: Login to DOCKER
|
||||
uses: docker/login-action@v3
|
||||
@@ -56,17 +56,18 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
if: steps.chose_registry.outputs.env == 'prod'
|
||||
with:
|
||||
context: ${{ github.workspace }}
|
||||
file: build/ks-apiserver/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
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
|
||||
uses: docker/build-push-action@v6
|
||||
if: steps.chose_registry.outputs.env == 'dev'
|
||||
with:
|
||||
context: ${{ github.workspace }}
|
||||
file: build/ks-apiserver/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
@@ -77,19 +78,34 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
if: steps.chose_registry.outputs.env == 'prod'
|
||||
with:
|
||||
context: ${{ github.workspace }}
|
||||
file: build/ks-controller-manager/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
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
|
||||
uses: docker/build-push-action@v6
|
||||
if: steps.chose_registry.outputs.env == 'dev'
|
||||
with:
|
||||
context: ${{ github.workspace }}
|
||||
file: build/ks-controller-manager/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
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:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -5,36 +7,64 @@ on:
|
||||
- 'config/ks-core/**'
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-*'
|
||||
tags:
|
||||
- 'helm-chart-*'
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
sync-chart:
|
||||
runs-on: self-runner-kubesphere
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Check out kubesphere/kubesphere
|
||||
uses: actions/checkout@v4
|
||||
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
|
||||
|
||||
- name: Setup SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
uses: MrSquaare/ssh-setup-action@v3
|
||||
with:
|
||||
working-directory: helm-charts
|
||||
host: github.com
|
||||
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.name "ks-ci-bot"
|
||||
git clone git@github.com:kubesphere/helm-charts.git
|
||||
rm -rf helm-charts/src/test/ks-core
|
||||
cp -r config/ks-core helm-charts/src/test/
|
||||
cd helm-charts/
|
||||
git remote add ks git@github.com:kubesphere/helm-charts.git
|
||||
git fetch ks master
|
||||
git checkout -b sync/ks-core/${GITHUB_REF#refs/*/} ks/master
|
||||
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 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 }}
|
||||
run: |
|
||||
cd helm-charts/
|
||||
if [[ $(gh pr ls -H sync/ks-core -B master) == "" ]]; then
|
||||
gh pr create -H sync/ks-core -B master --title "Update ks-core helm chart" --body "Update ks-core helm chart"
|
||||
cd helm-charts
|
||||
if [[ $(gh pr ls -R kubesphere/helm-charts -A ks-ci-bot -H sync/ks-core/${GITHUB_REF#refs/*/} -B master) == "" ]]; then
|
||||
# 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
|
||||
|
||||
9
Makefile
9
Makefile
@@ -2,9 +2,6 @@
|
||||
CRD_OPTIONS ?= "crd:allowDangerousTypes=true"
|
||||
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)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
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
|
||||
|
||||
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 --app-version=${APP_VERSION} --version=0.1.0 -d ./bin
|
||||
helm package config/ks-core -d ./bin
|
||||
|
||||
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
|
||||
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.
|
||||
- kubectl delete ns kubesphere-controls-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
|
||||
test: vet test-env ;$(info $(M)...Begin to run tests.) @ ## Run tests.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build
|
||||
FROM golang:1.20.7 AS build_context
|
||||
FROM golang:1.21.5 AS build_context
|
||||
|
||||
ENV OUTDIR=/out
|
||||
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
|
||||
|
||||
# Build
|
||||
FROM golang:1.20.7 AS build_context
|
||||
FROM golang:1.21.5 AS build_context
|
||||
|
||||
ENV OUTDIR=/out
|
||||
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
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 1.1.0
|
||||
version: 1.1.2
|
||||
|
||||
# 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
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: "v4.1.1"
|
||||
appVersion: "v4.1.2"
|
||||
|
||||
dependencies:
|
||||
- name: redis-ha
|
||||
|
||||
@@ -73,6 +73,8 @@ spec:
|
||||
type: string
|
||||
appVersionID:
|
||||
type: string
|
||||
icon:
|
||||
type: string
|
||||
values:
|
||||
format: byte
|
||||
type: string
|
||||
|
||||
@@ -50,12 +50,18 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
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
|
||||
description:
|
||||
type: string
|
||||
image:
|
||||
description: 'DEPRECATED: the field will remove in future versions,
|
||||
please use url.'
|
||||
type: string
|
||||
insecure:
|
||||
description: --insecure-skip-tls-verify. default false
|
||||
type: boolean
|
||||
updateStrategy:
|
||||
properties:
|
||||
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 "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
|
||||
kind: Job
|
||||
@@ -23,10 +53,10 @@ spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
serviceAccountName: {{ include "ks-core.serviceAccountName" . }}
|
||||
serviceAccountName: "{{ .Release.Name }}-pre-upgrade-crd"
|
||||
containers:
|
||||
- name: crd-install
|
||||
image: {{ template "preUpgrade.image" . }}
|
||||
image: {{ template "kubectl.image" . }}
|
||||
command:
|
||||
- /bin/bash
|
||||
- /scripts/install.sh
|
||||
@@ -34,7 +64,7 @@ spec:
|
||||
volumeMounts:
|
||||
- mountPath: /scripts
|
||||
name: scripts
|
||||
resources: {{- toYaml .Values.preUpgrade.resources | nindent 12 }}
|
||||
resources: {{- toYaml .Values.kubectl.resources | nindent 12 }}
|
||||
volumes:
|
||||
- name: scripts
|
||||
configMap:
|
||||
@@ -1,12 +1,11 @@
|
||||
# Default values for ks-crds.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
preUpgrade:
|
||||
kubectl:
|
||||
image:
|
||||
registry: ""
|
||||
repository: kubesphereio/kubectl
|
||||
tag: "v1.27.12"
|
||||
repository: kubesphere/kubectl
|
||||
tag: "v1.27.16"
|
||||
pullPolicy: IfNotPresent
|
||||
resources:
|
||||
limits:
|
||||
@@ -14,4 +13,4 @@ preUpgrade:
|
||||
memory: 1024Mi
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 100Mi
|
||||
memory: 100Mi
|
||||
|
||||
@@ -2,55 +2,9 @@
|
||||
|
||||
# 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'
|
||||
|
||||
for resource in $EXTENSION_RELATED_RESOURCES;do
|
||||
echo "kubectl delete $resource -l kubesphere.io/extension-ref --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) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "preUpgrade.image" -}}
|
||||
{{ include "common.images.image" (dict "imageRoot" .Values.preUpgrade.image "global" .Values.global) }}
|
||||
{{- define "extensions_museum.image" -}}
|
||||
{{ include "common.images.image" (dict "imageRoot" .Values.ksExtensionRepository.image "global" .Values.global) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- 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) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "extensions_museum.imagePullSecrets" -}}
|
||||
{{- include "common.images.pullSecrets" (dict "images" (list .Values.ksExtensionRepository.image) "global" .Values.global) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "common.images.pullSecrets" -}}
|
||||
{{- $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.20.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 }}
|
||||
kubesphere:
|
||||
tls: {{ .Values.internalTLS }}
|
||||
{{- if and .Values.cloud.enabled (eq (include "role" .) "host") }}
|
||||
{{- if and .Values.telemetry.enabled (eq (include "role" .) "host") }}
|
||||
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"
|
||||
{{- else if and .Values.cloud.customEnv .Values.cloud.customEnv.url }}
|
||||
ksCloudURL: {{ $.Values.cloud.customEnv.url | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.ha.enabled -}}
|
||||
{{- if .Values.ha.cache }}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{{- $kubeVersion := .Capabilities.KubeVersion }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
@@ -8,15 +7,6 @@ metadata:
|
||||
"helm.sh/hook-weight": "-1"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
|
||||
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 }}
|
||||
|
||||
---
|
||||
@@ -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
|
||||
kind: Job
|
||||
metadata:
|
||||
@@ -76,8 +60,6 @@ spec:
|
||||
command:
|
||||
- /bin/bash
|
||||
- /scripts/post-delete.sh
|
||||
- '{{ join " " $crdNameList }}'
|
||||
- /scripts/map.yaml
|
||||
volumeMounts:
|
||||
- mountPath: /scripts
|
||||
name: scripts
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## @param global.tag Global Docker image tag
|
||||
global:
|
||||
imageRegistry: docker.io
|
||||
tag: v4.1.1
|
||||
tag: v4.1.2
|
||||
imagePullSecrets: []
|
||||
|
||||
## @param nameOverride String to partially override common.names.fullname
|
||||
@@ -320,7 +320,10 @@ nodeShell:
|
||||
tag: "v1.27.16"
|
||||
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
|
||||
|
||||
extension:
|
||||
@@ -418,7 +421,7 @@ redisHA:
|
||||
- ""
|
||||
|
||||
ksCRDs:
|
||||
preUpgrade:
|
||||
kubectl:
|
||||
image:
|
||||
registry: ""
|
||||
repository: kubesphere/kubectl
|
||||
@@ -431,3 +434,12 @@ ksCRDs:
|
||||
requests:
|
||||
cpu: 20m
|
||||
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}"
|
||||
else
|
||||
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
|
||||
done
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
"k8s.io/klog/v2"
|
||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||
"kubesphere.io/api/iam/v1beta1"
|
||||
@@ -398,6 +399,9 @@ func (a *auditing) eventToBytes(events []*Event) [][]byte {
|
||||
return res
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = &ResponseCapture{}
|
||||
var _ responsewriter.UserProvidedDecorator = &ResponseCapture{}
|
||||
|
||||
type ResponseCapture struct {
|
||||
http.ResponseWriter
|
||||
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 {
|
||||
return c.ResponseWriter.Header()
|
||||
}
|
||||
@@ -425,9 +433,6 @@ func (c *ResponseCapture) Write(data []byte) (int, error) {
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if flusher, ok := c.ResponseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ package filters
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"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 {
|
||||
resp := auditing.NewResponseCapture(w)
|
||||
a.next.ServeHTTP(resp, req)
|
||||
a.next.ServeHTTP(responsewriter.WrapForHTTP1Or2(resp), req)
|
||||
go a.LogResponseObject(event, resp)
|
||||
} else {
|
||||
a.next.ServeHTTP(w, req)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/metrics"
|
||||
@@ -20,12 +21,19 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/utils/iputil"
|
||||
)
|
||||
|
||||
var _ http.ResponseWriter = &metaResponseWriter{}
|
||||
var _ responsewriter.UserProvidedDecorator = &metaResponseWriter{}
|
||||
|
||||
type metaResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
size int
|
||||
}
|
||||
|
||||
func (r *metaResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return r.ResponseWriter
|
||||
}
|
||||
|
||||
func newMetaResponseWriter(w http.ResponseWriter) *metaResponseWriter {
|
||||
return &metaResponseWriter{
|
||||
ResponseWriter: w,
|
||||
@@ -48,9 +56,6 @@ func (r *metaResponseWriter) Write(b []byte) (int, error) {
|
||||
if err != nil {
|
||||
return size, err
|
||||
}
|
||||
if flusher, ok := r.ResponseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
@@ -66,7 +71,8 @@ func WithGlobalFilter(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
wrapper := newMetaResponseWriter(w)
|
||||
start := time.Now()
|
||||
handler.ServeHTTP(wrapper, req)
|
||||
|
||||
handler.ServeHTTP(responsewriter.WrapForHTTP1Or2(wrapper), req)
|
||||
elapsedTime := time.Since(start)
|
||||
|
||||
// Record metrics for each request
|
||||
@@ -94,7 +100,7 @@ func WithGlobalFilter(handler http.Handler) http.Handler {
|
||||
req.Proto,
|
||||
wrapper.statusCode,
|
||||
wrapper.size,
|
||||
elapsedTime.Microseconds(),
|
||||
elapsedTime.Milliseconds(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -157,8 +157,7 @@ func (h *Helper) AggregationRole(ctx context.Context, ruleOwner RuleOwner, recor
|
||||
if !cover {
|
||||
needUpdate = true
|
||||
newRule := append(ruleOwner.GetRules(), uncovered...)
|
||||
squashedRules := SquashRules(len(newRule), newRule)
|
||||
ruleOwner.SetRules(squashedRules)
|
||||
ruleOwner.SetRules(newRule)
|
||||
}
|
||||
|
||||
if !templateNamesEqual {
|
||||
|
||||
@@ -149,7 +149,8 @@ func ruleCovers(ownerRule, subRule rbacv1.PolicyRule) bool {
|
||||
verbMatches := has(ownerRule.Verbs, rbacv1.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs)
|
||||
groupMatches := has(ownerRule.APIGroups, rbacv1.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups)
|
||||
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
|
||||
|
||||
|
||||
@@ -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 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 {
|
||||
klog.Errorf("failed to update apprelease %s status : %v", apprls.Name, 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
|
||||
}
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ func repoParseRequest(cli client.Client, versions helmrepo.ChartVersions, helmRe
|
||||
FromRepo: true,
|
||||
}
|
||||
url := ver.URLs[0]
|
||||
methodList := []string{"https://", "http://", "s3://"}
|
||||
methodList := []string{"https://", "http://", "s3://", "oci://"}
|
||||
needContact := true
|
||||
for _, method := range methodList {
|
||||
if strings.HasPrefix(url, method) {
|
||||
|
||||
@@ -456,26 +456,40 @@ func (r *InstallPlanReconciler) loadChartData(ctx context.Context) ([]byte, stri
|
||||
switch chartURL.Scheme {
|
||||
case registry.OCIScheme:
|
||||
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 {
|
||||
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
||||
}
|
||||
chartGetter, err = getter.NewOCIGetter(opts...)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to create chart getter: %v", err)
|
||||
}
|
||||
case "http", "https":
|
||||
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" {
|
||||
opts = append(opts, getter.WithInsecureSkipVerifyTLS(true))
|
||||
opts = append(opts, getter.WithInsecureSkipVerifyTLS(repo.Spec.Insecure))
|
||||
}
|
||||
if repo.Spec.BasicAuth != nil {
|
||||
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
||||
}
|
||||
chartGetter, err = getter.NewHTTPGetter(opts...)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to create chart getter: %v", err)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("unsupported scheme: %s", chartURL.Scheme)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to create chart getter: %v", err)
|
||||
return nil, "", fmt.Errorf("unsupported scheme: %s", chartURL.Scheme)
|
||||
}
|
||||
|
||||
buffer, err := chartGetter.Get(chartURL.String())
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -19,10 +18,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kscontroller "kubesphere.io/kubesphere/pkg/controller"
|
||||
|
||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@@ -35,6 +30,7 @@ import (
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/klog/v2"
|
||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||
corev1alpha1 "kubesphere.io/api/core/v1alpha1"
|
||||
"kubesphere.io/utils/helm"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@@ -43,6 +39,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
kscontroller "kubesphere.io/kubesphere/pkg/controller"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -52,6 +49,8 @@ const (
|
||||
defaultRequeueInterval = 15 * time.Second
|
||||
generateNameFormat = "repository-%s"
|
||||
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")
|
||||
@@ -178,10 +177,10 @@ func (r *RepositoryReconciler) syncExtensionsFromURL(ctx context.Context, repo *
|
||||
logger := klog.FromContext(ctx)
|
||||
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
cred := helm.RepoCredential{}
|
||||
if repo.Spec.BasicAuth != nil {
|
||||
cred.Username = repo.Spec.BasicAuth.Username
|
||||
cred.Password = repo.Spec.BasicAuth.Password
|
||||
|
||||
cred, err := newHelmCred(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index, err := helm.LoadRepoIndex(ctx, repoURL, cred)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
var result *corev1alpha1.ExtensionVersionSpec
|
||||
|
||||
err := retry.OnError(retry.DefaultRetry, func(err error) bool {
|
||||
return true
|
||||
}, func() error {
|
||||
req, err := http.NewRequest(http.MethodGet, chartURL, nil)
|
||||
data, err := helm.LoadData(ctx, chartURL, cred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if repo.Spec.BasicAuth != nil {
|
||||
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)
|
||||
files, err := loader.LoadArchiveFiles(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,15 +7,18 @@ package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
yaml3 "gopkg.in/yaml.v3"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
yaml3 "gopkg.in/yaml.v3"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
@@ -23,9 +26,9 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1"
|
||||
corev1alpha1 "kubesphere.io/api/core/v1alpha1"
|
||||
"kubesphere.io/utils/helm"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/utils/hashutil"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/version"
|
||||
)
|
||||
|
||||
@@ -264,3 +267,58 @@ func configChanged(sub *corev1alpha1.InstallPlan, cluster string) bool {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if !metav1.IsControlledBy(namespace, workspace) {
|
||||
if !metav1.IsControlledBy(namespace, workspace) && namespace.Labels[constants.KubeSphereManagedLabel] == "true" {
|
||||
namespace = namespace.DeepCopy()
|
||||
if err := controllerutil.SetControllerReference(workspace, namespace, scheme.Scheme); err != nil {
|
||||
return err
|
||||
|
||||
@@ -42,8 +42,11 @@ var _ = Describe("Namespace", func() {
|
||||
It("Should create successfully", func() {
|
||||
namespace := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-namespace",
|
||||
Labels: map[string]string{tenantv1beta1.WorkspaceLabel: workspace.Name},
|
||||
Name: "test-namespace",
|
||||
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{}) {
|
||||
apiService := obj.(*extensionsv1alpha1.APIService)
|
||||
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 {
|
||||
klog.Error(err)
|
||||
klog.V(4).Infof("add openapi v3 service failed: %v", err)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
apiService := new.(*extensionsv1alpha1.APIService)
|
||||
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 {
|
||||
klog.Error(err)
|
||||
klog.V(4).Infof("update openapi v3 service failed: %v", err)
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
|
||||
@@ -96,9 +96,9 @@ func (r *Reconciler) syncToKubernetes(ctx context.Context, role *iamv1beta1.Role
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -7,8 +7,12 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
@@ -22,6 +26,8 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
)
|
||||
|
||||
var caTemplate = "{{ .TempDIR }}/repository/{{ .RepositoryName }}/ssl/ca.crt"
|
||||
|
||||
type handler struct {
|
||||
cache runtimeclient.Reader
|
||||
}
|
||||
@@ -71,23 +77,40 @@ func (h *handler) ListFiles(request *restful.Request, response *restful.Response
|
||||
switch chartURL.Scheme {
|
||||
case registry.OCIScheme:
|
||||
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 {
|
||||
opts = append(opts, getter.WithBasicAuth(repo.Spec.BasicAuth.Username, repo.Spec.BasicAuth.Password))
|
||||
}
|
||||
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":
|
||||
options := make([]getter.Option, 0)
|
||||
if chartURL.Scheme == "https" {
|
||||
options = append(options, getter.WithInsecureSkipVerifyTLS(true))
|
||||
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 {
|
||||
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 {
|
||||
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...)
|
||||
}
|
||||
if err != nil {
|
||||
api.HandleInternalError(response, request, fmt.Errorf("failed to create chart getter: %v", err))
|
||||
chartGetter, err = getter.NewHTTPGetter(opts...)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -105,3 +128,39 @@ func (h *handler) ListFiles(request *restful.Request, response *restful.Response
|
||||
|
||||
_ = 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/kube"
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
helmrelease "helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/storage"
|
||||
"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) {
|
||||
if registry.IsOCI(u) {
|
||||
return LoadRepoIndexFromOci(u, cred)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(u, "/") {
|
||||
u = fmt.Sprintf("%s/index.yaml", u)
|
||||
} 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"
|
||||
"fmt"
|
||||
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"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)
|
||||
return data, err
|
||||
}
|
||||
if registry.IsOCI(pullUrl) {
|
||||
return HelmPullFromOci(pullUrl, repo.Spec.Credential)
|
||||
}
|
||||
buf, err := HelmPull(pullUrl, repo.Spec.Credential)
|
||||
if err != nil {
|
||||
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 {
|
||||
// DEPRECATED: the field will remove in future versions, please use url.
|
||||
Image string `json:"image,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
|
||||
UpdateStrategy *UpdateStrategy `json:"updateStrategy,omitempty"`
|
||||
// +optional The caBundle (base64 string) is used in helmExecutor to verify the helm server.
|
||||
// if the caBundle is empty, use --insecure-skip-tls-verify.
|
||||
// The caBundle (base64 string) is used in helmExecutor to verify the helm server.
|
||||
// +optional
|
||||
CABundle string `json:"caBundle,omitempty"`
|
||||
// --insecure-skip-tls-verify. default false
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
}
|
||||
|
||||
type RepositoryStatus struct {
|
||||
|
||||
@@ -24,7 +24,7 @@ func LoadRepoIndex(ctx context.Context, u string, cred RepoCredential) (*helmrep
|
||||
u = fmt.Sprintf("%s%s", u, IndexYaml)
|
||||
}
|
||||
|
||||
resp, err := loadData(ctx, u, cred)
|
||||
resp, err := LoadData(ctx, u, cred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func loadIndex(data []byte) (*helmrepo.IndexFile, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -81,18 +81,11 @@ func loadData(ctx context.Context, u string, cred RepoCredential) (*bytes.Buffer
|
||||
|
||||
resp = bytes.NewBuffer(data)
|
||||
} else {
|
||||
skipTLS := true
|
||||
if cred.InsecureSkipTLSVerify != nil && !*cred.InsecureSkipTLSVerify {
|
||||
skipTLS = false
|
||||
}
|
||||
|
||||
indexURL := parsedURL.String()
|
||||
// TODO add user-agent
|
||||
g, _ := getter.NewHTTPGetter()
|
||||
resp, err = g.Get(indexURL,
|
||||
resp, err = g.Get(parsedURL.String(),
|
||||
getter.WithTimeout(5*time.Minute),
|
||||
getter.WithURL(u),
|
||||
getter.WithInsecureSkipVerifyTLS(skipTLS),
|
||||
getter.WithInsecureSkipVerifyTLS(cred.InsecureSkipTLSVerify),
|
||||
getter.WithTLSClientConfig(cred.CertFile, cred.KeyFile, cred.CAFile),
|
||||
getter.WithBasicAuth(cred.Username, cred.Password),
|
||||
)
|
||||
@@ -135,7 +128,7 @@ type RepoCredential struct {
|
||||
// verify certificates of HTTPS-enabled servers using this CA bundle
|
||||
CAFile string `json:"caFile,omitempty"`
|
||||
// skip tls certificate checks for the repository, default is ture
|
||||
InsecureSkipTLSVerify *bool `json:"insecureSkipTLSVerify,omitempty"`
|
||||
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
|
||||
|
||||
S3Config `json:",inline"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user