未验证 提交 af3b87dd 编写于 作者: R runzexia

add devops credential controller

Signed-off-by: Nrunzexia <runzexia@yunify.com>
上级 e8b9d9cd
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"
)
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, "")
}
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)}
}
/*
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).
......
/*
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(),
}
}
/*
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
}
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
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册