add devops credential controller
Signed-off-by: runzexia <runzexia@yunify.com>
This commit is contained in:
56
pkg/apis/devops/v1alpha3/credential_types.go
Normal file
56
pkg/apis/devops/v1alpha3/credential_types.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package v1alpha3
|
||||
|
||||
import v1 "k8s.io/api/core/v1"
|
||||
|
||||
/**
|
||||
We use a special type of secret as a credential for DevOps.
|
||||
This file will not contain CRD, but the credential type constants and their fields.
|
||||
*/
|
||||
const (
|
||||
CredentialFinalizerName = "credential.finalizers.kubesphere.io"
|
||||
DevOpsCredentialPrefix = "credential.devops.kubesphere.io/"
|
||||
// SecretTypeBasicAuth contains data needed for basic authentication.
|
||||
//
|
||||
// Required at least one of fields:
|
||||
// - Secret.Data["username"] - username used for authentication
|
||||
// - Secret.Data["password"] - password or token needed for authentication
|
||||
SecretTypeBasicAuth v1.SecretType = DevOpsCredentialPrefix + "basic-auth"
|
||||
// BasicAuthUsernameKey is the key of the username for SecretTypeBasicAuth secrets
|
||||
BasicAuthUsernameKey = "username"
|
||||
// BasicAuthPasswordKey is the key of the password or token for SecretTypeBasicAuth secrets
|
||||
BasicAuthPasswordKey = "password"
|
||||
|
||||
// SecretTypeSSHAuth contains data needed for ssh authentication.
|
||||
//
|
||||
// Required at least one of fields:
|
||||
// - Secret.Data["username"] - username used for authentication
|
||||
// - Secret.Data["passphrase"] - passphrase needed for authentication
|
||||
// - Secret.Data["privatekey"] - privatekey needed for authentication
|
||||
SecretTypeSSHAuth v1.SecretType = DevOpsCredentialPrefix + "ssh-auth"
|
||||
// SSHAuthUsernameKey is the key of the username for SecretTypeSSHAuth secrets
|
||||
SSHAuthUsernameKey = "username"
|
||||
// SSHAuthPrivateKey is the key of the passphrase for SecretTypeSSHAuth secrets
|
||||
SSHAuthPassphraseKey = "passphrase"
|
||||
// SSHAuthPrivateKey is the key of the privatekey for SecretTypeSSHAuth secrets
|
||||
SSHAuthPrivateKey = "privatekey"
|
||||
|
||||
// SecretTypeSecretText contains data.
|
||||
//
|
||||
// Required at least one of fields:
|
||||
// - Secret.Data["secret"] - secret
|
||||
SecretTypeSecretText v1.SecretType = DevOpsCredentialPrefix + "secret-text"
|
||||
// SecretTextSecretKey is the key of the secret for SecretTypeSecretText secrets
|
||||
SecretTextSecretKey = "secret"
|
||||
|
||||
// SecretTypeKubeConfig contains data.
|
||||
//
|
||||
// Required at least one of fields:
|
||||
// - Secret.Data["secret"] - secret
|
||||
SecretTypeKubeConfig v1.SecretType = DevOpsCredentialPrefix + "kubeconfig"
|
||||
// KubeConfigSecretKey is the key of the secret for SecretTypeKubeConfig secrets
|
||||
KubeConfigSecretKey = "secret"
|
||||
// CredentialAutoSyncAnnoKey is used to indicate whether the secret is automatically synchronized to devops.
|
||||
// In the old version, the credential is stored in jenkins and cannot be obtained.
|
||||
// This field is set to ensure that the secret is not overwritten by a nil value.
|
||||
CredentialAutoSyncAnnoKey = DevOpsCredentialPrefix + "autosync"
|
||||
)
|
||||
284
pkg/controller/devopscredential/devopscredential_controller.go
Normal file
284
pkg/controller/devopscredential/devopscredential_controller.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package devopscredential
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
corev1informer "k8s.io/client-go/informers/core/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
corev1lister "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog"
|
||||
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
|
||||
kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
|
||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**
|
||||
DevOps project controller is used to maintain the state of the DevOps project.
|
||||
*/
|
||||
|
||||
type Controller struct {
|
||||
client clientset.Interface
|
||||
kubesphereClient kubesphereclient.Interface
|
||||
|
||||
eventBroadcaster record.EventBroadcaster
|
||||
eventRecorder record.EventRecorder
|
||||
|
||||
secretLister corev1lister.SecretLister
|
||||
secretSynced cache.InformerSynced
|
||||
|
||||
namespaceLister corev1lister.NamespaceLister
|
||||
namespaceSynced cache.InformerSynced
|
||||
|
||||
workqueue workqueue.RateLimitingInterface
|
||||
|
||||
workerLoopPeriod time.Duration
|
||||
|
||||
devopsClient devopsClient.Interface
|
||||
}
|
||||
|
||||
func NewController(client clientset.Interface,
|
||||
devopsClinet devopsClient.Interface,
|
||||
namespaceInformer corev1informer.NamespaceInformer,
|
||||
secretInformer corev1informer.SecretInformer) *Controller {
|
||||
|
||||
broadcaster := record.NewBroadcaster()
|
||||
broadcaster.StartLogging(func(format string, args ...interface{}) {
|
||||
klog.Info(fmt.Sprintf(format, args))
|
||||
})
|
||||
broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
|
||||
recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "pipeline-controller"})
|
||||
|
||||
v := &Controller{
|
||||
client: client,
|
||||
devopsClient: devopsClinet,
|
||||
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pipeline"),
|
||||
secretLister: secretInformer.Lister(),
|
||||
secretSynced: secretInformer.Informer().HasSynced,
|
||||
namespaceLister: namespaceInformer.Lister(),
|
||||
namespaceSynced: namespaceInformer.Informer().HasSynced,
|
||||
workerLoopPeriod: time.Second,
|
||||
}
|
||||
|
||||
v.eventBroadcaster = broadcaster
|
||||
v.eventRecorder = recorder
|
||||
|
||||
secretInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
secret := obj.(*v1.Secret)
|
||||
if strings.HasPrefix(string(secret.Type), devopsv1alpha3.DevOpsCredentialPrefix) {
|
||||
v.enqueueSecret(obj)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
old := oldObj.(*v1.Secret)
|
||||
new := newObj.(*v1.Secret)
|
||||
if old.ResourceVersion == new.ResourceVersion {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(string(new.Type), devopsv1alpha3.DevOpsCredentialPrefix) {
|
||||
v.enqueueSecret(newObj)
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
secret := obj.(*v1.Secret)
|
||||
if strings.HasPrefix(string(secret.Type), devopsv1alpha3.DevOpsCredentialPrefix) {
|
||||
v.enqueueSecret(obj)
|
||||
}
|
||||
},
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
// enqueueSecret takes a Foo resource and converts it into a namespace/name
|
||||
// string which is then put onto the work workqueue. This method should *not* be
|
||||
// passed resources of any type other than DevOpsProject.
|
||||
func (c *Controller) enqueueSecret(obj interface{}) {
|
||||
var key string
|
||||
var err error
|
||||
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
c.workqueue.Add(key)
|
||||
}
|
||||
|
||||
func (c *Controller) processNextWorkItem() bool {
|
||||
obj, shutdown := c.workqueue.Get()
|
||||
|
||||
if shutdown {
|
||||
return false
|
||||
}
|
||||
|
||||
err := func(obj interface{}) error {
|
||||
defer c.workqueue.Done(obj)
|
||||
var key string
|
||||
var ok bool
|
||||
|
||||
if key, ok = obj.(string); !ok {
|
||||
c.workqueue.Forget(obj)
|
||||
utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
|
||||
return nil
|
||||
}
|
||||
if err := c.syncHandler(key); err != nil {
|
||||
c.workqueue.AddRateLimited(key)
|
||||
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
|
||||
}
|
||||
c.workqueue.Forget(obj)
|
||||
klog.V(5).Infof("Successfully synced '%s'", key)
|
||||
return nil
|
||||
}(obj)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err, "could not reconcile devopsProject")
|
||||
utilruntime.HandleError(err)
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Controller) worker() {
|
||||
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) Start(stopCh <-chan struct{}) error {
|
||||
return c.Run(1, stopCh)
|
||||
}
|
||||
|
||||
func (c *Controller) Run(workers int, stopCh <-chan struct{}) error {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.workqueue.ShutDown()
|
||||
|
||||
klog.Info("starting pipeline controller")
|
||||
defer klog.Info("shutting down pipeline controller")
|
||||
|
||||
if !cache.WaitForCacheSync(stopCh, c.secretSynced) {
|
||||
return fmt.Errorf("failed to wait for caches to sync")
|
||||
}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(c.worker, c.workerLoopPeriod, stopCh)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncHandler compares the actual state with the desired, and attempts to
|
||||
// converge the two. It then updates the Status block of the pipeline resource
|
||||
// with the current status of the resource.
|
||||
func (c *Controller) syncHandler(key string) error {
|
||||
nsName, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
klog.Error(err, fmt.Sprintf("could not split copySecret meta %s ", key))
|
||||
return nil
|
||||
}
|
||||
namespace, err := c.namespaceLister.Get(nsName)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
klog.Info(fmt.Sprintf("namespace '%s' in work queue no longer exists ", key))
|
||||
return nil
|
||||
}
|
||||
klog.Error(err, fmt.Sprintf("could not get namespace %s ", key))
|
||||
return err
|
||||
}
|
||||
if !isDevOpsProjectAdminNamespace(namespace) {
|
||||
err := fmt.Errorf("cound not create credential in normal namespaces %s", namespace.Name)
|
||||
klog.Warning(err)
|
||||
return err
|
||||
}
|
||||
|
||||
secret, err := c.secretLister.Secrets(nsName).Get(name)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
klog.Info(fmt.Sprintf("secret '%s' in work queue no longer exists ", key))
|
||||
return nil
|
||||
}
|
||||
klog.Error(err, fmt.Sprintf("could not get secret %s ", key))
|
||||
return err
|
||||
}
|
||||
|
||||
copySecret := secret.DeepCopy()
|
||||
// DeletionTimestamp.IsZero() means copySecret has not been deleted.
|
||||
if copySecret.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
// https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#finalizers
|
||||
if !sliceutil.HasString(copySecret.ObjectMeta.Finalizers, devopsv1alpha3.CredentialFinalizerName) {
|
||||
copySecret.ObjectMeta.Finalizers = append(copySecret.ObjectMeta.Finalizers, devopsv1alpha3.CredentialFinalizerName)
|
||||
}
|
||||
// Check secret config exists, otherwise we will create it.
|
||||
// if secret exists, update config
|
||||
_, err := c.devopsClient.GetCredentialInProject(nsName, secret.Name)
|
||||
if err != nil && devopsClient.GetDevOpsStatusCode(err) != http.StatusNotFound {
|
||||
klog.Error(err, fmt.Sprintf("failed to get secret %s ", key))
|
||||
return err
|
||||
} else if err != nil {
|
||||
_, err := c.devopsClient.CreateCredentialInProject(nsName, copySecret)
|
||||
if err != nil {
|
||||
klog.Error(err, fmt.Sprintf("failed to create secret %s ", key))
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, ok := copySecret.Annotations[devopsv1alpha3.CredentialAutoSyncAnnoKey]; ok {
|
||||
_, err := c.devopsClient.UpdateCredentialInProject(nsName, copySecret)
|
||||
if err != nil {
|
||||
klog.Error(err, fmt.Sprintf("failed to update secret %s ", key))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Finalizers processing logic
|
||||
if sliceutil.HasString(copySecret.ObjectMeta.Finalizers, devopsv1alpha3.CredentialFinalizerName) {
|
||||
_, err := c.devopsClient.GetCredentialInProject(nsName, secret.Name)
|
||||
if err != nil && devopsClient.GetDevOpsStatusCode(err) != http.StatusNotFound {
|
||||
klog.Error(err, fmt.Sprintf("failed to get secret %s ", key))
|
||||
return err
|
||||
} else if err != nil && devopsClient.GetDevOpsStatusCode(err) == http.StatusNotFound {
|
||||
} else {
|
||||
if _, err := c.devopsClient.DeleteCredentialInProject(nsName, secret.Name); err != nil {
|
||||
klog.Error(err, fmt.Sprintf("failed to delete secret %s in devops", key))
|
||||
return err
|
||||
}
|
||||
}
|
||||
copySecret.ObjectMeta.Finalizers = sliceutil.RemoveString(copySecret.ObjectMeta.Finalizers, func(item string) bool {
|
||||
return item == devopsv1alpha3.CredentialFinalizerName
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(secret, copySecret) {
|
||||
_, err = c.client.CoreV1().Secrets(nsName).Update(copySecret)
|
||||
if err != nil {
|
||||
klog.Error(err, fmt.Sprintf("failed to update secret %s ", key))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isDevOpsProjectAdminNamespace(namespace *v1.Namespace) bool {
|
||||
_, ok := namespace.Labels[constants.DevOpsProjectLabelKey]
|
||||
|
||||
return ok && k8sutil.IsControlledBy(namespace.OwnerReferences,
|
||||
devopsv1alpha3.ResourceKindDevOpsProject, "")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
package devopscredential
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
fakeDevOps "kubesphere.io/kubesphere/pkg/simple/client/devops/fake"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
k8sfake "k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
devops "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
|
||||
)
|
||||
|
||||
var (
|
||||
alwaysReady = func() bool { return true }
|
||||
noResyncPeriodFunc = func() time.Duration { return 0 }
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
t *testing.T
|
||||
|
||||
kubeclient *k8sfake.Clientset
|
||||
namespaceLister []*v1.Namespace
|
||||
secretLister []*v1.Secret
|
||||
kubeactions []core.Action
|
||||
|
||||
kubeobjects []runtime.Object
|
||||
// Objects from here preloaded into NewSimpleFake.
|
||||
objects []runtime.Object
|
||||
// Objects from here preloaded into devops
|
||||
initDevOpsProject string
|
||||
initCredential []*v1.Secret
|
||||
expectCredential []*v1.Secret
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
f := &fixture{}
|
||||
f.t = t
|
||||
f.objects = []runtime.Object{}
|
||||
return f
|
||||
}
|
||||
|
||||
func newNamespace(name string, projectName string) *v1.Namespace {
|
||||
ns := &v1.Namespace{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Namespace",
|
||||
APIVersion: v1.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{constants.DevOpsProjectLabelKey: projectName},
|
||||
},
|
||||
}
|
||||
TRUE := true
|
||||
ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: devops.SchemeGroupVersion.String(),
|
||||
Kind: devops.ResourceKindDevOpsProject,
|
||||
Name: projectName,
|
||||
BlockOwnerDeletion: &TRUE,
|
||||
Controller: &TRUE,
|
||||
},
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
func newSecret(namespace, name string, data map[string][]byte, withFinalizers bool, autoSync bool) *v1.Secret {
|
||||
secret := &v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: devops.ResourceKindPipeline,
|
||||
APIVersion: devops.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
},
|
||||
Data: data,
|
||||
Type: devops.DevOpsCredentialPrefix + "test",
|
||||
}
|
||||
if withFinalizers {
|
||||
secret.Finalizers = append(secret.Finalizers, devops.CredentialFinalizerName)
|
||||
}
|
||||
if autoSync{
|
||||
if secret.Annotations == nil{
|
||||
secret.Annotations = map[string]string{}
|
||||
}
|
||||
secret.Annotations[devops.CredentialAutoSyncAnnoKey] = "true"
|
||||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
func newDeletingSecret(namespace, name string) *v1.Secret {
|
||||
now := metav1.Now()
|
||||
pipeline := &v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: devops.ResourceKindPipeline,
|
||||
APIVersion: devops.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
DeletionTimestamp: &now,
|
||||
},
|
||||
Type: devops.DevOpsCredentialPrefix + "test",
|
||||
}
|
||||
pipeline.Finalizers = append(pipeline.Finalizers, devops.CredentialFinalizerName)
|
||||
|
||||
return pipeline
|
||||
}
|
||||
|
||||
func (f *fixture) newController() (*Controller, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
|
||||
f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
|
||||
|
||||
k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
|
||||
dI := fakeDevOps.NewWithCredentials(f.initDevOpsProject, f.initCredential...)
|
||||
|
||||
c := NewController(f.kubeclient, dI, k8sI.Core().V1().Namespaces(),
|
||||
k8sI.Core().V1().Secrets())
|
||||
|
||||
c.secretSynced = alwaysReady
|
||||
c.eventRecorder = &record.FakeRecorder{}
|
||||
|
||||
for _, f := range f.secretLister {
|
||||
k8sI.Core().V1().Secrets().Informer().GetIndexer().Add(f)
|
||||
}
|
||||
|
||||
for _, d := range f.namespaceLister {
|
||||
k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
|
||||
}
|
||||
|
||||
return c, k8sI, dI
|
||||
}
|
||||
|
||||
func (f *fixture) run(fooName string) {
|
||||
f.runController(fooName, true, false)
|
||||
}
|
||||
|
||||
func (f *fixture) runExpectError(fooName string) {
|
||||
f.runController(fooName, true, true)
|
||||
}
|
||||
|
||||
func (f *fixture) runController(name string, startInformers bool, expectError bool) {
|
||||
c, k8sI, dI := f.newController()
|
||||
if startInformers {
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
k8sI.Start(stopCh)
|
||||
}
|
||||
|
||||
err := c.syncHandler(name)
|
||||
if !expectError && err != nil {
|
||||
f.t.Errorf("error syncing foo: %v", err)
|
||||
} else if expectError && err == nil {
|
||||
f.t.Error("expected error syncing foo, got nil")
|
||||
}
|
||||
|
||||
k8sActions := filterInformerActions(f.kubeclient.Actions())
|
||||
for i, action := range k8sActions {
|
||||
if len(f.kubeactions) < i+1 {
|
||||
f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[i:])
|
||||
break
|
||||
}
|
||||
|
||||
expectedAction := f.kubeactions[i]
|
||||
checkAction(expectedAction, action, f.t)
|
||||
}
|
||||
|
||||
if len(f.kubeactions) > len(k8sActions) {
|
||||
f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
|
||||
}
|
||||
|
||||
if len(dI.Credentials[f.initDevOpsProject]) != len(f.expectCredential) {
|
||||
f.t.Errorf(" unexpected objects: %v", dI.Projects)
|
||||
}
|
||||
for _, credential := range f.expectCredential {
|
||||
actualCredential := dI.Credentials[f.initDevOpsProject][credential.Name]
|
||||
if !reflect.DeepEqual(actualCredential, credential) {
|
||||
f.t.Errorf(" credential %+v not match \n %+v", credential, actualCredential)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkAction verifies that expected and actual actions are equal and both have
|
||||
// same attached resources
|
||||
func checkAction(expected, actual core.Action, t *testing.T) {
|
||||
if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
|
||||
t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
|
||||
return
|
||||
}
|
||||
|
||||
if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
|
||||
t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
|
||||
return
|
||||
}
|
||||
|
||||
switch a := actual.(type) {
|
||||
case core.CreateActionImpl:
|
||||
e, _ := expected.(core.CreateActionImpl)
|
||||
expObject := e.GetObject()
|
||||
object := a.GetObject()
|
||||
|
||||
if !reflect.DeepEqual(expObject, object) {
|
||||
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
|
||||
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
|
||||
}
|
||||
case core.UpdateActionImpl:
|
||||
e, _ := expected.(core.UpdateActionImpl)
|
||||
expObject := e.GetObject()
|
||||
object := a.GetObject()
|
||||
|
||||
if !reflect.DeepEqual(expObject, object) {
|
||||
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
|
||||
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
|
||||
}
|
||||
case core.PatchActionImpl:
|
||||
e, _ := expected.(core.PatchActionImpl)
|
||||
expPatch := e.GetPatch()
|
||||
patch := a.GetPatch()
|
||||
|
||||
if !reflect.DeepEqual(expPatch, patch) {
|
||||
t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
|
||||
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
|
||||
}
|
||||
default:
|
||||
t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
|
||||
actual.GetVerb(), actual.GetResource().Resource)
|
||||
}
|
||||
}
|
||||
|
||||
// filterInformerActions filters list and watch actions for testing resources.
|
||||
// Since list and watch don't change resource state we can filter it to lower
|
||||
// nose level in our tests.
|
||||
func filterInformerActions(actions []core.Action) []core.Action {
|
||||
ret := []core.Action{}
|
||||
for _, action := range actions {
|
||||
if len(action.GetNamespace()) == 0 &&
|
||||
(action.Matches("list", "secrets") ||
|
||||
action.Matches("watch", "secrets") ||
|
||||
action.Matches("list", "namespaces") ||
|
||||
action.Matches("watch", "namespaces")) {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, action)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f *fixture) expectUpdateSecretAction(p *v1.Secret) {
|
||||
action := core.NewUpdateAction(schema.GroupVersionResource{
|
||||
Version: "v1",
|
||||
Resource: "secrets",
|
||||
}, p.Namespace, p)
|
||||
f.kubeactions = append(f.kubeactions, action)
|
||||
}
|
||||
|
||||
func getKey(p *v1.Secret, t *testing.T) string {
|
||||
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error getting key for pipeline %v: %v", p.Name, err)
|
||||
return ""
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func TestDoNothing(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
nsName := "test-123"
|
||||
secretName := "test"
|
||||
projectName := "test_project"
|
||||
|
||||
ns := newNamespace(nsName, projectName)
|
||||
secret := newSecret(nsName, secretName, nil, true, true)
|
||||
|
||||
f.secretLister = append(f.secretLister, secret)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.objects = append(f.objects, secret)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initCredential = []*v1.Secret{secret}
|
||||
f.expectCredential = []*v1.Secret{secret}
|
||||
|
||||
f.run(getKey(secret, t))
|
||||
}
|
||||
|
||||
func TestAddCredentialFinalizers(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
nsName := "test-123"
|
||||
secretName := "test"
|
||||
projectName := "test_project"
|
||||
|
||||
ns := newNamespace(nsName, projectName)
|
||||
secret := newSecret(nsName, secretName, nil, false, true)
|
||||
|
||||
expectSecret := newSecret(nsName, secretName, nil, true, true)
|
||||
|
||||
f.secretLister = append(f.secretLister, secret)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.kubeobjects = append(f.kubeobjects, secret)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initCredential = []*v1.Secret{secret}
|
||||
f.expectCredential = []*v1.Secret{expectSecret}
|
||||
f.expectUpdateSecretAction(expectSecret)
|
||||
f.run(getKey(secret, t))
|
||||
}
|
||||
|
||||
func TestCreateCredential(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
nsName := "test-123"
|
||||
secretName := "test"
|
||||
projectName := "test_project"
|
||||
|
||||
ns := newNamespace(nsName, projectName)
|
||||
secret := newSecret(nsName, secretName, nil, true, true)
|
||||
|
||||
f.secretLister = append(f.secretLister, secret)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.kubeobjects = append(f.kubeobjects, secret)
|
||||
f.initDevOpsProject = nsName
|
||||
f.expectCredential = []*v1.Secret{secret}
|
||||
f.run(getKey(secret, t))
|
||||
}
|
||||
|
||||
func TestDeleteCredential(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
nsName := "test-123"
|
||||
secretName := "test"
|
||||
projectName := "test_project"
|
||||
|
||||
ns := newNamespace(nsName, projectName)
|
||||
secret := newDeletingSecret(nsName, secretName)
|
||||
|
||||
expectSecret := secret.DeepCopy()
|
||||
expectSecret.Finalizers = []string{}
|
||||
f.secretLister = append(f.secretLister, secret)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.kubeobjects = append(f.kubeobjects, secret)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initCredential = []*v1.Secret{secret}
|
||||
f.expectCredential = []*v1.Secret{}
|
||||
f.expectUpdateSecretAction(expectSecret)
|
||||
f.run(getKey(secret, t))
|
||||
}
|
||||
|
||||
func TestDeleteNotExistCredential(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
nsName := "test-123"
|
||||
pipelineName := "test"
|
||||
projectName := "test_project"
|
||||
|
||||
ns := newNamespace(nsName, projectName)
|
||||
secret := newDeletingSecret(nsName, pipelineName)
|
||||
|
||||
expectSecret := secret.DeepCopy()
|
||||
expectSecret.Finalizers = []string{}
|
||||
f.secretLister = append(f.secretLister, secret)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.kubeobjects = append(f.kubeobjects, secret)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initCredential = []*v1.Secret{}
|
||||
f.expectCredential = []*v1.Secret{}
|
||||
f.expectUpdateSecretAction(expectSecret)
|
||||
f.run(getKey(secret, t))
|
||||
}
|
||||
|
||||
func TestUpdateCredential(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
nsName := "test-123"
|
||||
secretName := "test"
|
||||
projectName := "test_project"
|
||||
|
||||
ns := newNamespace(nsName, projectName)
|
||||
initSecret := newSecret(nsName, secretName, nil, true, true)
|
||||
expectSecret := newSecret(nsName, secretName, map[string][]byte{"a":[]byte("aa")}, true, true)
|
||||
f.secretLister = append(f.secretLister, expectSecret)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.kubeobjects = append(f.kubeobjects, expectSecret)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initCredential = []*v1.Secret{initSecret}
|
||||
f.expectCredential = []*v1.Secret{expectSecret}
|
||||
f.run(getKey(expectSecret, t))
|
||||
}
|
||||
|
||||
func TestNotUpdateCredential(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
nsName := "test-123"
|
||||
secretName := "test"
|
||||
projectName := "test_project"
|
||||
|
||||
ns := newNamespace(nsName, projectName)
|
||||
initSecret := newSecret(nsName, secretName, nil, true, false)
|
||||
expectSecret := newSecret(nsName, secretName, map[string][]byte{"a":[]byte("aa")}, true, false)
|
||||
f.secretLister = append(f.secretLister, expectSecret)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.kubeobjects = append(f.kubeobjects, expectSecret)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initCredential = []*v1.Secret{initSecret}
|
||||
f.expectCredential = []*v1.Secret{initSecret}
|
||||
f.run(getKey(expectSecret, t))
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ func (c *Controller) syncHandler(key string) error {
|
||||
}
|
||||
}
|
||||
copyPipeline.ObjectMeta.Finalizers = sliceutil.RemoveString(copyPipeline.ObjectMeta.Finalizers, func(item string) bool {
|
||||
return item == devopsv1alpha3.DevOpsProjectFinalizerName
|
||||
return item == devopsv1alpha3.PipelineFinalizerName
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -354,12 +354,15 @@ func TestDeletePipeline(t *testing.T) {
|
||||
ns := newNamespace(nsName, projectName)
|
||||
pipeline := newDeletingPipeline(nsName, pipelineName)
|
||||
|
||||
expectPipeline := pipeline.DeepCopy()
|
||||
expectPipeline.Finalizers = []string{}
|
||||
f.pipelineLister = append(f.pipelineLister, pipeline)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.objects = append(f.objects, pipeline)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initPipeline = []*devops.Pipeline{pipeline}
|
||||
f.expectPipeline = []*devops.Pipeline{}
|
||||
f.expectUpdatePipelineAction(expectPipeline)
|
||||
f.run(getKey(pipeline, t))
|
||||
}
|
||||
|
||||
@@ -372,12 +375,15 @@ func TestDeleteNotExistPipeline(t *testing.T) {
|
||||
ns := newNamespace(nsName, projectName)
|
||||
pipeline := newDeletingPipeline(nsName, pipelineName)
|
||||
|
||||
expectPipeline := pipeline.DeepCopy()
|
||||
expectPipeline.Finalizers = []string{}
|
||||
f.pipelineLister = append(f.pipelineLister, pipeline)
|
||||
f.namespaceLister = append(f.namespaceLister, ns)
|
||||
f.objects = append(f.objects, pipeline)
|
||||
f.initDevOpsProject = nsName
|
||||
f.initPipeline = []*devops.Pipeline{}
|
||||
f.expectPipeline = []*devops.Pipeline{}
|
||||
f.expectUpdatePipelineAction(expectPipeline)
|
||||
f.run(getKey(pipeline, t))
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,9 @@ import (
|
||||
)
|
||||
|
||||
type ProjectPipelineHandler struct {
|
||||
projectCredentialOperator devops.ProjectCredentialOperator
|
||||
projectMemberOperator devops.ProjectMemberOperator
|
||||
devopsOperator devops.DevopsOperator
|
||||
projectOperator devops.ProjectOperator
|
||||
projectMemberOperator devops.ProjectMemberOperator
|
||||
devopsOperator devops.DevopsOperator
|
||||
projectOperator devops.ProjectOperator
|
||||
}
|
||||
|
||||
type PipelineSonarHandler struct {
|
||||
@@ -24,10 +23,9 @@ type PipelineSonarHandler struct {
|
||||
|
||||
func NewProjectPipelineHandler(devopsClient devopsClient.Interface, dbClient *mysql.Database) ProjectPipelineHandler {
|
||||
return ProjectPipelineHandler{
|
||||
projectCredentialOperator: devops.NewProjectCredentialOperator(devopsClient, dbClient),
|
||||
projectMemberOperator: devops.NewProjectMemberOperator(devopsClient, dbClient),
|
||||
devopsOperator: devops.NewDevopsOperator(devopsClient),
|
||||
projectOperator: devops.NewProjectOperator(dbClient),
|
||||
projectMemberOperator: devops.NewProjectMemberOperator(devopsClient, dbClient),
|
||||
devopsOperator: devops.NewDevopsOperator(devopsClient),
|
||||
projectOperator: devops.NewProjectOperator(dbClient),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +37,5 @@ func NewPipelineSonarHandler(devopsClient devopsClient.Interface, dbClient *mysq
|
||||
}
|
||||
|
||||
func NewS2iBinaryHandler(client versioned.Interface, informers externalversions.SharedInformerFactory, s3Client s3.Interface) S2iBinaryHandler {
|
||||
|
||||
return S2iBinaryHandler{devops.NewS2iBinaryUploader(client, informers, s3Client)}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 The KubeSphere 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 v1alpha2
|
||||
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
)
|
||||
|
||||
func (h ProjectPipelineHandler) CreateDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
|
||||
|
||||
projectId := request.PathParameter("devops")
|
||||
username := request.HeaderParameter(constants.UserNameHeader)
|
||||
var credential *devops.Credential
|
||||
err := request.ReadEntity(&credential)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
credentialId, err := h.projectCredentialOperator.CreateProjectCredential(projectId, username, credential)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteAsJson(struct {
|
||||
Name string `json:"name"`
|
||||
}{Name: credentialId})
|
||||
return
|
||||
}
|
||||
|
||||
func (h ProjectPipelineHandler) UpdateDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
|
||||
|
||||
projectId := request.PathParameter("devops")
|
||||
credentialId := request.PathParameter("credential")
|
||||
var credential *devops.Credential
|
||||
err := request.ReadEntity(&credential)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
api.HandleBadRequest(resp, nil, err)
|
||||
return
|
||||
}
|
||||
credentialId, err = h.projectCredentialOperator.UpdateProjectCredential(projectId, credentialId, credential)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteAsJson(struct {
|
||||
Name string `json:"name"`
|
||||
}{Name: credentialId})
|
||||
return
|
||||
}
|
||||
|
||||
func (h ProjectPipelineHandler) DeleteDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
|
||||
|
||||
projectId := request.PathParameter("devops")
|
||||
credentialId := request.PathParameter("credential")
|
||||
|
||||
credentialId, err := h.projectCredentialOperator.DeleteProjectCredential(projectId, credentialId)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteAsJson(struct {
|
||||
Name string `json:"name"`
|
||||
}{Name: credentialId})
|
||||
return
|
||||
}
|
||||
|
||||
func (h ProjectPipelineHandler) GetDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
|
||||
|
||||
projectId := request.PathParameter("devops")
|
||||
credentialId := request.PathParameter("credential")
|
||||
getContent := request.QueryParameter("content")
|
||||
response, err := h.projectCredentialOperator.GetProjectCredential(projectId, credentialId, getContent)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp.WriteAsJson(response)
|
||||
return
|
||||
}
|
||||
|
||||
func (h ProjectPipelineHandler) GetDevOpsProjectCredentialsHandler(request *restful.Request, resp *restful.Response) {
|
||||
projectId := request.PathParameter("devops")
|
||||
|
||||
jenkinsCredentials, err := h.projectCredentialOperator.GetProjectCredentials(projectId)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
api.HandleInternalError(resp, nil, err)
|
||||
return
|
||||
}
|
||||
resp.WriteAsJson(jenkinsCredentials)
|
||||
return
|
||||
}
|
||||
@@ -156,49 +156,6 @@ func AddToContainer(c *restful.Container, devopsClient devops.Interface,
|
||||
Param(webservice.PathParameter("member", "member's username, e.g. admin")).
|
||||
Writes(devops.ProjectMembership{}))
|
||||
|
||||
webservice.Route(webservice.POST("/devops/{devops}/credentials").
|
||||
To(projectPipelineHander.CreateDevOpsProjectCredentialHandler).
|
||||
Doc("Create a credential in the specified DevOps project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectCredentialTag}).
|
||||
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
|
||||
Reads(devops.Credential{}))
|
||||
|
||||
webservice.Route(webservice.PUT("/devops/{devops}/credentials/{credential}").
|
||||
To(projectPipelineHander.UpdateDevOpsProjectCredentialHandler).
|
||||
Doc("Update the specified credential of the DevOps project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectCredentialTag}).
|
||||
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
|
||||
Param(webservice.PathParameter("credential", "credential's ID, e.g. dockerhub-id")).
|
||||
Reads(devops.Credential{}))
|
||||
|
||||
webservice.Route(webservice.DELETE("/devops/{devops}/credentials/{credential}").
|
||||
To(projectPipelineHander.DeleteDevOpsProjectCredentialHandler).
|
||||
Doc("Delete the specified credential of the DevOps project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectCredentialTag}).
|
||||
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
|
||||
Param(webservice.PathParameter("credential", "credential's ID, e.g. dockerhub-id")))
|
||||
|
||||
webservice.Route(webservice.GET("/devops/{devops}/credentials/{credential}").
|
||||
To(projectPipelineHander.GetDevOpsProjectCredentialHandler).
|
||||
Doc("Get the specified credential of the DevOps project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectCredentialTag}).
|
||||
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
|
||||
Param(webservice.PathParameter("credential", "credential's ID, e.g. dockerhub-id")).
|
||||
Param(webservice.QueryParameter("content", `
|
||||
Get extra credential content if this query parameter is set.
|
||||
Specifically, there are three types of info in a credential. One is the basic info that must be returned for each query such as name, id, etc.
|
||||
The second one is non-encrypted info such as the username of the username-password type of credential, which returns when the "content" parameter is set to non-empty.
|
||||
The last one is encrypted info, such as the password of the username-password type of credential, which never returns.
|
||||
`)).
|
||||
Returns(http.StatusOK, RespOK, devops.Credential{}))
|
||||
|
||||
webservice.Route(webservice.GET("/devops/{devops}/credentials").
|
||||
To(projectPipelineHander.GetDevOpsProjectCredentialsHandler).
|
||||
Doc("Get all credentials of the specified DevOps project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectCredentialTag}).
|
||||
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
|
||||
Returns(http.StatusOK, RespOK, []devops.Credential{}))
|
||||
|
||||
// match Jenkisn api "/blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}"
|
||||
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}").
|
||||
To(projectPipelineHander.GetPipeline).
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 The KubeSphere 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 devops
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ProjectCredentialTableName = "project_credential"
|
||||
ProjectCredentialIdColumn = "credential_id"
|
||||
ProjectCredentialDomainColumn = "domain"
|
||||
ProjectCredentialProjectIdColumn = "project_id"
|
||||
)
|
||||
|
||||
type ProjectCredential struct {
|
||||
ProjectId string `json:"project_id"`
|
||||
CredentialId string `json:"credential_id"`
|
||||
Domain string `json:"domain"`
|
||||
Creator string `json:"creator"`
|
||||
CreateTime time.Time `json:"create_time"`
|
||||
}
|
||||
|
||||
var ProjectCredentialColumns = GetColumnsFromStruct(&ProjectCredential{})
|
||||
|
||||
func NewProjectCredential(projectId, credentialId, domain, creator string) *ProjectCredential {
|
||||
if govalidator.IsNull(domain) {
|
||||
domain = "_"
|
||||
}
|
||||
return &ProjectCredential{
|
||||
ProjectId: projectId,
|
||||
CredentialId: credentialId,
|
||||
Domain: domain,
|
||||
Creator: creator,
|
||||
CreateTime: time.Now(),
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 The KubeSphere 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 devops
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/gocraft/dbr"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/db"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ProjectCredentialOperator interface {
|
||||
CreateProjectCredential(projectId, username string, credentialRequest *devops.Credential) (string, error)
|
||||
UpdateProjectCredential(projectId, credentialId string, credentialRequest *devops.Credential) (string, error)
|
||||
DeleteProjectCredential(projectId, credentialId string) (string, error)
|
||||
GetProjectCredential(projectId, credentialId, getContent string) (*devops.Credential, error)
|
||||
GetProjectCredentials(projectId string) ([]*devops.Credential, error)
|
||||
}
|
||||
|
||||
type projectCredentialOperator struct {
|
||||
devopsClient devops.Interface
|
||||
db *mysql.Database
|
||||
}
|
||||
|
||||
func NewProjectCredentialOperator(devopsClient devops.Interface, dbClient *mysql.Database) ProjectCredentialOperator {
|
||||
return &projectCredentialOperator{devopsClient: devopsClient, db: dbClient}
|
||||
}
|
||||
|
||||
func (o *projectCredentialOperator) CreateProjectCredential(projectId, username string, credentialRequest *devops.Credential) (string, error) {
|
||||
switch credentialRequest.Type {
|
||||
case devops.CredentialTypeUsernamePassword:
|
||||
if credentialRequest.UsernamePasswordCredential == nil {
|
||||
err := fmt.Errorf("usename_password should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case devops.CredentialTypeSsh:
|
||||
if credentialRequest.SshCredential == nil {
|
||||
err := fmt.Errorf("ssh should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case devops.CredentialTypeSecretText:
|
||||
if credentialRequest.SecretTextCredential == nil {
|
||||
err := fmt.Errorf("secret_text should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case devops.CredentialTypeKubeConfig:
|
||||
if credentialRequest.KubeconfigCredential == nil {
|
||||
err := fmt.Errorf("kubeconfig should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("error unsupport credential type")
|
||||
klog.Errorf("%+v", err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
|
||||
}
|
||||
credentialId, err := o.devopsClient.CreateCredentialInProject(projectId, credentialRequest)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return "", err
|
||||
}
|
||||
err = o.insertCredentialToDb(projectId, *credentialId, credentialRequest.Domain, username)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return "", err
|
||||
}
|
||||
return *credentialId, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *projectCredentialOperator) UpdateProjectCredential(projectId, credentialId string, credentialRequest *devops.Credential) (string, error) {
|
||||
|
||||
credential, err := o.devopsClient.GetCredentialInProject(projectId,
|
||||
credentialId, false)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return "", err
|
||||
}
|
||||
switch credential.Type {
|
||||
case devops.CredentialTypeUsernamePassword:
|
||||
if credentialRequest.UsernamePasswordCredential == nil {
|
||||
err := fmt.Errorf("usename_password should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case devops.CredentialTypeSsh:
|
||||
if credentialRequest.SshCredential == nil {
|
||||
err := fmt.Errorf("ssh should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case devops.CredentialTypeSecretText:
|
||||
if credentialRequest.SecretTextCredential == nil {
|
||||
err := fmt.Errorf("secret_text should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case devops.CredentialTypeKubeConfig:
|
||||
if credentialRequest.KubeconfigCredential == nil {
|
||||
err := fmt.Errorf("kubeconfig should not be nil")
|
||||
klog.Error(err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("error unsupport credential type")
|
||||
klog.Errorf("%+v", err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
credentialRequest.Id = credentialId
|
||||
_, err = o.devopsClient.UpdateCredentialInProject(projectId, credentialRequest)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
return credentialId, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *projectCredentialOperator) DeleteProjectCredential(projectId, credentialId string) (string, error) {
|
||||
|
||||
_, err := o.devopsClient.GetCredentialInProject(projectId,
|
||||
credentialId, false)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
id, err := o.devopsClient.DeleteCredentialInProject(projectId, credentialId)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
deleteConditions := append(make([]dbr.Builder, 0), db.Eq(ProjectCredentialProjectIdColumn, projectId))
|
||||
deleteConditions = append(deleteConditions, db.Eq(ProjectCredentialIdColumn, credentialId))
|
||||
deleteConditions = append(deleteConditions, db.Eq(ProjectCredentialDomainColumn, "_"))
|
||||
|
||||
_, err = o.db.DeleteFrom(ProjectCredentialTableName).
|
||||
Where(db.And(deleteConditions...)).Exec()
|
||||
if err != nil && err != db.ErrNotFound {
|
||||
klog.Errorf("%+v", err)
|
||||
return "", err
|
||||
}
|
||||
return *id, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *projectCredentialOperator) GetProjectCredential(projectId, credentialId, getContent string) (*devops.Credential, error) {
|
||||
|
||||
content := false
|
||||
if getContent != "" {
|
||||
content = true
|
||||
}
|
||||
credential, err := o.devopsClient.GetCredentialInProject(projectId,
|
||||
credentialId,
|
||||
content)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return nil, err
|
||||
}
|
||||
projectCredential := &ProjectCredential{}
|
||||
err = o.db.Select(ProjectCredentialColumns...).
|
||||
From(ProjectCredentialTableName).Where(
|
||||
db.And(db.Eq(ProjectCredentialProjectIdColumn, projectId),
|
||||
db.Eq(ProjectCredentialIdColumn, credentialId),
|
||||
db.Eq(ProjectCredentialDomainColumn, credential.Domain))).LoadOne(projectCredential)
|
||||
|
||||
if err != nil && err != db.ErrNotFound {
|
||||
klog.Errorf("%+v", err)
|
||||
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
response := formatCredentialResponse(credential, projectCredential)
|
||||
return response, nil
|
||||
|
||||
}
|
||||
|
||||
func (o *projectCredentialOperator) GetProjectCredentials(projectId string) ([]*devops.Credential, error) {
|
||||
|
||||
credentialResponses, err := o.devopsClient.GetCredentialsInProject(projectId)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return nil, err
|
||||
}
|
||||
selectCondition := db.Eq(ProjectCredentialProjectIdColumn, projectId)
|
||||
projectCredentials := make([]*ProjectCredential, 0)
|
||||
_, err = o.db.Select(ProjectCredentialColumns...).
|
||||
From(ProjectCredentialTableName).Where(selectCondition).Load(&projectCredentials)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
response := formatCredentialsResponse(credentialResponses, projectCredentials)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (o *projectCredentialOperator) insertCredentialToDb(projectId, credentialId, domain, username string) error {
|
||||
|
||||
projectCredential := NewProjectCredential(projectId, credentialId, domain, username)
|
||||
_, err := o.db.InsertInto(ProjectCredentialTableName).Columns(ProjectCredentialColumns...).
|
||||
Record(projectCredential).Exec()
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return restful.NewError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatCredentialResponse(
|
||||
credentialResponse *devops.Credential,
|
||||
dbCredentialResponse *ProjectCredential) *devops.Credential {
|
||||
response := &devops.Credential{}
|
||||
response.Id = credentialResponse.Id
|
||||
response.Description = credentialResponse.Description
|
||||
response.DisplayName = credentialResponse.DisplayName
|
||||
if credentialResponse.Fingerprint != nil && credentialResponse.Fingerprint.Hash != "" {
|
||||
response.Fingerprint = &struct {
|
||||
FileName string `json:"file_name,omitempty" description:"Credential's display name and description"`
|
||||
Hash string `json:"hash,omitempty" description:"Credential's hash"`
|
||||
Usage []*struct {
|
||||
Name string `json:"name,omitempty" description:"pipeline full name"`
|
||||
Ranges struct {
|
||||
Ranges []*struct {
|
||||
Start int `json:"start,omitempty" description:"Start build number"`
|
||||
End int `json:"end,omitempty" description:"End build number"`
|
||||
} `json:"ranges,omitempty"`
|
||||
} `json:"ranges,omitempty" description:"The build number of all pipelines that use this credential"`
|
||||
} `json:"usage,omitempty" description:"all usage of Credential"`
|
||||
}{}
|
||||
response.Fingerprint.FileName = credentialResponse.Fingerprint.FileName
|
||||
response.Fingerprint.Hash = credentialResponse.Fingerprint.Hash
|
||||
for _, usage := range credentialResponse.Fingerprint.Usage {
|
||||
response.Fingerprint.Usage = append(response.Fingerprint.Usage, usage)
|
||||
}
|
||||
}
|
||||
response.Domain = credentialResponse.Domain
|
||||
|
||||
if dbCredentialResponse != nil {
|
||||
response.CreateTime = &dbCredentialResponse.CreateTime
|
||||
response.Creator = dbCredentialResponse.Creator
|
||||
}
|
||||
|
||||
credentialType, ok := devops.CredentialTypeMap[credentialResponse.Type]
|
||||
if ok {
|
||||
response.Type = credentialType
|
||||
return response
|
||||
}
|
||||
response.Type = credentialResponse.Type
|
||||
return response
|
||||
}
|
||||
|
||||
func formatCredentialsResponse(credentialsResponse []*devops.Credential,
|
||||
projectCredentials []*ProjectCredential) []*devops.Credential {
|
||||
responseSlice := make([]*devops.Credential, 0)
|
||||
for _, credential := range credentialsResponse {
|
||||
var dbCredential *ProjectCredential = nil
|
||||
for _, projectCredential := range projectCredentials {
|
||||
if projectCredential.CredentialId == credential.Id &&
|
||||
projectCredential.Domain == credential.Domain {
|
||||
dbCredential = projectCredential
|
||||
}
|
||||
}
|
||||
responseSlice = append(responseSlice, formatCredentialResponse(credential, dbCredential))
|
||||
}
|
||||
return responseSlice
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package devops
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -50,28 +51,14 @@ type KubeconfigCredential struct {
|
||||
Content string `json:"content,omitempty" description:"content of kubeconfig"`
|
||||
}
|
||||
|
||||
const (
|
||||
CredentialTypeUsernamePassword = "username_password"
|
||||
CredentialTypeSsh = "ssh"
|
||||
CredentialTypeSecretText = "secret_text"
|
||||
CredentialTypeKubeConfig = "kubeconfig"
|
||||
)
|
||||
|
||||
var CredentialTypeMap = map[string]string{
|
||||
"SSH Username with private key": CredentialTypeSsh,
|
||||
"Username with password": CredentialTypeUsernamePassword,
|
||||
"Secret text": CredentialTypeSecretText,
|
||||
"Kubernetes configuration (kubeconfig)": CredentialTypeKubeConfig,
|
||||
}
|
||||
|
||||
type CredentialOperator interface {
|
||||
CreateCredentialInProject(projectId string, credential *Credential) (*string, error)
|
||||
CreateCredentialInProject(projectId string, credential *v1.Secret) (string, error)
|
||||
|
||||
UpdateCredentialInProject(projectId string, credential *Credential) (*string, error)
|
||||
UpdateCredentialInProject(projectId string, credential *v1.Secret) (string, error)
|
||||
|
||||
GetCredentialInProject(projectId, id string, content bool) (*Credential, error)
|
||||
GetCredentialInProject(projectId, id string) (*Credential, error)
|
||||
|
||||
GetCredentialsInProject(projectId string) ([]*Credential, error)
|
||||
|
||||
DeleteCredentialInProject(projectId, id string) (*string, error)
|
||||
DeleteCredentialInProject(projectId, id string) (string, error)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/emicklei/go-restful"
|
||||
"io/ioutil"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"net/http"
|
||||
@@ -17,6 +18,8 @@ type Devops struct {
|
||||
Projects map[string]interface{}
|
||||
|
||||
Pipelines map[string]map[string]*devopsv1alpha3.Pipeline
|
||||
|
||||
Credentials map[string]map[string]*v1.Secret
|
||||
}
|
||||
|
||||
func New(projects ...string) *Devops {
|
||||
@@ -45,12 +48,28 @@ func NewWithPipelines(project string, pipelines ...*devopsv1alpha3.Pipeline) *De
|
||||
return d
|
||||
}
|
||||
|
||||
func NewWithCredentials(project string, credentials ...*v1.Secret) *Devops {
|
||||
d := &Devops{
|
||||
Data: nil,
|
||||
Projects: map[string]interface{}{},
|
||||
Credentials: map[string]map[string]*v1.Secret{},
|
||||
}
|
||||
|
||||
d.Projects[project] = true
|
||||
d.Credentials[project] = map[string]*v1.Secret{}
|
||||
for _, f := range credentials {
|
||||
d.Credentials[project][f.Name] = f
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Devops) CreateDevOpsProject(projectId string) (string, error) {
|
||||
if _, ok := d.Projects[projectId]; ok {
|
||||
return projectId, nil
|
||||
}
|
||||
d.Projects[projectId] = true
|
||||
d.Pipelines[projectId] = map[string]*devopsv1alpha3.Pipeline{}
|
||||
d.Credentials[projectId] = map[string]*v1.Secret{}
|
||||
return projectId, nil
|
||||
}
|
||||
|
||||
@@ -58,6 +77,7 @@ func (d *Devops) DeleteDevOpsProject(projectId string) error {
|
||||
if _, ok := d.Projects[projectId]; ok {
|
||||
delete(d.Projects, projectId)
|
||||
delete(d.Pipelines, projectId)
|
||||
delete(d.Credentials, projectId)
|
||||
return nil
|
||||
} else {
|
||||
return &devops.ErrorResponse{
|
||||
@@ -264,19 +284,128 @@ func (d *Devops) ToJson(httpParameters *devops.HttpParameters) (*devops.ResJson,
|
||||
}
|
||||
|
||||
// CredentialOperator
|
||||
func (d *Devops) CreateCredentialInProject(projectId string, credential *devops.Credential) (*string, error) {
|
||||
return nil, nil
|
||||
func (d *Devops) CreateCredentialInProject(projectId string, credential *v1.Secret) (string, error) {
|
||||
if _, ok := d.Credentials[projectId][credential.Name]; ok {
|
||||
err := fmt.Errorf("credential name [%s] has been used", credential.Name)
|
||||
return "", restful.NewError(http.StatusConflict, err.Error())
|
||||
}
|
||||
d.Credentials[projectId][credential.Name] = credential
|
||||
return credential.Name, nil
|
||||
}
|
||||
func (d *Devops) UpdateCredentialInProject(projectId string, credential *devops.Credential) (*string, error) {
|
||||
return nil, nil
|
||||
func (d *Devops) UpdateCredentialInProject(projectId string, credential *v1.Secret) (string, error) {
|
||||
if _, ok := d.Credentials[projectId][credential.Name]; !ok {
|
||||
err := &devops.ErrorResponse{
|
||||
Body: []byte{},
|
||||
Response: &http.Response{
|
||||
Status: "404 Not Found",
|
||||
StatusCode: 404,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
ContentLength: 50,
|
||||
Header: http.Header{
|
||||
"Foo": []string{"Bar"},
|
||||
},
|
||||
Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used
|
||||
Request: &http.Request{
|
||||
Method: "",
|
||||
URL: &url.URL{
|
||||
Scheme: "",
|
||||
Opaque: "",
|
||||
User: nil,
|
||||
Host: "",
|
||||
Path: "",
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: "",
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
d.Credentials[projectId][credential.Name] = credential
|
||||
return credential.Name, nil
|
||||
}
|
||||
func (d *Devops) GetCredentialInProject(projectId, id string, content bool) (*devops.Credential, error) {
|
||||
return nil, nil
|
||||
|
||||
func (d *Devops) GetCredentialInProject(projectId, id string) (*devops.Credential, error) {
|
||||
if _, ok := d.Credentials[projectId][id]; !ok {
|
||||
err := &devops.ErrorResponse{
|
||||
Body: []byte{},
|
||||
Response: &http.Response{
|
||||
Status: "404 Not Found",
|
||||
StatusCode: 404,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
ContentLength: 50,
|
||||
Header: http.Header{
|
||||
"Foo": []string{"Bar"},
|
||||
},
|
||||
Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used
|
||||
Request: &http.Request{
|
||||
Method: "",
|
||||
URL: &url.URL{
|
||||
Scheme: "",
|
||||
Opaque: "",
|
||||
User: nil,
|
||||
Host: "",
|
||||
Path: "",
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: "",
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &devops.Credential{Id: id}, nil
|
||||
}
|
||||
func (d *Devops) GetCredentialsInProject(projectId string) ([]*devops.Credential, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (d *Devops) DeleteCredentialInProject(projectId, id string) (*string, error) { return nil, nil }
|
||||
func (d *Devops) DeleteCredentialInProject(projectId, id string) (string, error) {
|
||||
if _, ok := d.Credentials[projectId][id]; !ok {
|
||||
err := &devops.ErrorResponse{
|
||||
Body: []byte{},
|
||||
Response: &http.Response{
|
||||
Status: "404 Not Found",
|
||||
StatusCode: 404,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
ContentLength: 50,
|
||||
Header: http.Header{
|
||||
"Foo": []string{"Bar"},
|
||||
},
|
||||
Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used
|
||||
Request: &http.Request{
|
||||
Method: "",
|
||||
URL: &url.URL{
|
||||
Scheme: "",
|
||||
Opaque: "",
|
||||
User: nil,
|
||||
Host: "",
|
||||
Path: "",
|
||||
RawPath: "",
|
||||
ForceQuery: false,
|
||||
RawQuery: "",
|
||||
Fragment: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: "",
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
delete(d.Credentials[projectId], id)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// BuildGetter
|
||||
func (d *Devops) GetProjectPipelineBuildByType(projectId, pipelineId string, status string) (*devops.Build, error) {
|
||||
@@ -306,6 +435,7 @@ func (d *Devops) CreateProjectPipeline(projectId string, pipeline *devopsv1alpha
|
||||
d.Pipelines[projectId][pipeline.Name] = pipeline
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (d *Devops) DeleteProjectPipeline(projectId string, pipelineId string) (string, error) {
|
||||
if _, ok := d.Pipelines[projectId][pipelineId]; !ok {
|
||||
err := &devops.ErrorResponse{
|
||||
@@ -343,6 +473,7 @@ func (d *Devops) DeleteProjectPipeline(projectId string, pipelineId string) (str
|
||||
delete(d.Pipelines[projectId], pipelineId)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (d *Devops) UpdateProjectPipeline(projectId string, pipeline *devopsv1alpha3.Pipeline) (string, error) {
|
||||
if _, ok := d.Pipelines[projectId][pipeline.Name]; !ok {
|
||||
err := &devops.ErrorResponse{
|
||||
@@ -380,6 +511,7 @@ func (d *Devops) UpdateProjectPipeline(projectId string, pipeline *devopsv1alpha
|
||||
d.Pipelines[projectId][pipeline.Name] = pipeline
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (d *Devops) GetProjectPipelineConfig(projectId, pipelineId string) (*devopsv1alpha3.Pipeline, error) {
|
||||
if _, ok := d.Pipelines[projectId][pipelineId]; !ok {
|
||||
err := &devops.ErrorResponse{
|
||||
|
||||
@@ -16,13 +16,13 @@ package jenkins
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/emicklei/go-restful"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/klog"
|
||||
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const SSHCrenditalStaplerClass = "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey"
|
||||
@@ -99,10 +99,15 @@ type CredentialResponse struct {
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
func NewSshCredential(id, username, passphrase, privateKey, description string) *SshCredential {
|
||||
func NewSshCredential(secret *v1.Secret) *SshCredential {
|
||||
id := secret.Name
|
||||
username := string(secret.Data[devopsv1alpha3.SSHAuthUsernameKey])
|
||||
passphrase := string(secret.Data[devopsv1alpha3.SSHAuthPassphraseKey])
|
||||
privatekey := string(secret.Data[devopsv1alpha3.SSHAuthPrivateKey])
|
||||
|
||||
keySource := PrivateKeySource{
|
||||
StaplerClass: DirectSSHCrenditalStaplerClass,
|
||||
PrivateKey: privateKey,
|
||||
PrivateKey: privatekey,
|
||||
}
|
||||
|
||||
return &SshCredential{
|
||||
@@ -111,48 +116,52 @@ func NewSshCredential(id, username, passphrase, privateKey, description string)
|
||||
Username: username,
|
||||
Passphrase: passphrase,
|
||||
KeySource: keySource,
|
||||
Description: description,
|
||||
StaplerClass: SSHCrenditalStaplerClass,
|
||||
}
|
||||
}
|
||||
|
||||
func NewUsernamePasswordCredential(id, username, password, description string) *UsernamePasswordCredential {
|
||||
func NewUsernamePasswordCredential(secret *v1.Secret) *UsernamePasswordCredential {
|
||||
id := secret.Name
|
||||
username := string(secret.Data[devopsv1alpha3.BasicAuthUsernameKey])
|
||||
password := string(secret.Data[devopsv1alpha3.BasicAuthPasswordKey])
|
||||
return &UsernamePasswordCredential{
|
||||
Scope: GLOBALScope,
|
||||
Id: id,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Description: description,
|
||||
StaplerClass: UsernamePassswordCredentialStaplerClass,
|
||||
}
|
||||
}
|
||||
|
||||
func NewSecretTextCredential(id, secret, description string) *SecretTextCredential {
|
||||
func NewSecretTextCredential(secret *v1.Secret) *SecretTextCredential {
|
||||
id := secret.Name
|
||||
secretContent := string(secret.Data[devopsv1alpha3.SecretTextSecretKey])
|
||||
return &SecretTextCredential{
|
||||
Scope: GLOBALScope,
|
||||
Id: id,
|
||||
Secret: secret,
|
||||
Description: description,
|
||||
Secret: secretContent,
|
||||
StaplerClass: SecretTextCredentialStaplerClass,
|
||||
}
|
||||
}
|
||||
|
||||
func NewKubeconfigCredential(id, content, description string) *KubeconfigCredential {
|
||||
func NewKubeconfigCredential(secret *v1.Secret) *KubeconfigCredential {
|
||||
id := secret.Name
|
||||
secretContent := string(secret.Data[devopsv1alpha3.KubeConfigSecretKey])
|
||||
|
||||
credentialSource := KubeconfigSource{
|
||||
StaplerClass: DirectKubeconfigCredentialStaperClass,
|
||||
Content: content,
|
||||
Content: secretContent,
|
||||
}
|
||||
|
||||
return &KubeconfigCredential{
|
||||
Scope: GLOBALScope,
|
||||
Id: id,
|
||||
Description: description,
|
||||
KubeconfigSource: credentialSource,
|
||||
StaplerClass: KubeconfigCredentialStaplerClass,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Jenkins) GetCredentialInProject(projectId, id string, content bool) (*devops.Credential, error) {
|
||||
func (j *Jenkins) GetCredentialInProject(projectId, id string) (*devops.Credential, error) {
|
||||
responseStruct := &devops.Credential{}
|
||||
|
||||
domain := "_"
|
||||
@@ -169,54 +178,6 @@ func (j *Jenkins) GetCredentialInProject(projectId, id string, content bool) (*d
|
||||
return nil, errors.New(strconv.Itoa(response.StatusCode))
|
||||
}
|
||||
responseStruct.Domain = domain
|
||||
if content {
|
||||
|
||||
}
|
||||
contentString := ""
|
||||
response, err = j.Requester.GetHtml(
|
||||
fmt.Sprintf("/job/%s/credentials/store/folder/domain/%s/credential/%s/update", projectId, domain, id),
|
||||
&contentString, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(strconv.Itoa(response.StatusCode))
|
||||
}
|
||||
stringReader := strings.NewReader(contentString)
|
||||
doc, err := goquery.NewDocumentFromReader(stringReader)
|
||||
if err != nil {
|
||||
klog.Errorf("%+v", err)
|
||||
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
switch responseStruct.Type {
|
||||
case devops.CredentialTypeKubeConfig:
|
||||
content := &devops.KubeconfigCredential{}
|
||||
doc.Find("textarea[name*=content]").Each(func(i int, selection *goquery.Selection) {
|
||||
value := selection.Text()
|
||||
content.Content = value
|
||||
})
|
||||
responseStruct.KubeconfigCredential = content
|
||||
case devops.CredentialTypeUsernamePassword:
|
||||
content := &devops.UsernamePasswordCredential{}
|
||||
doc.Find("input[name*=username]").Each(func(i int, selection *goquery.Selection) {
|
||||
value, _ := selection.Attr("value")
|
||||
content.Username = value
|
||||
})
|
||||
|
||||
responseStruct.UsernamePasswordCredential = content
|
||||
case devops.CredentialTypeSsh:
|
||||
content := &devops.SshCredential{}
|
||||
doc.Find("input[name*=username]").Each(func(i int, selection *goquery.Selection) {
|
||||
value, _ := selection.Attr("value")
|
||||
content.Username = value
|
||||
})
|
||||
|
||||
doc.Find("textarea[name*=privateKey]").Each(func(i int, selection *goquery.Selection) {
|
||||
value := selection.Text()
|
||||
content.PrivateKey = value
|
||||
})
|
||||
responseStruct.SshCredential = content
|
||||
}
|
||||
return responseStruct, nil
|
||||
}
|
||||
|
||||
@@ -243,30 +204,23 @@ func (j *Jenkins) GetCredentialsInProject(projectId string) ([]*devops.Credentia
|
||||
|
||||
}
|
||||
|
||||
func (j *Jenkins) CreateCredentialInProject(projectId string, credential *devops.Credential) (*string, error) {
|
||||
func (j *Jenkins) CreateCredentialInProject(projectId string, credential *v1.Secret) (string, error) {
|
||||
|
||||
var request interface{}
|
||||
responseString := ""
|
||||
switch credential.Type {
|
||||
case devops.CredentialTypeUsernamePassword:
|
||||
request = NewUsernamePasswordCredential(credential.Id,
|
||||
credential.UsernamePasswordCredential.Username, credential.UsernamePasswordCredential.Password,
|
||||
credential.Description)
|
||||
|
||||
case devops.CredentialTypeSsh:
|
||||
request = NewSshCredential(credential.Id,
|
||||
credential.SshCredential.Username, credential.SshCredential.Passphrase,
|
||||
credential.SshCredential.PrivateKey, credential.Description)
|
||||
case devops.CredentialTypeSecretText:
|
||||
request = NewSecretTextCredential(credential.Id,
|
||||
credential.SecretTextCredential.Secret, credential.Description)
|
||||
case devops.CredentialTypeKubeConfig:
|
||||
request = NewKubeconfigCredential(credential.Id,
|
||||
credential.KubeconfigCredential.Content, credential.Description)
|
||||
case devopsv1alpha3.SecretTypeBasicAuth:
|
||||
request = NewUsernamePasswordCredential(credential)
|
||||
case devopsv1alpha3.SecretTypeSSHAuth:
|
||||
request = NewSshCredential(credential)
|
||||
case devopsv1alpha3.SecretTypeSecretText:
|
||||
request = NewSecretTextCredential(credential)
|
||||
case devopsv1alpha3.SecretTypeKubeConfig:
|
||||
request = NewKubeconfigCredential(credential)
|
||||
default:
|
||||
err := fmt.Errorf("error unsupport credential type")
|
||||
klog.Errorf("%+v", err)
|
||||
return nil, restful.NewError(http.StatusBadRequest, err.Error())
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
response, err := j.Requester.Post(
|
||||
@@ -277,65 +231,58 @@ func (j *Jenkins) CreateCredentialInProject(projectId string, credential *devops
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(strconv.Itoa(response.StatusCode))
|
||||
return "", errors.New(strconv.Itoa(response.StatusCode))
|
||||
}
|
||||
return &credential.Id, nil
|
||||
return credential.Name, nil
|
||||
}
|
||||
|
||||
func (j *Jenkins) UpdateCredentialInProject(projectId string, credential *devops.Credential) (*string, error) {
|
||||
func (j *Jenkins) UpdateCredentialInProject(projectId string, credential *v1.Secret) (string, error) {
|
||||
|
||||
requestContent := ""
|
||||
switch credential.Type {
|
||||
case devops.CredentialTypeUsernamePassword:
|
||||
requestStruct := NewUsernamePasswordCredential(credential.Id,
|
||||
credential.UsernamePasswordCredential.Username, credential.UsernamePasswordCredential.Password,
|
||||
credential.Description)
|
||||
case devopsv1alpha3.SecretTypeBasicAuth:
|
||||
requestStruct := NewUsernamePasswordCredential(credential)
|
||||
requestContent = makeJson(requestStruct)
|
||||
|
||||
case devops.CredentialTypeSsh:
|
||||
requestStruct := NewSshCredential(credential.Id,
|
||||
credential.SshCredential.Username, credential.SshCredential.Passphrase,
|
||||
credential.SshCredential.PrivateKey, credential.Description)
|
||||
case devopsv1alpha3.SecretTypeSSHAuth:
|
||||
requestStruct := NewSshCredential(credential)
|
||||
requestContent = makeJson(requestStruct)
|
||||
case devops.CredentialTypeSecretText:
|
||||
requestStruct := NewSecretTextCredential(credential.Id,
|
||||
credential.SecretTextCredential.Secret, credential.Description)
|
||||
case devopsv1alpha3.SecretTypeSecretText:
|
||||
requestStruct := NewSecretTextCredential(credential)
|
||||
requestContent = makeJson(requestStruct)
|
||||
case devops.CredentialTypeKubeConfig:
|
||||
requestStruct := NewKubeconfigCredential(credential.Id,
|
||||
credential.KubeconfigCredential.Content, credential.Description)
|
||||
case devopsv1alpha3.SecretTypeKubeConfig:
|
||||
requestStruct := NewKubeconfigCredential(credential)
|
||||
requestContent = makeJson(requestStruct)
|
||||
default:
|
||||
err := fmt.Errorf("error unsupport credential type")
|
||||
klog.Errorf("%+v", err)
|
||||
return nil, restful.NewError(http.StatusBadRequest, err.Error())
|
||||
return "", restful.NewError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
response, err := j.Requester.Post(
|
||||
fmt.Sprintf("/job/%s/credentials/store/folder/domain/_/credential/%s/updateSubmit", projectId, credential.Id),
|
||||
fmt.Sprintf("/job/%s/credentials/store/folder/domain/_/credential/%s/updateSubmit", projectId, credential.Name),
|
||||
nil, nil, map[string]string{
|
||||
"json": requestContent,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(strconv.Itoa(response.StatusCode))
|
||||
return "", errors.New(strconv.Itoa(response.StatusCode))
|
||||
}
|
||||
return &credential.Id, nil
|
||||
return credential.Name, nil
|
||||
}
|
||||
|
||||
func (j *Jenkins) DeleteCredentialInProject(projectId, id string) (*string, error) {
|
||||
func (j *Jenkins) DeleteCredentialInProject(projectId, id string) (string, error) {
|
||||
response, err := j.Requester.Post(
|
||||
fmt.Sprintf("/job/%s/credentials/store/folder/domain/_/credential/%s/doDelete", projectId, id),
|
||||
nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(strconv.Itoa(response.StatusCode))
|
||||
return "", errors.New(strconv.Itoa(response.StatusCode))
|
||||
}
|
||||
return &id, nil
|
||||
return id, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user