mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-23 06:14:30 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e3ab732df | ||
|
|
62d5507499 | ||
|
|
c0daf688a6 | ||
|
|
48d19a2427 | ||
|
|
4da8d49e60 | ||
|
|
4db9e89816 | ||
|
|
667053409d |
@@ -106,7 +106,7 @@ metadata:
|
||||
name: v1alpha1.cluster.core.oam.dev
|
||||
annotations:
|
||||
{{- if and .Values.multicluster.clusterGateway.secureTLS.enabled .Values.multicluster.clusterGateway.secureTLS.certManager.enabled }}
|
||||
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kubevela.fullname" . }}-cluster-gateway-tls"
|
||||
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kubevela.fullname" . }}-cluster-gateway-tls-v2"
|
||||
{{- end }}
|
||||
labels:
|
||||
api: cluster-extension-apiserver
|
||||
|
||||
@@ -20,6 +20,7 @@ spec:
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
output: {
|
||||
@@ -42,21 +43,29 @@ spec:
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
stringData: ".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if parameter.insecure != _|_ {
|
||||
"insecure-skip-verify": strconv.FormatBool(parameter.insecure)
|
||||
}
|
||||
if parameter.useHTTP != _|_ {
|
||||
"protocol-use-http": strconv.FormatBool(parameter.useHTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Image registry FQDN
|
||||
// +usage=Image registry FQDN, such as: index.docker.io
|
||||
registry: string
|
||||
// +usage=Authenticate the image registry
|
||||
auth?: {
|
||||
@@ -67,6 +76,10 @@ spec:
|
||||
// +usage=Private Image registry email
|
||||
email?: string
|
||||
}
|
||||
// +usage=For the registry server that uses the self-signed certificate
|
||||
insecure?: bool
|
||||
// +usage=For the registry server that uses the HTTP protocol
|
||||
useHTTP?: bool
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
@@ -20,6 +20,7 @@ spec:
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
output: {
|
||||
@@ -42,21 +43,29 @@ spec:
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
stringData: ".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if parameter.insecure != _|_ {
|
||||
"insecure-skip-verify": strconv.FormatBool(parameter.insecure)
|
||||
}
|
||||
if parameter.useHTTP != _|_ {
|
||||
"protocol-use-http": strconv.FormatBool(parameter.useHTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Image registry FQDN
|
||||
// +usage=Image registry FQDN, such as: index.docker.io
|
||||
registry: string
|
||||
// +usage=Authenticate the image registry
|
||||
auth?: {
|
||||
@@ -67,6 +76,10 @@ spec:
|
||||
// +usage=Private Image registry email
|
||||
email?: string
|
||||
}
|
||||
// +usage=For the registry server that uses the self-signed certificate
|
||||
insecure?: bool
|
||||
// +usage=For the registry server that uses the HTTP protocol
|
||||
useHTTP?: bool
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
2
go.mod
2
go.mod
@@ -46,7 +46,7 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.9.1
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/kubevela/prism v1.4.0
|
||||
github.com/kubevela/prism v1.4.1-0.20220613123457-94f1190f87c2
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.1
|
||||
github.com/oam-dev/cluster-gateway v1.4.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1344,8 +1344,8 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kubevela/prism v1.4.0 h1:wYCKXA3p9YpkcSsZjGnSEGBVL+3bPoZNEt4DYs3IxW4=
|
||||
github.com/kubevela/prism v1.4.0/go.mod h1:RP69+bRb57Occer6BeeF5zK3hrD1IhnYf2RNRsIdh9E=
|
||||
github.com/kubevela/prism v1.4.1-0.20220613123457-94f1190f87c2 h1:TaHlO4raKI3ehVSYY8QixYMHdI0VwKHY1KPNWcUre3I=
|
||||
github.com/kubevela/prism v1.4.1-0.20220613123457-94f1190f87c2/go.mod h1:RP69+bRb57Occer6BeeF5zK3hrD1IhnYf2RNRsIdh9E=
|
||||
github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
|
||||
github.com/kunwardeep/paralleltest v1.0.2/go.mod h1:ZPqNm1fVHPllh5LPVujzbVz1JN2GhLxSfY+oqUsvG30=
|
||||
github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
|
||||
|
||||
@@ -29,6 +29,12 @@ func init() {
|
||||
RegisterModel(&WorkflowRecord{})
|
||||
}
|
||||
|
||||
// Finished means the workflow record is finished
|
||||
const Finished = "true"
|
||||
|
||||
// UnFinished means the workflow record is not finished
|
||||
const UnFinished = "false"
|
||||
|
||||
// Workflow application delivery database model
|
||||
type Workflow struct {
|
||||
BaseModel
|
||||
|
||||
@@ -507,14 +507,14 @@ func (c *applicationServiceImpl) UpdateApplication(ctx context.Context, app *mod
|
||||
func (c *applicationServiceImpl) ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error) {
|
||||
var record = model.WorkflowRecord{
|
||||
AppPrimaryKey: appName,
|
||||
Finished: "false",
|
||||
Finished: model.UnFinished,
|
||||
}
|
||||
records, err := c.Store.List(ctx, &record, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
record.Finished = "true"
|
||||
record.Finished = model.Finished
|
||||
records, err = c.Store.List(ctx, &record, &datastore.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: 1,
|
||||
|
||||
@@ -60,7 +60,10 @@ var _ = Describe("Test cluster service function", func() {
|
||||
secret.Name = name
|
||||
secret.Namespace = prismclusterv1alpha1.StorageNamespace
|
||||
secret.SetAnnotations(map[string]string{prismclusterv1alpha1.AnnotationClusterAlias: alias})
|
||||
secret.SetLabels(map[string]string{clustergatewaycommon.LabelKeyClusterCredentialType: string(clustergatewayv1alpha1.CredentialTypeX509Certificate)})
|
||||
secret.SetLabels(map[string]string{
|
||||
clustergatewaycommon.LabelKeyClusterEndpointType: string(clustergatewayv1alpha1.ClusterEndpointTypeConst),
|
||||
clustergatewaycommon.LabelKeyClusterCredentialType: string(clustergatewayv1alpha1.CredentialTypeX509Certificate),
|
||||
})
|
||||
time.Sleep(time.Second)
|
||||
return k8sClient.Create(ctx, secret)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ type DefinitionService interface {
|
||||
UpdateDefinitionStatus(ctx context.Context, name string, status apisv1.UpdateDefinitionStatusRequest) (*apisv1.DetailDefinitionResponse, error)
|
||||
}
|
||||
|
||||
// DefinitionHidden means the definition can not be used in VelaUX
|
||||
const DefinitionHidden = "true"
|
||||
|
||||
type definitionServiceImpl struct {
|
||||
KubeClient client.Client `inject:"kubeClient"`
|
||||
}
|
||||
@@ -344,7 +347,7 @@ func (d *definitionServiceImpl) UpdateDefinitionStatus(ctx context.Context, name
|
||||
}
|
||||
if !exist && update.HiddenInUI {
|
||||
labels := def.GetLabels()
|
||||
labels[types.LabelDefinitionHidden] = "true"
|
||||
labels[types.LabelDefinitionHidden] = DefinitionHidden
|
||||
def.SetLabels(labels)
|
||||
if err := d.KubeClient.Update(ctx, def); err != nil {
|
||||
return nil, err
|
||||
|
||||
228
pkg/apiserver/domain/service/image.go
Normal file
228
pkg/apiserver/domain/service/image.go
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
)
|
||||
|
||||
// NewImageService create a image service instance
|
||||
func NewImageService() ImageService {
|
||||
return &imageImpl{}
|
||||
}
|
||||
|
||||
// ImageService the image service provide some handler functions about the docker image
|
||||
type ImageService interface {
|
||||
ListImageRepos(ctx context.Context, project string) ([]v1.ImageRegistry, error)
|
||||
GetImageInfo(ctx context.Context, project, secretName, imageName string) v1.ImageInfo
|
||||
}
|
||||
|
||||
type imageImpl struct {
|
||||
K8sClient client.Client `inject:"kubeClient"`
|
||||
}
|
||||
|
||||
// ListImageRepos list the image repositories via user configuration
|
||||
func (i *imageImpl) ListImageRepos(ctx context.Context, project string) ([]v1.ImageRegistry, error) {
|
||||
var secrets corev1.SecretList
|
||||
if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var repos []v1.ImageRegistry
|
||||
for _, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
repos = append(repos, v1.ImageRegistry{
|
||||
Name: secret.Name,
|
||||
SecretName: secret.Name,
|
||||
Domain: secret.Labels[types.LabelConfigIdentifier],
|
||||
})
|
||||
}
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetImageInfo get the image info from image registry
|
||||
func (i *imageImpl) GetImageInfo(ctx context.Context, project, secretName, imageName string) v1.ImageInfo {
|
||||
var imageInfo = v1.ImageInfo{
|
||||
Name: imageName,
|
||||
}
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
imageInfo.Message = "The image name is invalid"
|
||||
return imageInfo
|
||||
}
|
||||
registryDomain := ref.Context().RegistryStr()
|
||||
imageInfo.Registry = registryDomain
|
||||
var secrets corev1.SecretList
|
||||
if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
types.LabelConfigIdentifier: registryDomain,
|
||||
}); err != nil {
|
||||
log.Logger.Warnf("fail to list the docker registries, %s", err.Error())
|
||||
}
|
||||
var selectSecret []*corev1.Secret
|
||||
var selectSecretNames []string
|
||||
// get info with specified secret
|
||||
if secretName != "" {
|
||||
for i, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
if secretName == secret.Name {
|
||||
selectSecret = append(selectSecret, &secrets.Items[i])
|
||||
selectSecretNames = append(selectSecretNames, secret.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get info with the secret which match the registry domain
|
||||
if selectSecret == nil {
|
||||
for i, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
if secret.Labels[types.LabelConfigIdentifier] == registryDomain {
|
||||
selectSecret = append(selectSecret, &secrets.Items[i])
|
||||
selectSecretNames = append(selectSecretNames, secret.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var username, password string
|
||||
var insecure = false
|
||||
var useHTTP = false
|
||||
imageInfo.SecretNames = selectSecretNames
|
||||
if len(selectSecret) > 0 {
|
||||
insecure, useHTTP, username, password = getAccountFromSecret(*selectSecret[0], registryDomain)
|
||||
}
|
||||
err = getImageInfo(imageName, insecure, useHTTP, username, password, &imageInfo)
|
||||
if err != nil {
|
||||
imageInfo.Message = fmt.Sprintf("Fail to get the image info:%s", err.Error())
|
||||
}
|
||||
return imageInfo
|
||||
}
|
||||
|
||||
// getAccountFromSecret get the username and password from the secret of `kubernetes.io/dockerconfigjson` type
|
||||
// refer: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
|
||||
func getAccountFromSecret(secret corev1.Secret, registryDomain string) (insecure, useHTTP bool, username, password string) {
|
||||
if secret.Data != nil {
|
||||
// If users use the self-signed certificate, enable the insecure-skip-verify
|
||||
insecure = string(secret.Data["insecure-skip-verify"]) == "true"
|
||||
useHTTP = string(secret.Data["protocol-use-http"]) == "true"
|
||||
conf := secret.Data[".dockerconfigjson"]
|
||||
if len(conf) > 0 {
|
||||
var authConfig map[string]map[string]map[string]string
|
||||
if err := json.Unmarshal(conf, &authConfig); err != nil {
|
||||
log.Logger.Warnf("fail to unmarshal the secret %s , %s", secret.Name, err.Error())
|
||||
return
|
||||
}
|
||||
if authConfig != nil && authConfig["auths"] != nil && authConfig["auths"][registryDomain] != nil {
|
||||
data := authConfig["auths"][registryDomain]
|
||||
username = data["username"]
|
||||
password = data["password"]
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getImageInfo(imageName string, insecure, useHTTP bool, username, password string, info *v1.ImageInfo) error {
|
||||
var options []remote.Option
|
||||
if username != "" || password != "" {
|
||||
basic := &authn.Basic{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
options = append(options, remote.WithAuth(basic))
|
||||
}
|
||||
if insecure {
|
||||
options = append(options, remote.WithTransport(&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
// By default we wrap the transport in retries, so reduce the
|
||||
// default dial timeout to 5s to avoid 5x 30s of connection
|
||||
// timeouts when doing the "ping" on certain http registries.
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
// #nosec G402
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
|
||||
}))
|
||||
}
|
||||
|
||||
var parseOptions []name.Option
|
||||
if useHTTP {
|
||||
parseOptions = append(parseOptions, name.Insecure)
|
||||
}
|
||||
var err error
|
||||
ref, err := name.ParseReference(imageName, parseOptions...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
image, err := remote.Image(ref, options...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "incorrect username or password") {
|
||||
return fmt.Errorf("incorrect username or password")
|
||||
}
|
||||
var terr *transport.Error
|
||||
if errors.As(err, &terr) {
|
||||
fmt.Println(terr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
info.Manifest, err = image.Manifest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get the manifest:%w", err)
|
||||
}
|
||||
info.Info, err = image.ConfigFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get the config:%w", err)
|
||||
}
|
||||
for _, l := range info.Manifest.Layers {
|
||||
info.Size += l.Size
|
||||
}
|
||||
info.Size += info.Manifest.Config.Size
|
||||
return nil
|
||||
}
|
||||
67
pkg/apiserver/domain/service/image_test.go
Normal file
67
pkg/apiserver/domain/service/image_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
)
|
||||
|
||||
func TestGetImageInfo(t *testing.T) {
|
||||
|
||||
s2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s2",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: velatypes.ImageRegistry,
|
||||
velatypes.LabelConfigProject: "",
|
||||
velatypes.LabelConfigIdentifier: "index.docker.io",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"insecure-skip-verify": []byte("true"),
|
||||
".dockerconfigjson": []byte(`{"auths":{"index.docker.io":{"auth":"aHlicmlkY2xvdWRAcHJvZC5YTEyMw==","username":"xxx","password":"yyy"}}}`),
|
||||
},
|
||||
}
|
||||
|
||||
insecure, useHTTP, user, pass := getAccountFromSecret(*s2, "index.docker.io")
|
||||
assert.DeepEqual(t, user, "xxx")
|
||||
assert.DeepEqual(t, pass, "yyy")
|
||||
assert.DeepEqual(t, insecure, true)
|
||||
assert.DeepEqual(t, useHTTP, false)
|
||||
|
||||
var cf v1.ImageInfo
|
||||
// Test the public image
|
||||
err := getImageInfo("nginx", false, false, "", "", &cf)
|
||||
assert.DeepEqual(t, err, nil)
|
||||
assert.DeepEqual(t, cf.Info.Config.Entrypoint, []string{"/docker-entrypoint.sh"})
|
||||
|
||||
// Test the private image
|
||||
err = getImageInfo("nginx424ru823-should-not-existed", false, false, "abc", "efg", &cf)
|
||||
assert.DeepEqual(t, err.Error(), "incorrect username or password")
|
||||
|
||||
err = getImageInfo("text.registry/test-image", false, false, "", "", &cf)
|
||||
assert.DeepEqual(t, err != nil, true)
|
||||
}
|
||||
@@ -18,15 +18,12 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -41,7 +38,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
image "github.com/oam-dev/kubevela/pkg/utils/imageregistry"
|
||||
)
|
||||
|
||||
// ProjectService project manage service.
|
||||
@@ -59,7 +55,6 @@ type ProjectService interface {
|
||||
UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error)
|
||||
Init(ctx context.Context) error
|
||||
GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error)
|
||||
ValidateImage(ctx context.Context, projectName, image string) (*apisv1.ImageResponse, error)
|
||||
}
|
||||
|
||||
type projectServiceImpl struct {
|
||||
@@ -623,57 +618,3 @@ func retrieveConfigFromApplication(a v1beta1.Application, project string) *apisv
|
||||
Description: a.Annotations[types.AnnotationConfigDescription],
|
||||
}
|
||||
}
|
||||
|
||||
func (p *projectServiceImpl) ValidateImage(ctx context.Context, projectName, image string) (*apisv1.ImageResponse, error) {
|
||||
return validateImage(ctx, p.K8sClient, projectName, image)
|
||||
}
|
||||
|
||||
func validateImage(ctx context.Context, k8sClient client.Client, project, imageName string) (*apisv1.ImageResponse, error) {
|
||||
var (
|
||||
secrets v1.SecretList
|
||||
username string
|
||||
password string
|
||||
imagePullSecret string
|
||||
)
|
||||
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageURL := ref.Context().RegistryStr()
|
||||
|
||||
if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
types.LabelConfigIdentifier: ref.Context().RegistryStr(),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range secrets.Items {
|
||||
if s.Labels[types.LabelConfigProject] == "" || s.Labels[types.LabelConfigProject] == project {
|
||||
conf := s.Data[".dockerconfigjson"]
|
||||
var auths map[string]map[string]map[string]string
|
||||
if err := json.Unmarshal(conf, &auths); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imagePullSecret = s.Name
|
||||
if auths["auths"] != nil && auths["auths"][imageURL] != nil {
|
||||
data := auths["auths"][imageURL]
|
||||
username = data["username"]
|
||||
password = data["password"]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
existed, err := image.IsExisted(username, password, imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apisv1.ImageResponse{
|
||||
Existed: existed,
|
||||
Secret: imagePullSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -18,20 +18,14 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"gotest.tools/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -299,331 +293,3 @@ var _ = Describe("Test project service functions", func() {
|
||||
Expect(roles.Total).Should(BeEquivalentTo(0))
|
||||
})
|
||||
})
|
||||
|
||||
func TestProjectGetConfigs(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
|
||||
createdTime, _ := time.Parse(time.UnixDate, "Wed Apr 7 11:06:39 PST 2022")
|
||||
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a1",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "terraform-provider",
|
||||
"config.oam.dev/project": "p1",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{Phase: common.ApplicationRunning},
|
||||
}
|
||||
|
||||
app2 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a2",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "terraform-provider",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{Phase: common.ApplicationRunning},
|
||||
}
|
||||
|
||||
app3 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a3",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "dex-connector",
|
||||
"config.oam.dev/project": "p3",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{Phase: common.ApplicationRunning},
|
||||
}
|
||||
|
||||
provider1 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider1",
|
||||
Namespace: "default",
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsReady,
|
||||
},
|
||||
}
|
||||
|
||||
provider2 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider2",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsNotReady,
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(app1, app2, app3, provider1, provider2).Build()
|
||||
|
||||
h := &projectServiceImpl{K8sClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
projectName string
|
||||
configType string
|
||||
h ProjectService
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configs []*apisv1.Config
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "project is matched",
|
||||
args: args{
|
||||
projectName: "p1",
|
||||
configType: "terraform-provider",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a1",
|
||||
Project: "p1",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a2",
|
||||
Project: "",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project is not matched",
|
||||
args: args{
|
||||
projectName: "p999",
|
||||
configType: "terraform-provider",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a2",
|
||||
Project: "",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config type is empty",
|
||||
args: args{
|
||||
projectName: "p3",
|
||||
configType: "",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a2",
|
||||
Project: "",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
ConfigType: "dex-connector",
|
||||
Name: "a3",
|
||||
Project: "p3",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config type is dex",
|
||||
args: args{
|
||||
projectName: "p3",
|
||||
configType: "config-dex-connector",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "dex-connector",
|
||||
Name: "a3",
|
||||
Project: "p3",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config type is invalid",
|
||||
args: args{
|
||||
configType: "xxx",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "unsupported config type",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.GetConfigs(ctx, tc.args.projectName, tc.args.configType)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateImage(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
|
||||
s1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s1",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: velatypes.ImageRegistry,
|
||||
velatypes.LabelConfigProject: "",
|
||||
velatypes.LabelConfigIdentifier: "abce34289jwerojwerofaf77.com789",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
".dockerconfigjson": []byte(`{"auths":{"abce34289jwerojwerofaf77.com789":{"auth":"aHlicmlkY2xvdWRAcHJvZC5YTEyMw==","username":"xxx","password":"yyy"}}}`),
|
||||
},
|
||||
}
|
||||
k8sClient1 := fake.NewClientBuilder().WithScheme(s).WithObjects(s1).Build()
|
||||
h1 := &projectServiceImpl{K8sClient: k8sClient1}
|
||||
|
||||
s2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s2",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: velatypes.ImageRegistry,
|
||||
velatypes.LabelConfigProject: "",
|
||||
velatypes.LabelConfigIdentifier: "index.docker.io",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
".dockerconfigjson": []byte(`{"auths":{"index.docker.io":{"auth":"aHlicmlkY2xvdWRAcHJvZC5YTEyMw==","username":"xxx","password":"yyy"}}}`),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient2 := fake.NewClientBuilder().WithScheme(s).WithObjects(s2).Build()
|
||||
h2 := &projectServiceImpl{K8sClient: k8sClient2}
|
||||
|
||||
type args struct {
|
||||
project string
|
||||
imageName string
|
||||
h ProjectService
|
||||
}
|
||||
|
||||
type want struct {
|
||||
resp *apisv1.ImageResponse
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "validate image",
|
||||
args: args{
|
||||
project: "p1",
|
||||
imageName: "nginx",
|
||||
h: h1,
|
||||
},
|
||||
want: want{
|
||||
resp: &apisv1.ImageResponse{
|
||||
Existed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid image",
|
||||
args: args{
|
||||
project: "p1",
|
||||
imageName: "abce34289jwerojwerofaf77.com789/d/e:v1",
|
||||
h: h1,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "Get",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "private docker image",
|
||||
args: args{
|
||||
project: "p1",
|
||||
imageName: "nginx424ru823-should-not-existed",
|
||||
h: h2,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "incorrect username or password",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.ValidateImage(ctx, tc.args.project, tc.args.imageName)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,6 @@ var ResourceMaps = map[string]resourceMetadata{
|
||||
},
|
||||
"applicationTemplate": {},
|
||||
"config": {},
|
||||
"image": {},
|
||||
},
|
||||
pathName: "projectName",
|
||||
},
|
||||
|
||||
@@ -50,7 +50,7 @@ func InitServiceBean(c config.Config) []interface{} {
|
||||
return []interface{}{
|
||||
clusterService, rbacService, projectService, envService, targetService, workflowService, oamApplicationService,
|
||||
velaQLService, definitionService, addonService, envBindingService, systemInfoService, helmService, userService,
|
||||
authenticationService, configService, applicationService, webhookService,
|
||||
authenticationService, configService, applicationService, webhookService, NewImageService(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
registryv1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -1380,3 +1381,26 @@ type ChartRepoResponse struct {
|
||||
type ChartRepoResponseList struct {
|
||||
ChartRepoResponse []*ChartRepoResponse `json:"repos"`
|
||||
}
|
||||
|
||||
// ImageInfo the docker image info
|
||||
type ImageInfo struct {
|
||||
Name string `json:"name"`
|
||||
SecretNames []string `json:"secretNames"`
|
||||
Registry string `json:"registry"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Info *registryv1.ConfigFile `json:"info,omitempty"`
|
||||
Size int64 `json:"size"`
|
||||
Manifest *registryv1.Manifest `json:"manifest"`
|
||||
}
|
||||
|
||||
// ImageRegistry the image repository info
|
||||
type ImageRegistry struct {
|
||||
Name string `json:"name"`
|
||||
SecretName string `json:"secretName"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
// ListImageRegistryResponse the response struct of listing the image registries
|
||||
type ListImageRegistryResponse struct {
|
||||
Registries []ImageRegistry `json:"registries"`
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func InitAPIBean() []interface{} {
|
||||
RegisterAPIInterface(NewTargetAPIInterface())
|
||||
RegisterAPIInterface(NewVelaQLAPIInterface())
|
||||
RegisterAPIInterface(NewWebhookAPIInterface())
|
||||
RegisterAPIInterface(NewHelmAPIInterface())
|
||||
RegisterAPIInterface(NewRepositoryAPIInterface())
|
||||
|
||||
// Authentication
|
||||
RegisterAPIInterface(NewAuthenticationAPIInterface())
|
||||
|
||||
@@ -203,16 +203,6 @@ func (n *projectAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]*apis.Config{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/validate_image").To(n.validateImage).
|
||||
Doc("validate an image in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/image", "get")).
|
||||
Param(ws.QueryParameter("image", "image name").DataType("string")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
|
||||
Returns(200, "OK", []*apis.ImageResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]*apis.ImageResponse{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
@@ -599,23 +589,3 @@ func (n *projectAPIInterface) getConfigs(req *restful.Request, res *restful.Resp
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) validateImage(req *restful.Request, res *restful.Response) {
|
||||
resp, err := n.ProjectService.ValidateImage(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("image"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if resp == nil {
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(map[string]interface{}{"data": resp})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
@@ -29,16 +28,18 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
)
|
||||
|
||||
type helmAPIInterface struct {
|
||||
HelmService service.HelmService `inject:""`
|
||||
type repositoryAPIInterface struct {
|
||||
HelmService service.HelmService `inject:""`
|
||||
ImageService service.ImageService `inject:""`
|
||||
RbacService service.RBACService `inject:""`
|
||||
}
|
||||
|
||||
// NewHelmAPIInterface will return helm APIInterface
|
||||
func NewHelmAPIInterface() Interface {
|
||||
return &helmAPIInterface{}
|
||||
// NewRepositoryAPIInterface will return the repository APIInterface
|
||||
func NewRepositoryAPIInterface() Interface {
|
||||
return &repositoryAPIInterface{}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
func (h repositoryAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/repository").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
@@ -47,11 +48,12 @@ func (h helmAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
|
||||
tags := []string{"repository", "helm"}
|
||||
|
||||
// List charts
|
||||
// List chart repos
|
||||
ws.Route(ws.GET("/chart_repos").To(h.listRepo).
|
||||
Doc("list chart repo").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string")).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string").Required(true)).
|
||||
Filter(h.RbacService.CheckPerm("project/config", "list")).
|
||||
Returns(200, "OK", []string{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
@@ -86,11 +88,31 @@ func (h helmAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
|
||||
ws.Route(ws.GET("/image/repos").To(h.getImageRepos).
|
||||
Doc("get the oci repos").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string").Required(true)).
|
||||
Filter(h.RbacService.CheckPerm("project/config", "list")).
|
||||
Returns(200, "OK", v1.ListImageRegistryResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
|
||||
ws.Route(ws.GET("/image/info").To(h.getImageInfo).
|
||||
Doc("get the oci repos").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string").Required(true)).
|
||||
Param(ws.QueryParameter("name", "the image name").DataType("string").Required(true)).
|
||||
Param(ws.QueryParameter("secretName", "the secret name of the image repository").DataType("string")).
|
||||
Filter(h.RbacService.CheckPerm("project/config", "list")).
|
||||
Returns(200, "OK", v1.ImageInfo{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) listCharts(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) listCharts(req *restful.Request, res *restful.Response) {
|
||||
url := utils.Sanitize(req.QueryParameter("repoUrl"))
|
||||
secName := utils.Sanitize(req.QueryParameter("secretName"))
|
||||
skipCache, err := isSkipCache(req)
|
||||
@@ -98,7 +120,7 @@ func (h helmAPIInterface) listCharts(req *restful.Request, res *restful.Response
|
||||
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
|
||||
return
|
||||
}
|
||||
charts, err := h.HelmService.ListChartNames(context.Background(), url, secName, skipCache)
|
||||
charts, err := h.HelmService.ListChartNames(req.Request.Context(), url, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -110,7 +132,7 @@ func (h helmAPIInterface) listCharts(req *restful.Request, res *restful.Response
|
||||
}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) listVersions(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) listVersions(req *restful.Request, res *restful.Response) {
|
||||
url := req.QueryParameter("repoUrl")
|
||||
chartName := req.PathParameter("chart")
|
||||
secName := req.QueryParameter("secretName")
|
||||
@@ -120,7 +142,7 @@ func (h helmAPIInterface) listVersions(req *restful.Request, res *restful.Respon
|
||||
return
|
||||
}
|
||||
|
||||
versions, err := h.HelmService.ListChartVersions(context.Background(), url, chartName, secName, skipCache)
|
||||
versions, err := h.HelmService.ListChartVersions(req.Request.Context(), url, chartName, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -132,7 +154,7 @@ func (h helmAPIInterface) listVersions(req *restful.Request, res *restful.Respon
|
||||
}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) chartValues(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) chartValues(req *restful.Request, res *restful.Response) {
|
||||
url := req.QueryParameter("repoUrl")
|
||||
secName := req.QueryParameter("secretName")
|
||||
chartName := req.PathParameter("chart")
|
||||
@@ -143,7 +165,7 @@ func (h helmAPIInterface) chartValues(req *restful.Request, res *restful.Respons
|
||||
return
|
||||
}
|
||||
|
||||
versions, err := h.HelmService.GetChartValues(context.Background(), url, chartName, version, secName, skipCache)
|
||||
versions, err := h.HelmService.GetChartValues(req.Request.Context(), url, chartName, version, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -155,9 +177,9 @@ func (h helmAPIInterface) chartValues(req *restful.Request, res *restful.Respons
|
||||
}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) listRepo(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) listRepo(req *restful.Request, res *restful.Response) {
|
||||
project := req.QueryParameter("project")
|
||||
repos, err := h.HelmService.ListChartRepo(context.Background(), project)
|
||||
repos, err := h.HelmService.ListChartRepo(req.Request.Context(), project)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -169,6 +191,31 @@ func (h helmAPIInterface) listRepo(req *restful.Request, res *restful.Response)
|
||||
}
|
||||
}
|
||||
|
||||
func (h repositoryAPIInterface) getImageRepos(req *restful.Request, res *restful.Response) {
|
||||
project := req.QueryParameter("project")
|
||||
repos, err := h.ImageService.ListImageRepos(req.Request.Context(), project)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(v1.ListImageRegistryResponse{Registries: repos})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (h repositoryAPIInterface) getImageInfo(req *restful.Request, res *restful.Response) {
|
||||
project := req.QueryParameter("project")
|
||||
imageInfo := h.ImageService.GetImageInfo(req.Request.Context(), project, req.QueryParameter("secretName"), req.QueryParameter("name"))
|
||||
err := res.WriteEntity(imageInfo)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func isSkipCache(req *restful.Request) (bool, error) {
|
||||
skipStr := req.QueryParameter("skipCache")
|
||||
skipCache := false
|
||||
@@ -264,6 +264,10 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
|
||||
var traitStatusList []common.ApplicationTraitStatus
|
||||
for _, tr := range wl.Traits {
|
||||
if tr.FullTemplate.TraitDefinition.Spec.ControlPlaneOnly {
|
||||
namespace = appRev.GetNamespace()
|
||||
wl.Ctx.SetCtx(context.WithValue(wl.Ctx.GetCtx(), multicluster.ClusterContextKey, multicluster.ClusterLocalName))
|
||||
}
|
||||
var traitStatus = common.ApplicationTraitStatus{
|
||||
Type: tr.Name,
|
||||
Healthy: true,
|
||||
@@ -277,6 +281,8 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate status message error", appName, wl.Name, tr.Name)
|
||||
}
|
||||
traitStatusList = append(traitStatusList, traitStatus)
|
||||
namespace = appRev.GetNamespace()
|
||||
wl.Ctx.SetCtx(context.WithValue(wl.Ctx.GetCtx(), multicluster.ClusterContextKey, status.Cluster))
|
||||
}
|
||||
|
||||
status.Traits = traitStatusList
|
||||
|
||||
@@ -302,7 +302,7 @@ func strategyUnify(baseFile *ast.File, patchFile *ast.File, params *UnifyParams,
|
||||
|
||||
ret := baseInst.Value().Unify(patchInst.Value())
|
||||
|
||||
rv, err := toString(ret)
|
||||
rv, err := toString(ret, removeTmpVar)
|
||||
if err != nil {
|
||||
return rv, errors.WithMessage(err, " format result toString")
|
||||
}
|
||||
|
||||
@@ -358,3 +358,38 @@ func listOpen(expr ast.Node) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeTmpVar(expr ast.Node) ast.Node {
|
||||
switch v := expr.(type) {
|
||||
case *ast.File:
|
||||
for _, decl := range v.Decls {
|
||||
removeTmpVar(decl)
|
||||
}
|
||||
case *ast.Field:
|
||||
removeTmpVar(v.Value)
|
||||
case *ast.StructLit:
|
||||
var elts []ast.Decl
|
||||
for _, elt := range v.Elts {
|
||||
if field, isField := elt.(*ast.Field); isField {
|
||||
if ident, isIdent := field.Label.(*ast.Ident); isIdent && strings.HasPrefix(ident.Name, "_") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
removeTmpVar(elt)
|
||||
elts = append(elts, elt)
|
||||
}
|
||||
v.Elts = elts
|
||||
case *ast.BinaryExpr:
|
||||
removeTmpVar(v.X)
|
||||
removeTmpVar(v.Y)
|
||||
case *ast.EmbedDecl:
|
||||
removeTmpVar(v.Expr)
|
||||
case *ast.Comprehension:
|
||||
removeTmpVar(v.Value)
|
||||
case *ast.ListLit:
|
||||
for _, elt := range v.Elts {
|
||||
removeTmpVar(elt)
|
||||
}
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"cuelang.org/go/cue/ast"
|
||||
"cuelang.org/go/cue/parser"
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
@@ -128,3 +129,33 @@ func TestWalk(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemoveTmpVar(t *testing.T) {
|
||||
src := `spec: {
|
||||
_tmp: "x"
|
||||
list: [{
|
||||
_tmp: "x"
|
||||
retain: "y"
|
||||
}, {
|
||||
_tmp: "x"
|
||||
retain: "z"
|
||||
}]
|
||||
retain: "y"
|
||||
}
|
||||
`
|
||||
r := require.New(t)
|
||||
var runtime cue.Runtime
|
||||
inst, err := runtime.Compile("-", src)
|
||||
r.NoError(err)
|
||||
s, err := toString(inst.Value(), removeTmpVar)
|
||||
r.NoError(err)
|
||||
r.Equal(`spec: {
|
||||
list: [{
|
||||
retain: "y"
|
||||
}, {
|
||||
retain: "z"
|
||||
}]
|
||||
retain: "y"
|
||||
}
|
||||
`, s)
|
||||
}
|
||||
|
||||
@@ -302,9 +302,6 @@ func (ctx *templateContext) GetCtx() context.Context {
|
||||
}
|
||||
|
||||
func (ctx *templateContext) SetCtx(newContext context.Context) {
|
||||
if ctx.ctx != nil {
|
||||
return
|
||||
}
|
||||
ctx.ctx = newContext
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
|
||||
clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
|
||||
"github.com/oam-dev/cluster-register/pkg/hub"
|
||||
"github.com/oam-dev/cluster-register/pkg/spoke"
|
||||
"github.com/pkg/errors"
|
||||
@@ -36,11 +39,7 @@ import (
|
||||
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/policy/envbinding"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
@@ -49,6 +48,7 @@ import (
|
||||
|
||||
// KubeClusterConfig info for cluster management
|
||||
type KubeClusterConfig struct {
|
||||
FilePath string
|
||||
ClusterName string
|
||||
CreateNamespace string
|
||||
*clientcmdapi.Config
|
||||
@@ -84,17 +84,26 @@ func (clusterConfig *KubeClusterConfig) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterByVelaSecret create cluster secrets for KubeVela to use
|
||||
func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context, cli client.Client) error {
|
||||
if err := ensureClusterNotExists(ctx, cli, clusterConfig.ClusterName); err != nil {
|
||||
return errors.Wrapf(err, "cannot use cluster name %s", clusterConfig.ClusterName)
|
||||
// PostRegistration try to create namespace after cluster registered. If failed, cluster will be unregistered.
|
||||
func (clusterConfig *KubeClusterConfig) PostRegistration(ctx context.Context, cli client.Client) error {
|
||||
if clusterConfig.CreateNamespace == "" {
|
||||
return nil
|
||||
}
|
||||
if err := ensureNamespaceExists(ctx, cli, clusterConfig.ClusterName, clusterConfig.CreateNamespace); err != nil {
|
||||
_ = DetachCluster(ctx, cli, clusterConfig.ClusterName, DetachClusterManagedClusterKubeConfigPathOption(clusterConfig.FilePath))
|
||||
return fmt.Errorf("failed to ensure %s namespace installed in cluster %s: %w", clusterConfig.CreateNamespace, clusterConfig.ClusterName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (clusterConfig *KubeClusterConfig) createClusterSecret(ctx context.Context, cli client.Client, withEndpoint bool) error {
|
||||
var credentialType clusterv1alpha1.CredentialType
|
||||
data := map[string][]byte{
|
||||
"endpoint": []byte(clusterConfig.Cluster.Server),
|
||||
}
|
||||
if !clusterConfig.Cluster.InsecureSkipTLSVerify {
|
||||
data["ca.crt"] = clusterConfig.Cluster.CertificateAuthorityData
|
||||
data := map[string][]byte{}
|
||||
if withEndpoint {
|
||||
data["endpoint"] = []byte(clusterConfig.Cluster.Server)
|
||||
if !clusterConfig.Cluster.InsecureSkipTLSVerify {
|
||||
data["ca.crt"] = clusterConfig.Cluster.CertificateAuthorityData
|
||||
}
|
||||
}
|
||||
if len(clusterConfig.AuthInfo.Token) > 0 {
|
||||
credentialType = clusterv1alpha1.CredentialTypeServiceAccountToken
|
||||
@@ -115,22 +124,50 @@ func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: data,
|
||||
}
|
||||
if err := cli.Create(ctx, secret); err != nil {
|
||||
return cli.Create(ctx, secret)
|
||||
}
|
||||
|
||||
// RegisterByVelaSecret create cluster secrets for KubeVela to use
|
||||
func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context, cli client.Client) error {
|
||||
if err := ensureClusterNotExists(ctx, cli, clusterConfig.ClusterName); err != nil {
|
||||
return errors.Wrapf(err, "cannot use cluster name %s", clusterConfig.ClusterName)
|
||||
}
|
||||
if err := clusterConfig.createClusterSecret(ctx, cli, true); err != nil {
|
||||
return errors.Wrapf(err, "failed to add cluster to kubernetes")
|
||||
}
|
||||
// TODO(somefive): create namespace now only work for cluster secret
|
||||
if clusterConfig.CreateNamespace != "" {
|
||||
if err := ensureNamespaceExists(ctx, cli, clusterConfig.ClusterName, clusterConfig.CreateNamespace); err != nil {
|
||||
_ = cli.Delete(ctx, secret)
|
||||
return errors.Wrapf(err, "failed to ensure %s namespace installed in cluster %s", clusterConfig.CreateNamespace, clusterConfig.ClusterName)
|
||||
return clusterConfig.PostRegistration(ctx, cli)
|
||||
}
|
||||
|
||||
// CreateBootstrapConfigMapIfNotExists alternative to
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.24.1/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go#L43
|
||||
func CreateBootstrapConfigMapIfNotExists(ctx context.Context, cli client.Client) error {
|
||||
cm := &corev1.ConfigMap{}
|
||||
key := apitypes.NamespacedName{Namespace: metav1.NamespacePublic, Name: "cluster-info"}
|
||||
if err := cli.Get(ctx, key, cm); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
cm.ObjectMeta = metav1.ObjectMeta{Namespace: key.Namespace, Name: key.Name}
|
||||
adminConfig, err := clientcmd.NewDefaultPathOptions().GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adminCluster := adminConfig.Contexts[adminConfig.CurrentContext].Cluster
|
||||
bs, err := clientcmd.Write(clientcmdapi.Config{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{"": adminConfig.Clusters[adminCluster]},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data = map[string]string{"kubeconfig": string(bs)}
|
||||
return cli.Create(ctx, cm)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterClusterManagedByOCM create ocm managed cluster for use
|
||||
// TODO(somefive): OCM ManagedCluster only support cli join now
|
||||
func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.Context, args *JoinClusterArgs) error {
|
||||
func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.Context, cli client.Client, args *JoinClusterArgs) error {
|
||||
newTrackingSpinner := args.trackingSpinnerFactory
|
||||
hubCluster, err := hub.NewHubCluster(args.hubConfig)
|
||||
if err != nil {
|
||||
@@ -164,6 +201,9 @@ func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.
|
||||
return errors.Wrap(err, "fail to convert spoke-cluster kubeconfig")
|
||||
}
|
||||
|
||||
if err = CreateBootstrapConfigMapIfNotExists(ctx, cli); err != nil {
|
||||
return fmt.Errorf("failed to ensure cluster-info ConfigMap in kube-public namespace exists: %w", err)
|
||||
}
|
||||
spokeTracker := newTrackingSpinner("Building registration config for the managed cluster")
|
||||
spokeTracker.FinalMSG = "Successfully prepared registration config.\n"
|
||||
spokeTracker.Start()
|
||||
@@ -172,6 +212,7 @@ func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.
|
||||
args.ioStreams.Infof("Using the api endpoint from hub kubeconfig %q as registration entry.\n", args.hubConfig.Host)
|
||||
overridingRegistrationEndpoint = args.hubConfig.Host
|
||||
}
|
||||
|
||||
hubKubeToken, err := hubCluster.GenerateHubClusterKubeConfig(ctx, overridingRegistrationEndpoint)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fail to generate the token for spoke-cluster")
|
||||
@@ -230,7 +271,7 @@ func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.
|
||||
|
||||
// LoadKubeClusterConfigFromFile create KubeClusterConfig from kubeconfig file
|
||||
func LoadKubeClusterConfigFromFile(filepath string) (*KubeClusterConfig, error) {
|
||||
clusterConfig := &KubeClusterConfig{}
|
||||
clusterConfig := &KubeClusterConfig{FilePath: filepath}
|
||||
var err error
|
||||
clusterConfig.Config, err = clientcmd.LoadFromFile(filepath)
|
||||
if err != nil {
|
||||
@@ -345,7 +386,7 @@ func JoinClusterByKubeConfig(ctx context.Context, cli client.Client, kubeconfigP
|
||||
return nil, errors.Wrapf(err, "failed to determine the registration endpoint for the hub cluster "+
|
||||
"when parsing --in-cluster-bootstrap flag")
|
||||
}
|
||||
if err = clusterConfig.RegisterClusterManagedByOCM(ctx, args); err != nil {
|
||||
if err = clusterConfig.RegisterClusterManagedByOCM(ctx, cli, args); err != nil {
|
||||
return clusterConfig, err
|
||||
}
|
||||
}
|
||||
@@ -384,12 +425,12 @@ func DetachCluster(ctx context.Context, cli client.Client, clusterName string, o
|
||||
if clusterName == ClusterLocalName {
|
||||
return ErrReservedLocalClusterName
|
||||
}
|
||||
vc, err := GetVirtualCluster(ctx, cli, clusterName)
|
||||
vc, err := prismclusterv1alpha1.NewClusterClient(cli).Get(ctx, clusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch vc.Type {
|
||||
switch vc.Spec.CredentialType {
|
||||
case clusterv1alpha1.CredentialTypeX509Certificate, clusterv1alpha1.CredentialTypeServiceAccountToken:
|
||||
clusterSecret, err := getMutableClusterSecret(ctx, cli, clusterName)
|
||||
if err != nil {
|
||||
@@ -398,7 +439,7 @@ func DetachCluster(ctx context.Context, cli client.Client, clusterName string, o
|
||||
if err := cli.Delete(ctx, clusterSecret); err != nil {
|
||||
return errors.Wrapf(err, "failed to detach cluster %s", clusterName)
|
||||
}
|
||||
case types.CredentialTypeOCMManagedCluster:
|
||||
case prismclusterv1alpha1.CredentialTypeOCMManagedCluster:
|
||||
if args.managedClusterKubeConfigPath == "" {
|
||||
return errors.New("kubeconfig-path must be set to detach ocm managed cluster")
|
||||
}
|
||||
@@ -416,7 +457,7 @@ func DetachCluster(ctx context.Context, cli client.Client, clusterName string, o
|
||||
return err
|
||||
}
|
||||
managedCluster := ocmclusterv1.ManagedCluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName}}
|
||||
if err = cli.Delete(context.Background(), &managedCluster); err != nil {
|
||||
if err = cli.Delete(ctx, &managedCluster); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ func newDeleteConfig(options ...DeleteOption) *deleteConfig {
|
||||
|
||||
// Delete delete resources
|
||||
func (h *resourceKeeper) Delete(ctx context.Context, manifests []*unstructured.Unstructured, options ...DeleteOption) (err error) {
|
||||
h.ClearNamespaceForClusterScopedResources(manifests)
|
||||
if err = h.AdmissionCheck(ctx, manifests); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ func (h *resourceKeeper) Dispatch(ctx context.Context, manifests []*unstructured
|
||||
if h.applyOncePolicy != nil && h.applyOncePolicy.Enable && h.applyOncePolicy.Rules == nil {
|
||||
options = append(options, MetaOnlyOption{})
|
||||
}
|
||||
h.ClearNamespaceForClusterScopedResources(manifests)
|
||||
// 0. check admission
|
||||
if err = h.AdmissionCheck(ctx, manifests); err != nil {
|
||||
return err
|
||||
|
||||
35
pkg/resourcekeeper/utils.go
Normal file
35
pkg/resourcekeeper/utils.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resourcekeeper
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
// ClearNamespaceForClusterScopedResources clear namespace for cluster scoped resources
|
||||
func (h *resourceKeeper) ClearNamespaceForClusterScopedResources(manifests []*unstructured.Unstructured) {
|
||||
for _, manifest := range manifests {
|
||||
mappings, err := h.Client.RESTMapper().RESTMappings(manifest.GroupVersionKind().GroupKind(), manifest.GroupVersionKind().Version)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(mappings) > 0 && mappings[0].Scope.Name() == meta.RESTScopeNameRoot {
|
||||
manifest.SetNamespace("")
|
||||
}
|
||||
}
|
||||
}
|
||||
55
pkg/resourcekeeper/utils_test.go
Normal file
55
pkg/resourcekeeper/utils_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resourcekeeper
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
||||
var _ = Describe("Test ResourceKeeper utilities", func() {
|
||||
|
||||
It("Test ClearNamespaceForClusterScopedResources", func() {
|
||||
cli := testClient
|
||||
h := &resourceKeeper{
|
||||
Client: cli,
|
||||
app: &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "default"}},
|
||||
applicator: apply.NewAPIApplicator(cli),
|
||||
cache: newResourceCache(cli),
|
||||
}
|
||||
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "example", Namespace: "vela"}}
|
||||
nsObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ns)
|
||||
Expect(err).Should(Succeed())
|
||||
cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "example", Namespace: "vela"}}
|
||||
cmObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm)
|
||||
Expect(err).Should(Succeed())
|
||||
uns := []*unstructured.Unstructured{{Object: nsObj}, {Object: cmObj}}
|
||||
uns[0].SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Namespace"))
|
||||
uns[1].SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap"))
|
||||
h.ClearNamespaceForClusterScopedResources(uns)
|
||||
Expect(uns[0].GetNamespace()).Should(Equal(""))
|
||||
Expect(uns[1].GetNamespace()).Should(Equal("vela"))
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
)
|
||||
|
||||
// Meta is the struct for image metadata
|
||||
type Meta struct {
|
||||
Registry string
|
||||
Repository string
|
||||
Name string
|
||||
Tag string
|
||||
}
|
||||
|
||||
// DockerHubImageTagResponse is the struct for docker hub image tag response
|
||||
type DockerHubImageTagResponse struct {
|
||||
Count int `json:"count"`
|
||||
Results []Result
|
||||
}
|
||||
|
||||
// Result is the struct for docker hub image tag result
|
||||
type Result struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// IsExisted checks whether a public or private image exists
|
||||
func IsExisted(username, password, image string) (bool, error) {
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var img v1.Image
|
||||
|
||||
if username != "" || password != "" {
|
||||
basic := &authn.Basic{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
option := remote.WithAuth(basic)
|
||||
img, err = remote.Image(ref, option)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
img, err = remote.Image(ref)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = img.Digest()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = img.Manifest()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RegistryMeta is the struct for registry metadata
|
||||
type RegistryMeta struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExisted(t *testing.T) {
|
||||
type args struct {
|
||||
username string
|
||||
password string
|
||||
image string
|
||||
}
|
||||
type want struct {
|
||||
existed bool
|
||||
errMsg string
|
||||
}
|
||||
testcases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"empty image name": {
|
||||
args: args{
|
||||
image: "",
|
||||
},
|
||||
want: want{
|
||||
existed: false,
|
||||
errMsg: "could not parse referenc",
|
||||
},
|
||||
},
|
||||
"just image name": {
|
||||
args: args{
|
||||
image: "nginx",
|
||||
},
|
||||
want: want{
|
||||
existed: true,
|
||||
},
|
||||
},
|
||||
" image name with registry": {
|
||||
args: args{
|
||||
image: "docker.io/nginx",
|
||||
},
|
||||
want: want{
|
||||
existed: true,
|
||||
},
|
||||
},
|
||||
"image name with tag": {
|
||||
args: args{
|
||||
image: "nginx:latest",
|
||||
},
|
||||
want: want{
|
||||
existed: true,
|
||||
},
|
||||
},
|
||||
"image name with repository": {
|
||||
args: args{
|
||||
image: "library/nginx:latest",
|
||||
},
|
||||
want: want{
|
||||
existed: true,
|
||||
},
|
||||
},
|
||||
"image name with registry and repository": {
|
||||
args: args{
|
||||
image: "docker.io/library/nginx",
|
||||
},
|
||||
want: want{
|
||||
existed: true,
|
||||
},
|
||||
},
|
||||
"invalid image name": {
|
||||
args: args{
|
||||
image: "jfsdfjwfjwf:fwefsfsjflwejfwjfoewfsffsfw",
|
||||
},
|
||||
want: want{
|
||||
existed: false,
|
||||
errMsg: "UNAUTHORIZED",
|
||||
},
|
||||
},
|
||||
"invalid image registry": {
|
||||
args: args{
|
||||
image: "nginx1sf/jfsdfjwfjwf:fwefsfsjflwejfwjfoewfsffsfw",
|
||||
},
|
||||
want: want{
|
||||
existed: false,
|
||||
errMsg: "UNAUTHORIZED",
|
||||
},
|
||||
},
|
||||
"not docker hub registry": {
|
||||
args: args{
|
||||
image: "alibabacloud.com/d/e:v0.1",
|
||||
},
|
||||
want: want{
|
||||
existed: false,
|
||||
errMsg: "invalid character '<' looking for beginning of value",
|
||||
},
|
||||
},
|
||||
"private registry, invalid authentication": {
|
||||
args: args{
|
||||
image: "abcfefsfjflsfjweffwe73rr.com/d/e:v0.1",
|
||||
username: "admin",
|
||||
},
|
||||
want: want{
|
||||
existed: false,
|
||||
errMsg: "Get",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, err := IsExisted(tc.args.username, tc.args.password, tc.args.image)
|
||||
if err != nil || tc.want.errMsg != "" {
|
||||
assert.Contains(t, err.Error(), tc.want.errMsg)
|
||||
}
|
||||
assert.Equal(t, got, tc.want.existed)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,9 +38,12 @@ const (
|
||||
addonGitToken = "gitToken"
|
||||
addonOssType = "OSS"
|
||||
addonGitType = "git"
|
||||
addonGiteeType = "gitee"
|
||||
addonGitlabType = "gitlab"
|
||||
addonHelmType = "helm"
|
||||
addonUsername = "username"
|
||||
addonPassword = "password"
|
||||
addonRepoName = "repoName"
|
||||
)
|
||||
|
||||
// NewAddonRegistryCommand return an addon registry command
|
||||
@@ -344,6 +347,37 @@ func getRegistryFromArgs(cmd *cobra.Command, args []string) (*pkgaddon.Registry,
|
||||
return nil, err
|
||||
}
|
||||
r.Git.Token = token
|
||||
case addonGiteeType:
|
||||
r.Gitee = &pkgaddon.GiteeAddonSource{}
|
||||
r.Gitee.URL = endpoint
|
||||
path, err := cmd.Flags().GetString(addonPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Gitee.Path = path
|
||||
token, err := cmd.Flags().GetString(addonGitToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Gitee.Token = token
|
||||
case addonGitlabType:
|
||||
r.Gitlab = &pkgaddon.GitlabAddonSource{}
|
||||
r.Gitlab.URL = endpoint
|
||||
path, err := cmd.Flags().GetString(addonPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Gitlab.Path = path
|
||||
token, err := cmd.Flags().GetString(addonGitToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Gitlab.Token = token
|
||||
gitLabRepoName, err := cmd.Flags().GetString(addonRepoName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Gitlab.Repo = gitLabRepoName
|
||||
case addonHelmType:
|
||||
r.Helm = &pkgaddon.HelmSource{}
|
||||
r.Helm.URL = endpoint
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/fatih/color"
|
||||
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/oam-dev/cluster-gateway/pkg/config"
|
||||
"github.com/oam-dev/cluster-gateway/pkg/generated/clientset/versioned"
|
||||
"github.com/pkg/errors"
|
||||
@@ -107,11 +108,11 @@ func NewClusterListCommand(c *common.Args) *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clusters, err := multicluster.ListVirtualClusters(context.Background(), client)
|
||||
clusters, err := prismclusterv1alpha1.NewClusterClient(client).List(context.Background())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fail to get registered cluster")
|
||||
}
|
||||
for _, cluster := range clusters {
|
||||
for _, cluster := range clusters.Items {
|
||||
var labels []string
|
||||
for k, v := range cluster.Labels {
|
||||
if !strings.HasPrefix(k, config.MetaApiGroupName) {
|
||||
@@ -124,7 +125,7 @@ func NewClusterListCommand(c *common.Args) *cobra.Command {
|
||||
}
|
||||
for i, l := range labels {
|
||||
if i == 0 {
|
||||
table.AddRow(cluster.Name, cluster.Alias, cluster.Type, cluster.EndPoint, fmt.Sprintf("%v", cluster.Accepted), l)
|
||||
table.AddRow(cluster.Name, cluster.Spec.Alias, cluster.Spec.CredentialType, cluster.Spec.Endpoint, fmt.Sprintf("%v", cluster.Spec.Accepted), l)
|
||||
} else {
|
||||
table.AddRow("", "", "", "", "", l)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
"config-image-registry": {
|
||||
@@ -39,8 +40,8 @@ template: {
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
stringData: {
|
||||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
"auths": "\(parameter.registry)": {
|
||||
"username": parameter.auth.username
|
||||
@@ -52,11 +53,17 @@ template: {
|
||||
}
|
||||
})
|
||||
}
|
||||
if parameter.insecure != _|_ {
|
||||
"insecure-skip-verify": strconv.FormatBool(parameter.insecure)
|
||||
}
|
||||
if parameter.useHTTP != _|_ {
|
||||
"protocol-use-http": strconv.FormatBool(parameter.useHTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Image registry FQDN
|
||||
// +usage=Image registry FQDN, such as: index.docker.io
|
||||
registry: string
|
||||
// +usage=Authenticate the image registry
|
||||
auth?: {
|
||||
@@ -67,5 +74,9 @@ template: {
|
||||
// +usage=Private Image registry email
|
||||
email?: string
|
||||
}
|
||||
// +usage=For the registry server that uses the self-signed certificate
|
||||
insecure?: bool
|
||||
// +usage=For the registry server that uses the HTTP protocol
|
||||
useHTTP?: bool
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user