Merge pull request #4129 from LinuxSuRen/devops-proxy

Add proxy devops APIs request to ks-devops
This commit is contained in:
KubeSphere CI Bot
2021-08-12 10:05:40 +08:00
committed by GitHub
27 changed files with 4340 additions and 12342 deletions

View File

@@ -1,6 +1,6 @@
API rule violation: list_type_missing,./pkg/apis/devops/v1alpha3,DevOpsProjectList,Items
API rule violation: list_type_missing,./pkg/apis/devops/v1alpha3,NoScmPipeline,Parameters
API rule violation: list_type_missing,./pkg/apis/devops/v1alpha3,PipelineList,Items
API rule violation: list_type_missing,./staging/src/kubesphere.io/api/devops/v1alpha3,DevOpsProjectList,Items
API rule violation: list_type_missing,./staging/src/kubesphere.io/api/devops/v1alpha3,NoScmPipeline,Parameters
API rule violation: list_type_missing,./staging/src/kubesphere.io/api/devops/v1alpha3,PipelineList,Items
API rule violation: list_type_missing,k8s.io/apimachinery/pkg/apis/meta/v1,APIGroup,ServerAddressByClientCIDRs
API rule violation: list_type_missing,k8s.io/apimachinery/pkg/apis/meta/v1,APIGroup,Versions
API rule violation: list_type_missing,k8s.io/apimachinery/pkg/apis/meta/v1,APIGroupList,Groups
@@ -29,63 +29,63 @@ API rule violation: list_type_missing,k8s.io/apimachinery/pkg/apis/meta/v1,Table
API rule violation: list_type_missing,k8s.io/apimachinery/pkg/apis/meta/v1,UpdateOptions,DryRun
API rule violation: list_type_missing,k8s.io/apimachinery/pkg/runtime,RawExtension,Raw
API rule violation: list_type_missing,k8s.io/apimachinery/pkg/runtime,Unknown,Raw
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,ApiUri
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,CloneOption
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,CredentialId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,DiscoverBranches
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,DiscoverPRFromForks
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,DiscoverPRFromOrigin
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,DiscoverTags
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,RegexFilter
API rule violation: names_match,./pkg/apis/devops/v1alpha3,BitbucketServerSource,ScmId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,DiscarderProperty,DaysToKeep
API rule violation: names_match,./pkg/apis/devops/v1alpha3,DiscarderProperty,NumToKeep
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitSource,CloneOption
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitSource,CredentialId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitSource,DiscoverBranches
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitSource,DiscoverTags
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitSource,RegexFilter
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitSource,ScmId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,ApiUri
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,CloneOption
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,CredentialId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,DiscoverBranches
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,DiscoverPRFromForks
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,DiscoverPRFromOrigin
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,DiscoverTags
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,RegexFilter
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GithubSource,ScmId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,ApiUri
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,CloneOption
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,CredentialId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,DiscoverBranches
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,DiscoverPRFromForks
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,DiscoverPRFromOrigin
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,DiscoverTags
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,RegexFilter
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,ScmId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,GitlabSource,ServerName
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchJobTrigger,CreateActionJobsToTrigger
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchJobTrigger,DeleteActionJobsToTrigger
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,BitbucketServerSource
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,GitHubSource
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,GitSource
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,GitlabSource
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,MultiBranchJobTrigger
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,ScriptPath
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,SingleSvnSource
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,SourceType
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,SvnSource
API rule violation: names_match,./pkg/apis/devops/v1alpha3,MultiBranchPipeline,TimerTrigger
API rule violation: names_match,./pkg/apis/devops/v1alpha3,NoScmPipeline,DisableConcurrent
API rule violation: names_match,./pkg/apis/devops/v1alpha3,NoScmPipeline,RemoteTrigger
API rule violation: names_match,./pkg/apis/devops/v1alpha3,NoScmPipeline,TimerTrigger
API rule violation: names_match,./pkg/apis/devops/v1alpha3,Parameter,DefaultValue
API rule violation: names_match,./pkg/apis/devops/v1alpha3,PipelineSpec,MultiBranchPipeline
API rule violation: names_match,./pkg/apis/devops/v1alpha3,SingleSvnSource,CredentialId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,SingleSvnSource,ScmId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,SvnSource,CredentialId
API rule violation: names_match,./pkg/apis/devops/v1alpha3,SvnSource,ScmId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,ApiUri
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,CloneOption
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,CredentialId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,DiscoverBranches
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,DiscoverPRFromForks
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,DiscoverPRFromOrigin
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,DiscoverTags
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,RegexFilter
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,BitbucketServerSource,ScmId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,DiscarderProperty,DaysToKeep
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,DiscarderProperty,NumToKeep
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitSource,CloneOption
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitSource,CredentialId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitSource,DiscoverBranches
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitSource,DiscoverTags
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitSource,RegexFilter
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitSource,ScmId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,ApiUri
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,CloneOption
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,CredentialId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,DiscoverBranches
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,DiscoverPRFromForks
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,DiscoverPRFromOrigin
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,DiscoverTags
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,RegexFilter
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GithubSource,ScmId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,ApiUri
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,CloneOption
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,CredentialId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,DiscoverBranches
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,DiscoverPRFromForks
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,DiscoverPRFromOrigin
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,DiscoverTags
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,RegexFilter
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,ScmId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,GitlabSource,ServerName
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchJobTrigger,CreateActionJobsToTrigger
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchJobTrigger,DeleteActionJobsToTrigger
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,BitbucketServerSource
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,GitHubSource
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,GitSource
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,GitlabSource
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,MultiBranchJobTrigger
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,ScriptPath
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,SingleSvnSource
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,SourceType
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,SvnSource
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,MultiBranchPipeline,TimerTrigger
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,NoScmPipeline,DisableConcurrent
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,NoScmPipeline,RemoteTrigger
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,NoScmPipeline,TimerTrigger
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,Parameter,DefaultValue
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,PipelineSpec,MultiBranchPipeline
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,SingleSvnSource,CredentialId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,SingleSvnSource,ScmId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,SvnSource,CredentialId
API rule violation: names_match,./staging/src/kubesphere.io/api/devops/v1alpha3,SvnSource,ScmId
API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,APIResourceList,APIResources
API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,Duration,Duration
API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,InternalEvent,Object

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -30,8 +30,6 @@ import (
"kubesphere.io/kubesphere/pkg/controller/cluster"
"kubesphere.io/kubesphere/pkg/controller/clusterrolebinding"
"kubesphere.io/kubesphere/pkg/controller/destinationrule"
"kubesphere.io/kubesphere/pkg/controller/devopscredential"
"kubesphere.io/kubesphere/pkg/controller/devopsproject"
"kubesphere.io/kubesphere/pkg/controller/globalrole"
"kubesphere.io/kubesphere/pkg/controller/globalrolebinding"
"kubesphere.io/kubesphere/pkg/controller/group"
@@ -42,9 +40,6 @@ import (
"kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy"
"kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy/provider"
"kubesphere.io/kubesphere/pkg/controller/notification"
"kubesphere.io/kubesphere/pkg/controller/pipeline"
"kubesphere.io/kubesphere/pkg/controller/s2ibinary"
"kubesphere.io/kubesphere/pkg/controller/s2irun"
"kubesphere.io/kubesphere/pkg/controller/storage/capability"
"kubesphere.io/kubesphere/pkg/controller/storage/expansion"
"kubesphere.io/kubesphere/pkg/controller/user"
@@ -101,38 +96,6 @@ func addControllers(
jobController := job.NewJobController(kubernetesInformer.Batch().V1().Jobs(), client.Kubernetes())
var s2iBinaryController, s2iRunController, devopsProjectController, devopsPipelineController, devopsCredentialController manager.Runnable
if devopsClient != nil {
s2iBinaryController = s2ibinary.NewController(client.Kubernetes(),
client.KubeSphere(),
kubesphereInformer.Devops().V1alpha1().S2iBinaries(),
s3Client,
)
s2iRunController = s2irun.NewS2iRunController(client.Kubernetes(),
client.KubeSphere(),
kubesphereInformer.Devops().V1alpha1().S2iBinaries(),
kubesphereInformer.Devops().V1alpha1().S2iRuns())
devopsProjectController = devopsproject.NewController(client.Kubernetes(),
client.KubeSphere(), devopsClient,
informerFactory.KubernetesSharedInformerFactory().Core().V1().Namespaces(),
informerFactory.KubeSphereSharedInformerFactory().Devops().V1alpha3().DevOpsProjects(),
informerFactory.KubeSphereSharedInformerFactory().Tenant().V1alpha1().Workspaces())
devopsPipelineController = pipeline.NewController(client.Kubernetes(),
client.KubeSphere(),
devopsClient,
informerFactory.KubernetesSharedInformerFactory().Core().V1().Namespaces(),
informerFactory.KubeSphereSharedInformerFactory().Devops().V1alpha3().Pipelines())
devopsCredentialController = devopscredential.NewController(client.Kubernetes(),
devopsClient,
informerFactory.KubernetesSharedInformerFactory().Core().V1().Namespaces(),
informerFactory.KubernetesSharedInformerFactory().Core().V1().Secrets())
}
storageCapabilityController := capability.NewController(
client.KubeSphere().StorageV1alpha1().StorageClassCapabilities(),
kubesphereInformer.Storage().V1alpha1(),
@@ -262,8 +225,6 @@ func addControllers(
"virtualservice-controller": vsController,
"destinationrule-controller": drController,
"job-controller": jobController,
"s2ibinary-controller": s2iBinaryController,
"s2irun-controller": s2iRunController,
"storagecapability-controller": storageCapabilityController,
"volumeexpansion-controller": volumeExpansionController,
"user-controller": userController,
@@ -278,12 +239,6 @@ func addControllers(
"group-controller": groupController,
}
if devopsClient != nil {
controllers["pipeline-controller"] = devopsPipelineController
controllers["devopsprojects-controller"] = devopsProjectController
controllers["devopscredential-controller"] = devopsCredentialController
}
if multiClusterEnabled {
controllers["globalrole-controller"] = globalRoleController
notificationController, err := notification.NewController(client.Kubernetes(), mgr.GetClient(), mgr.GetCache())

View File

@@ -256,20 +256,8 @@ func (s *APIServer) installKubeSphereAPIs() {
s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.Config.ServiceMeshOptions, s.container, s.KubernetesClient.Kubernetes(), s.CacheClient))
urlruntime.Must(networkv1alpha2.AddToContainer(s.container, s.Config.NetworkOptions.WeaveScopeHost))
urlruntime.Must(devopsv1alpha2.AddToContainer(s.container,
s.InformerFactory.KubeSphereSharedInformerFactory(),
s.DevopsClient,
s.SonarClient,
s.KubernetesClient.KubeSphere(),
s.S3Client,
s.Config.DevopsOptions.Host,
rbacAuthorizer))
urlruntime.Must(devopsv1alpha3.AddToContainer(s.container,
s.DevopsClient,
s.KubernetesClient.Kubernetes(),
s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory(),
s.InformerFactory.KubernetesSharedInformerFactory()))
urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.Config.DevopsOptions.Endpoint))
urlruntime.Must(devopsv1alpha3.AddToContainer(s.container, s.Config.DevopsOptions.Endpoint))
urlruntime.Must(notificationv1.AddToContainer(s.container, s.Config.NotificationOptions.Endpoint))
urlruntime.Must(alertingv1.AddToContainer(s.container, s.Config.AlertingOptions.Endpoint))
urlruntime.Must(alertingv2alpha1.AddToContainer(s.container, s.InformerFactory,

View File

@@ -1,333 +0,0 @@
/*
Copyright 2020 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 devopscredential
import (
"context"
"fmt"
"net/http"
"reflect"
"strings"
"time"
"github.com/emicklei/go-restful"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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/api/devops/v1alpha3"
kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/controller/utils"
modelsdevops "kubesphere.io/kubesphere/pkg/models/devops"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
/**
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,
devopsClient 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: "devopscredential-controller"})
v := &Controller{
client: client,
devopsClient: devopsClient,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "devopscredential"),
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, ok := obj.(*v1.Secret)
if ok && strings.HasPrefix(string(secret.Type), devopsv1alpha3.DevOpsCredentialPrefix) {
v.enqueueSecret(obj)
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
old, ook := oldObj.(*v1.Secret)
new, nok := newObj.(*v1.Secret)
if ook && nok && old.ResourceVersion == new.ResourceVersion {
return
}
if ook && nok && strings.HasPrefix(string(new.Type), devopsv1alpha3.DevOpsCredentialPrefix) {
v.enqueueSecret(newObj)
}
},
DeleteFunc: func(obj interface{}) {
secret, ok := obj.(*v1.Secret)
if ok && 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 devopscredential controller")
defer klog.Info("shutting down devopscredential 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 secret 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 or update credential '%s' in normal namespaces %s", name, 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() {
// make sure Annotations is not nil
if copySecret.Annotations == nil {
copySecret.Annotations = map[string]string{}
}
//If the sync is successful, return handle
if state, ok := copySecret.Annotations[devopsv1alpha3.CredentialSyncStatusAnnoKey]; ok && state == modelsdevops.StatusSuccessful {
specHash := utils.ComputeHash(copySecret.Data)
oldHash, _ := copySecret.Annotations[devopsv1alpha3.DevOpsCredentialDataHash] // don't need to check if it's nil, only compare if they're different
if specHash == oldHash {
// it was synced successfully, and there's any change with the Pipeline spec, skip this round
return nil
} else {
copySecret.Annotations[devopsv1alpha3.DevOpsCredentialDataHash] = specHash
}
}
// https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#finalizers
if !sliceutil.HasString(secret.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, copySecret.Name)
if err == nil {
if _, ok := copySecret.Annotations[devopsv1alpha3.CredentialAutoSyncAnnoKey]; ok {
_, err := c.devopsClient.UpdateCredentialInProject(nsName, copySecret)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update secret %s ", key))
return err
}
}
} else {
_, err = c.devopsClient.CreateCredentialInProject(nsName, copySecret)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to create secret %s ", key))
return err
}
}
//If there is no early return, then the sync is successful.
copySecret.Annotations[devopsv1alpha3.CredentialSyncStatusAnnoKey] = modelsdevops.StatusSuccessful
} else {
// Finalizers processing logic
if sliceutil.HasString(copySecret.ObjectMeta.Finalizers, devopsv1alpha3.CredentialFinalizerName) {
delSuccess := false
if _, err := c.devopsClient.DeleteCredentialInProject(nsName, secret.Name); err != nil {
// the status code should be 404 if the credential does not exists
if srvErr, ok := err.(restful.ServiceError); ok {
delSuccess = srvErr.Code == http.StatusNotFound
} else if srvErr, ok := err.(*devopsClient.ErrorResponse); ok {
delSuccess = srvErr.Response.StatusCode == http.StatusNotFound
} else {
klog.Error(fmt.Sprintf("unexpected error type: %v, should be *restful.ServiceError", err))
}
klog.V(8).Info(err, fmt.Sprintf("failed to delete secret %s in devops", key))
} else {
delSuccess = true
}
if delSuccess {
copySecret.ObjectMeta.Finalizers = sliceutil.RemoveString(copySecret.ObjectMeta.Finalizers, func(item string) bool {
return item == devopsv1alpha3.CredentialFinalizerName
})
} else {
// make sure the corresponding Jenkins credentials can be clean
// You can remove the finalizer via kubectl manually in a very special case that Jenkins might be not able to available anymore
return fmt.Errorf("failed to remove devops credential finalizer due to bad communication with Jenkins")
}
}
}
if !reflect.DeepEqual(secret, copySecret) {
_, err = c.client.CoreV1().Secrets(nsName).Update(context.Background(), copySecret, metav1.UpdateOptions{})
if err != nil {
klog.V(8).Info(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, "")
}

View File

@@ -1,403 +0,0 @@
/*
Copyright 2020 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 devopscredential
import (
"reflect"
"testing"
"time"
modelsdevops "kubesphere.io/kubesphere/pkg/models/devops"
v1 "k8s.io/api/core/v1"
"kubesphere.io/kubesphere/pkg/constants"
fakeDevOps "kubesphere.io/kubesphere/pkg/simple/client/devops/fake"
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/api/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, syncOk bool) *v1.Secret {
secret := &v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: devops.ResourceKindPipeline,
APIVersion: devops.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
Annotations: map[string]string{},
},
Data: data,
Type: devops.DevOpsCredentialPrefix + "test",
}
if withFinalizers {
secret.Finalizers = append(secret.Finalizers, devops.CredentialFinalizerName)
}
if autoSync {
secret.Annotations[devops.CredentialAutoSyncAnnoKey] = "true"
}
if syncOk {
secret.Annotations[devops.CredentialSyncStatusAnnoKey] = modelsdevops.StatusSuccessful
}
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())
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, false)
expectSecret := newSecret(nsName, secretName, nil, true, 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.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, false)
expectSecret := newSecret(nsName, secretName, nil, true, 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, false)
expectSecret := newSecret(nsName, secretName, nil, true, 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{expectSecret}
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 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, false)
modifiedSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, true, false)
expectSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, true, true)
f.secretLister = append(f.secretLister, modifiedSecret)
f.namespaceLister = append(f.namespaceLister, ns)
f.kubeobjects = append(f.kubeobjects, modifiedSecret)
f.initDevOpsProject = nsName
f.initCredential = []*v1.Secret{initSecret}
f.expectCredential = []*v1.Secret{expectSecret}
f.run(getKey(modifiedSecret, 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, false)
expectSecret := newSecret(nsName, secretName, map[string][]byte{"a": []byte("aa")}, true, false, 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{initSecret}
f.run(getKey(expectSecret, t))
}

View File

@@ -1,445 +0,0 @@
/*
Copyright 2020 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 devopsproject
import (
"context"
"fmt"
"net/http"
"reflect"
"time"
"github.com/emicklei/go-restful"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
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"
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"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
devopsv1alpha3 "kubesphere.io/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
tenantv1alpha1informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tenant/v1alpha1"
tenantv1alpha1listers "kubesphere.io/kubesphere/pkg/client/listers/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/constants"
modelsdevops "kubesphere.io/kubesphere/pkg/models/devops"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
devopsinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/devops/v1alpha3"
devopslisters "kubesphere.io/kubesphere/pkg/client/listers/devops/v1alpha3"
)
/**
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
devOpsProjectLister devopslisters.DevOpsProjectLister
devOpsProjectSynced cache.InformerSynced
namespaceLister corev1lister.NamespaceLister
namespaceSynced cache.InformerSynced
workspaceLister tenantv1alpha1listers.WorkspaceLister
workspaceSynced cache.InformerSynced
workqueue workqueue.RateLimitingInterface
workerLoopPeriod time.Duration
devopsClient devopsClient.Interface
}
func NewController(client clientset.Interface,
kubesphereClient kubesphereclient.Interface,
devopsClinet devopsClient.Interface,
namespaceInformer corev1informer.NamespaceInformer,
devopsInformer devopsinformers.DevOpsProjectInformer,
workspaceInformer tenantv1alpha1informers.WorkspaceInformer) *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: "devopsproject-controller"})
v := &Controller{
client: client,
devopsClient: devopsClinet,
kubesphereClient: kubesphereClient,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "devopsproject"),
devOpsProjectLister: devopsInformer.Lister(),
devOpsProjectSynced: devopsInformer.Informer().HasSynced,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,
workspaceLister: workspaceInformer.Lister(),
workspaceSynced: workspaceInformer.Informer().HasSynced,
workerLoopPeriod: time.Second,
}
v.eventBroadcaster = broadcaster
v.eventRecorder = recorder
devopsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: v.enqueueDevOpsProject,
UpdateFunc: func(oldObj, newObj interface{}) {
old := oldObj.(*devopsv1alpha3.DevOpsProject)
new := newObj.(*devopsv1alpha3.DevOpsProject)
if old.ResourceVersion == new.ResourceVersion {
return
}
v.enqueueDevOpsProject(newObj)
},
DeleteFunc: v.enqueueDevOpsProject,
})
return v
}
// enqueueDevOpsProject 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) enqueueDevOpsProject(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 devops project controller")
defer klog.Info("shutting down devops project controller")
if !cache.WaitForCacheSync(stopCh, c.devOpsProjectSynced, c.devOpsProjectSynced, c.workspaceSynced) {
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 devopsproject resource
// with the current status of the resource.
func (c *Controller) syncHandler(key string) error {
project, err := c.devOpsProjectLister.Get(key)
if err != nil {
if errors.IsNotFound(err) {
klog.Info(fmt.Sprintf("devopsproject '%s' in work queue no longer exists ", key))
return nil
}
klog.V(8).Info(err, fmt.Sprintf("could not get devopsproject %s ", key))
return err
}
copyProject := project.DeepCopy()
// DeletionTimestamp.IsZero() means DevOps project has not been deleted.
if project.ObjectMeta.DeletionTimestamp.IsZero() {
//If the sync is successful, return handle
if state, ok := project.Annotations[devopsv1alpha3.DevOpeProjectSyncStatusAnnoKey]; ok && state == modelsdevops.StatusSuccessful {
return nil
}
// Use Finalizers to sync DevOps status when DevOps project was deleted
// https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#finalizers
if !sliceutil.HasString(project.ObjectMeta.Finalizers, devopsv1alpha3.DevOpsProjectFinalizerName) {
copyProject.ObjectMeta.Finalizers = append(copyProject.ObjectMeta.Finalizers, devopsv1alpha3.DevOpsProjectFinalizerName)
}
if project.Status.AdminNamespace != "" {
ns, err := c.namespaceLister.Get(project.Status.AdminNamespace)
if err != nil && !errors.IsNotFound(err) {
klog.V(8).Info(err, fmt.Sprintf("faild to get namespace"))
return err
} else if errors.IsNotFound(err) {
// if admin ns is not found, clean project status, rerun reconcile
copyProject.Status.AdminNamespace = ""
_, err := c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(context.Background(), copyProject, metav1.UpdateOptions{})
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update project %s ", key))
return err
}
c.enqueueDevOpsProject(key)
return nil
}
// If ns exists, but the associated attributes with the project are not set correctly,
// then reset the associated attributes
if k8sutil.IsControlledBy(ns.OwnerReferences,
devopsv1alpha3.ResourceKindDevOpsProject, project.Name) &&
ns.Labels[constants.DevOpsProjectLabelKey] == project.Name {
} else {
copyNs := ns.DeepCopy()
err := controllerutil.SetControllerReference(copyProject, copyNs, scheme.Scheme)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to set ownerreference %s ", key))
return err
}
copyNs.Labels[constants.DevOpsProjectLabelKey] = project.Name
_, err = c.client.CoreV1().Namespaces().Update(context.Background(), copyNs, metav1.UpdateOptions{})
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update ns %s ", key))
return err
}
}
} else {
// list ns by devops project
namespaces, err := c.namespaceLister.List(
labels.SelectorFromSet(labels.Set{constants.DevOpsProjectLabelKey: project.Name}))
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to list ns %s ", key))
return err
}
// if there is no ns, generate new one
if len(namespaces) == 0 {
ns := c.generateNewNamespace(project)
ns, err := c.client.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{})
if err != nil {
// devops project name is conflict, cannot create admin namespace
if errors.IsAlreadyExists(err) {
klog.Errorf("Failed to create admin namespace for devopsproject %s, error %v", project.Name, err)
c.eventRecorder.Event(project, v1.EventTypeWarning, "CreateAdminNamespaceFailed", err.Error())
return err
}
klog.V(8).Info(err, fmt.Sprintf("failed to create ns %s ", key))
return err
}
copyProject.Status.AdminNamespace = ns.Name
} else if len(namespaces) != 0 {
ns := namespaces[0]
// reset ownerReferences
if !k8sutil.IsControlledBy(ns.OwnerReferences,
devopsv1alpha3.ResourceKindDevOpsProject, project.Name) {
copyNs := ns.DeepCopy()
err := controllerutil.SetControllerReference(copyProject, copyNs, scheme.Scheme)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to set ownerreference %s ", key))
return err
}
copyNs.Labels[constants.DevOpsProjectLabelKey] = project.Name
_, err = c.client.CoreV1().Namespaces().Update(context.Background(), copyNs, metav1.UpdateOptions{})
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update ns %s ", key))
return err
}
}
copyProject.Status.AdminNamespace = ns.Name
}
}
if copyProject, err = c.bindWorkspace(copyProject); err != nil {
klog.Error(err)
return err
}
// Check project exists, otherwise we will create it.
_, err := c.devopsClient.GetDevOpsProject(copyProject.Status.AdminNamespace)
if err != nil {
klog.Error(err, fmt.Sprintf("failed to get project %s ", key))
_, err := c.devopsClient.CreateDevOpsProject(copyProject.Status.AdminNamespace)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to get project %s ", key))
return err
}
}
//If there is no early return, then the sync is successful.
if copyProject.Annotations == nil {
copyProject.Annotations = map[string]string{}
}
copyProject.Annotations[devopsv1alpha3.DevOpeProjectSyncStatusAnnoKey] = modelsdevops.StatusSuccessful
if !reflect.DeepEqual(copyProject, project) {
copyProject, err = c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(context.Background(), copyProject, metav1.UpdateOptions{})
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update ns %s ", key))
return err
}
}
} else {
// Finalizers processing logic
if sliceutil.HasString(project.ObjectMeta.Finalizers, devopsv1alpha3.DevOpsProjectFinalizerName) {
delSuccess := false
if err := c.deleteDevOpsProjectInDevOps(project); err != nil {
// the status code should be 404 if the job does not exists
if srvErr, ok := err.(restful.ServiceError); ok {
delSuccess = srvErr.Code == http.StatusNotFound
} else if srvErr, ok := err.(*devopsClient.ErrorResponse); ok {
delSuccess = srvErr.Response.StatusCode == http.StatusNotFound
} else {
klog.Error(fmt.Sprintf("unexpected error type: %v, should be *restful.ServiceError", err))
}
klog.V(8).Info(err, fmt.Sprintf("failed to delete resource %s in devops", key))
} else {
delSuccess = true
}
if delSuccess {
project.ObjectMeta.Finalizers = sliceutil.RemoveString(project.ObjectMeta.Finalizers, func(item string) bool {
return item == devopsv1alpha3.DevOpsProjectFinalizerName
})
} else {
// make sure the corresponding Jenkins job can be clean
// You can remove the finalizer via kubectl manually in a very special case that Jenkins might be not able to available anymore
return fmt.Errorf("failed to remove devopsproject finalizer due to bad communication with Jenkins")
}
_, err = c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(context.Background(), project, metav1.UpdateOptions{})
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update project %s ", key))
return err
}
}
}
return nil
}
func (c *Controller) bindWorkspace(project *devopsv1alpha3.DevOpsProject) (*devopsv1alpha3.DevOpsProject, error) {
workspaceName := project.Labels[constants.WorkspaceLabelKey]
if workspaceName == "" {
return project, nil
}
workspace, err := c.workspaceLister.Get(workspaceName)
if err != nil {
// skip if workspace not found
if errors.IsNotFound(err) {
return project, nil
}
klog.Error(err)
return nil, err
}
if !metav1.IsControlledBy(project, workspace) {
project.OwnerReferences = nil
if err := controllerutil.SetControllerReference(workspace, project, scheme.Scheme); err != nil {
klog.Error(err)
return nil, err
}
return c.kubesphereClient.DevopsV1alpha3().DevOpsProjects().Update(context.Background(), project, metav1.UpdateOptions{})
}
return project, nil
}
func (c *Controller) deleteDevOpsProjectInDevOps(project *devopsv1alpha3.DevOpsProject) (err error) {
err = c.devopsClient.DeleteDevOpsProject(project.Status.AdminNamespace)
return
}
func (c *Controller) generateNewNamespace(project *devopsv1alpha3.DevOpsProject) *v1.Namespace {
// devops project name and admin namespace name should be the same
// solve the access control problem of devops API v1alpha2 and v1alpha3
ns := &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: v1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: project.Name,
Labels: map[string]string{
constants.DevOpsProjectLabelKey: project.Name,
},
},
}
if creator := project.Annotations[constants.CreatorAnnotationKey]; creator != "" {
ns.Annotations = map[string]string{constants.CreatorAnnotationKey: creator}
}
controllerutil.SetControllerReference(project, ns, scheme.Scheme)
return ns
}

View File

@@ -1,412 +0,0 @@
/*
Copyright 2020 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 devopsproject
import (
"reflect"
"testing"
"time"
v1 "k8s.io/api/core/v1"
devopsprojects "kubesphere.io/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/constants"
fakeDevOps "kubesphere.io/kubesphere/pkg/simple/client/devops/fake"
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/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
)
var (
alwaysReady = func() bool { return true }
noResyncPeriodFunc = func() time.Duration { return 0 }
)
type fixture struct {
t *testing.T
client *fake.Clientset
kubeclient *k8sfake.Clientset
// Objects to put in the store.
devopsProjectLister []*devops.DevOpsProject
namespaceLister []*v1.Namespace
actions []core.Action
kubeactions []core.Action
kubeobjects []runtime.Object
// Objects from here preloaded into NewSimpleFake.
objects []runtime.Object
// Objects from here preloaded into devops
initDevOpsProject []string
expectDevOpsProject []string
}
func newFixture(t *testing.T) *fixture {
f := &fixture{}
f.t = t
f.objects = []runtime.Object{}
return f
}
func newDevOpsProject(name string, nsName string, withFinalizers bool, withStatus bool) *devopsprojects.DevOpsProject {
project := &devopsprojects.DevOpsProject{
TypeMeta: metav1.TypeMeta{APIVersion: devopsprojects.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
if withFinalizers {
project.Finalizers = []string{devopsprojects.DevOpsProjectFinalizerName}
}
if withStatus {
project.Status = devops.DevOpsProjectStatus{AdminNamespace: nsName}
}
return project
}
func newNamespace(name string, projectName string, useGenerateName, withOwnerReference bool) *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},
},
}
if useGenerateName {
ns.ObjectMeta.Name = ""
ns.ObjectMeta.GenerateName = projectName
}
if withOwnerReference {
TRUE := true
ns.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: devops.SchemeGroupVersion.String(),
Kind: devops.ResourceKindDevOpsProject,
Name: projectName,
BlockOwnerDeletion: &TRUE,
Controller: &TRUE,
},
}
}
return ns
}
func newDeletingDevOpsProject(name string) *devopsprojects.DevOpsProject {
now := metav1.Now()
return &devopsprojects.DevOpsProject{
TypeMeta: metav1.TypeMeta{APIVersion: devopsprojects.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
DeletionTimestamp: &now,
Finalizers: []string{devopsprojects.DevOpsProjectFinalizerName},
},
}
}
func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
f.client = fake.NewSimpleClientset(f.objects...)
f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
dI := fakeDevOps.New(f.initDevOpsProject...)
c := NewController(f.kubeclient, f.client, dI,
k8sI.Core().V1().Namespaces(),
i.Devops().V1alpha3().DevOpsProjects(),
i.Tenant().V1alpha1().Workspaces())
c.devOpsProjectSynced = alwaysReady
c.eventRecorder = &record.FakeRecorder{}
for _, f := range f.devopsProjectLister {
i.Devops().V1alpha3().DevOpsProjects().Informer().GetIndexer().Add(f)
}
for _, d := range f.namespaceLister {
k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
}
return c, i, 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(projectName string, startInformers bool, expectError bool) {
c, i, k8sI, dI := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
i.Start(stopCh)
k8sI.Start(stopCh)
}
err := c.syncHandler(projectName)
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")
}
actions := filterInformerActions(f.client.Actions())
k8sActions := filterInformerActions(f.kubeclient.Actions())
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(f.actions) > len(actions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
}
if len(dI.Projects) != len(f.expectDevOpsProject) {
f.t.Errorf(" unexpected objects: %v", dI.Projects)
}
}
// 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", devopsprojects.ResourcePluralDevOpsProject) ||
action.Matches("watch", devopsprojects.ResourcePluralDevOpsProject) ||
action.Matches("list", "namespaces") ||
action.Matches("watch", "namespaces") ||
action.Matches("watch", "workspaces") ||
action.Matches("list", "workspaces")) {
continue
}
ret = append(ret, action)
}
return ret
}
func (f *fixture) expectUpdateDevOpsProjectAction(p *devopsprojects.DevOpsProject) {
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: devopsprojects.ResourcePluralDevOpsProject},
p.Namespace, p)
f.actions = append(f.actions, action)
}
func (f *fixture) expectUpdateNamespaceAction(p *v1.Namespace) {
action := core.NewUpdateAction(schema.GroupVersionResource{
Version: "v1",
Resource: "namespaces",
}, p.Namespace, p)
f.kubeactions = append(f.kubeactions, action)
}
func (f *fixture) expectCreateNamespaceAction(p *v1.Namespace) {
action := core.NewCreateAction(schema.GroupVersionResource{
Version: "v1",
Resource: "namespaces",
}, p.Namespace, p)
f.kubeactions = append(f.kubeactions, action)
}
func getKey(p *devopsprojects.DevOpsProject, t *testing.T) string {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(p)
if err != nil {
t.Errorf("Unexpected error getting key for devopsprojects %v: %v", p.Name, err)
return ""
}
return key
}
func TestDoNothing(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
projectName := "test"
project := newDevOpsProject(projectName, nsName, true, true)
ns := newNamespace(nsName, projectName, false, true)
f.devopsProjectLister = append(f.devopsProjectLister, project)
f.namespaceLister = append(f.namespaceLister, ns)
f.objects = append(f.objects, project)
f.initDevOpsProject = []string{ns.Name}
f.expectDevOpsProject = []string{ns.Name}
f.run(getKey(project, t))
}
func TestUpdateProjectFinalizers(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
projectName := "test"
project := newDevOpsProject(projectName, nsName, false, true)
ns := newNamespace(nsName, projectName, false, true)
f.devopsProjectLister = append(f.devopsProjectLister, project)
f.namespaceLister = append(f.namespaceLister, ns)
f.objects = append(f.objects, project)
f.kubeobjects = append(f.kubeobjects, ns)
f.initDevOpsProject = []string{ns.Name}
f.expectDevOpsProject = []string{ns.Name}
expectUpdateProject := project.DeepCopy()
expectUpdateProject.Finalizers = []string{devops.DevOpsProjectFinalizerName}
f.expectUpdateDevOpsProjectAction(expectUpdateProject)
f.run(getKey(project, t))
}
func TestUpdateProjectStatus(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
projectName := "test"
project := newDevOpsProject(projectName, nsName, true, false)
ns := newNamespace(nsName, projectName, false, true)
f.devopsProjectLister = append(f.devopsProjectLister, project)
f.namespaceLister = append(f.namespaceLister, ns)
f.objects = append(f.objects, project)
f.kubeobjects = append(f.kubeobjects, ns)
f.initDevOpsProject = []string{ns.Name}
f.expectDevOpsProject = []string{ns.Name}
expectUpdateProject := project.DeepCopy()
expectUpdateProject.Status.AdminNamespace = nsName
f.expectUpdateDevOpsProjectAction(expectUpdateProject)
f.run(getKey(project, t))
}
func TestUpdateNsOwnerReference(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
projectName := "test"
project := newDevOpsProject(projectName, nsName, true, true)
ns := newNamespace(nsName, projectName, false, false)
f.devopsProjectLister = append(f.devopsProjectLister, project)
f.namespaceLister = append(f.namespaceLister, ns)
f.objects = append(f.objects, project)
f.kubeobjects = append(f.kubeobjects, ns)
f.initDevOpsProject = []string{ns.Name}
f.expectDevOpsProject = []string{ns.Name}
expectUpdateNs := newNamespace(nsName, projectName, false, true)
f.expectUpdateNamespaceAction(expectUpdateNs)
f.run(getKey(project, t))
}
func TestCreateDevOpsProjects(t *testing.T) {
f := newFixture(t)
project := newDevOpsProject("test", "", true, false)
ns := newNamespace("test", "test", false, true)
f.devopsProjectLister = append(f.devopsProjectLister, project)
f.objects = append(f.objects, project)
f.expectDevOpsProject = []string{""}
expect := project.DeepCopy()
expect.Status.AdminNamespace = "test"
f.expectUpdateDevOpsProjectAction(expect)
f.expectCreateNamespaceAction(ns)
f.run(getKey(project, t))
}
func TestDeleteDevOpsProjects(t *testing.T) {
f := newFixture(t)
project := newDeletingDevOpsProject("test")
f.devopsProjectLister = append(f.devopsProjectLister, project)
f.objects = append(f.objects, project)
f.initDevOpsProject = []string{project.Name}
f.expectDevOpsProject = []string{project.Name}
expectProject := project.DeepCopy()
expectProject.Finalizers = []string{}
f.expectUpdateDevOpsProjectAction(expectProject)
f.run(getKey(project, t))
}
func TestDeleteDevOpsProjectsWithNull(t *testing.T) {
f := newFixture(t)
project := newDeletingDevOpsProject("test")
f.devopsProjectLister = append(f.devopsProjectLister, project)
f.objects = append(f.objects, project)
f.expectDevOpsProject = []string{}
expectProject := project.DeepCopy()
expectProject.Finalizers = []string{}
f.expectUpdateDevOpsProjectAction(expectProject)
f.run(getKey(project, t))
}

View File

@@ -1,328 +0,0 @@
/*
Copyright 2020 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 pipeline
import (
"context"
"fmt"
"net/http"
"reflect"
"time"
"github.com/emicklei/go-restful"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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/api/devops/v1alpha3"
kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
devopsinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/devops/v1alpha3"
devopslisters "kubesphere.io/kubesphere/pkg/client/listers/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/controller/utils"
modelsdevops "kubesphere.io/kubesphere/pkg/models/devops"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
/**
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
devOpsProjectLister devopslisters.PipelineLister
pipelineSynced cache.InformerSynced
namespaceLister corev1lister.NamespaceLister
namespaceSynced cache.InformerSynced
workqueue workqueue.RateLimitingInterface
workerLoopPeriod time.Duration
devopsClient devopsClient.Interface
}
func NewController(client clientset.Interface,
kubesphereClient kubesphereclient.Interface,
devopsClinet devopsClient.Interface,
namespaceInformer corev1informer.NamespaceInformer,
devopsInformer devopsinformers.PipelineInformer) *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,
kubesphereClient: kubesphereClient,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pipeline"),
devOpsProjectLister: devopsInformer.Lister(),
pipelineSynced: devopsInformer.Informer().HasSynced,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,
workerLoopPeriod: time.Second,
}
v.eventBroadcaster = broadcaster
v.eventRecorder = recorder
devopsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: v.enqueuePipeline,
UpdateFunc: func(oldObj, newObj interface{}) {
oldPipeline := oldObj.(*devopsv1alpha3.Pipeline)
newPipeline := newObj.(*devopsv1alpha3.Pipeline)
if oldPipeline.ResourceVersion == newPipeline.ResourceVersion {
return
}
v.enqueuePipeline(newObj)
},
DeleteFunc: v.enqueuePipeline,
})
return v
}
// enqueuePipeline 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) enqueuePipeline(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.pipelineSynced) {
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 copyPipeline 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.V(8).Info(err, fmt.Sprintf("could not get namespace %s ", key))
return err
}
if !isDevOpsProjectAdminNamespace(namespace) {
err := fmt.Errorf("cound not create copyPipeline in normal namespaces %s", namespace.Name)
klog.Warning(err)
return err
}
pipeline, err := c.devOpsProjectLister.Pipelines(nsName).Get(name)
if err != nil {
if errors.IsNotFound(err) {
klog.V(8).Info(fmt.Sprintf("copyPipeline '%s' in work queue no longer exists ", key))
return nil
}
klog.Error(err, fmt.Sprintf("could not get copyPipeline %s ", key))
return err
}
copyPipeline := pipeline.DeepCopy()
// DeletionTimestamp.IsZero() means copyPipeline has not been deleted.
if copyPipeline.ObjectMeta.DeletionTimestamp.IsZero() {
// make sure Annotations is not nil
if copyPipeline.Annotations == nil {
copyPipeline.Annotations = map[string]string{}
}
//If the sync is successful, return handle
if state, ok := copyPipeline.Annotations[devopsv1alpha3.PipelineSyncStatusAnnoKey]; ok && state == modelsdevops.StatusSuccessful {
specHash := utils.ComputeHash(copyPipeline.Spec)
oldHash, _ := copyPipeline.Annotations[devopsv1alpha3.PipelineSpecHash] // don't need to check if it's nil, only compare if they're different
if specHash == oldHash {
// it was synced successfully, and there's any change with the Pipeline spec, skip this round
return nil
} else {
copyPipeline.Annotations[devopsv1alpha3.PipelineSpecHash] = specHash
}
}
// https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#finalizers
if !sliceutil.HasString(copyPipeline.ObjectMeta.Finalizers, devopsv1alpha3.PipelineFinalizerName) {
copyPipeline.ObjectMeta.Finalizers = append(copyPipeline.ObjectMeta.Finalizers, devopsv1alpha3.PipelineFinalizerName)
}
// Check pipeline config exists, otherwise we will create it.
// if pipeline exists, check & update config
jenkinsPipeline, err := c.devopsClient.GetProjectPipelineConfig(nsName, pipeline.Name)
if err == nil {
if !reflect.DeepEqual(jenkinsPipeline.Spec, copyPipeline.Spec) {
_, err := c.devopsClient.UpdateProjectPipeline(nsName, copyPipeline)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update pipeline config %s ", key))
return err
}
} else {
klog.V(8).Info(fmt.Sprintf("nothing was changed, pipeline '%v'", copyPipeline.Spec))
}
} else {
_, err := c.devopsClient.CreateProjectPipeline(nsName, copyPipeline)
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to create copyPipeline %s ", key))
return err
}
}
//If there is no early return, then the sync is successful.
copyPipeline.Annotations[devopsv1alpha3.PipelineSyncStatusAnnoKey] = modelsdevops.StatusSuccessful
} else {
// Finalizers processing logic
if sliceutil.HasString(copyPipeline.ObjectMeta.Finalizers, devopsv1alpha3.PipelineFinalizerName) {
delSuccess := false
if _, err := c.devopsClient.DeleteProjectPipeline(nsName, pipeline.Name); err != nil {
// the status code should be 404 if the job does not exists
if srvErr, ok := err.(restful.ServiceError); ok {
delSuccess = srvErr.Code == http.StatusNotFound
} else if srvErr, ok := err.(*devopsClient.ErrorResponse); ok {
delSuccess = srvErr.Response.StatusCode == http.StatusNotFound
} else {
klog.Error(fmt.Sprintf("unexpected error type: %v, should be *restful.ServiceError", err))
}
klog.V(8).Info(err, fmt.Sprintf("failed to delete pipeline %s in devops", key))
} else {
delSuccess = true
}
if delSuccess {
copyPipeline.ObjectMeta.Finalizers = sliceutil.RemoveString(copyPipeline.ObjectMeta.Finalizers, func(item string) bool {
return item == devopsv1alpha3.PipelineFinalizerName
})
} else {
// make sure the corresponding Jenkins job can be clean
// You can remove the finalizer via kubectl manually in a very special case that Jenkins might be not able to available anymore
return fmt.Errorf("failed to remove pipeline job finalizer due to bad communication with Jenkins")
}
}
}
if !reflect.DeepEqual(pipeline, copyPipeline) {
_, err = c.kubesphereClient.DevopsV1alpha3().Pipelines(nsName).Update(context.Background(), copyPipeline, metav1.UpdateOptions{})
if err != nil {
klog.V(8).Info(err, fmt.Sprintf("failed to update pipeline %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, "")
}

View File

@@ -1,423 +0,0 @@
/*
Copyright 2020 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 pipeline
import (
"reflect"
"testing"
"time"
v1 "k8s.io/api/core/v1"
"kubesphere.io/kubesphere/pkg/constants"
modelsdevops "kubesphere.io/kubesphere/pkg/models/devops"
fakeDevOps "kubesphere.io/kubesphere/pkg/simple/client/devops/fake"
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/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
)
var (
alwaysReady = func() bool { return true }
noResyncPeriodFunc = func() time.Duration { return 0 }
)
type fixture struct {
t *testing.T
client *fake.Clientset
kubeclient *k8sfake.Clientset
namespaceLister []*v1.Namespace
pipelineLister []*devops.Pipeline
actions []core.Action
kubeactions []core.Action
kubeobjects []runtime.Object
// Objects from here preloaded into NewSimpleFake.
objects []runtime.Object
// Objects from here preloaded into devops
initDevOpsProject string
initPipeline []*devops.Pipeline
expectPipeline []*devops.Pipeline
}
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 newPipeline(namespace, name string, spec devops.PipelineSpec, withFinalizers bool, syncOk bool) *devops.Pipeline {
pipeline := &devops.Pipeline{
TypeMeta: metav1.TypeMeta{
Kind: devops.ResourceKindPipeline,
APIVersion: devops.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
Annotations: map[string]string{},
},
Spec: spec,
Status: devops.PipelineStatus{},
}
if withFinalizers {
pipeline.Finalizers = append(pipeline.Finalizers, devops.PipelineFinalizerName)
}
if syncOk {
pipeline.Annotations[devops.PipelineSyncStatusAnnoKey] = modelsdevops.StatusSuccessful
}
return pipeline
}
func newDeletingPipeline(namespace, name string) *devops.Pipeline {
now := metav1.Now()
pipeline := &devops.Pipeline{
TypeMeta: metav1.TypeMeta{
Kind: devops.ResourceKindPipeline,
APIVersion: devops.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
DeletionTimestamp: &now,
},
}
pipeline.Finalizers = append(pipeline.Finalizers, devops.PipelineFinalizerName)
return pipeline
}
func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, kubeinformers.SharedInformerFactory, *fakeDevOps.Devops) {
f.client = fake.NewSimpleClientset(f.objects...)
f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
dI := fakeDevOps.NewWithPipelines(f.initDevOpsProject, f.initPipeline...)
c := NewController(f.kubeclient, f.client, dI, k8sI.Core().V1().Namespaces(),
i.Devops().V1alpha3().Pipelines())
c.pipelineSynced = alwaysReady
c.eventRecorder = &record.FakeRecorder{}
for _, f := range f.pipelineLister {
i.Devops().V1alpha3().Pipelines().Informer().GetIndexer().Add(f)
}
for _, d := range f.namespaceLister {
k8sI.Core().V1().Namespaces().Informer().GetIndexer().Add(d)
}
return c, i, 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(projectName string, startInformers bool, expectError bool) {
c, i, k8sI, dI := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
i.Start(stopCh)
k8sI.Start(stopCh)
}
err := c.syncHandler(projectName)
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")
}
actions := filterInformerActions(f.client.Actions())
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(f.actions) > len(actions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
}
if len(dI.Pipelines[f.initDevOpsProject]) != len(f.expectPipeline) {
f.t.Errorf(" unexpected objects: %v", dI.Projects)
}
for _, pipeline := range f.expectPipeline {
actualPipeline := dI.Pipelines[f.initDevOpsProject][pipeline.Name]
if !reflect.DeepEqual(actualPipeline, pipeline) {
f.t.Errorf(" pipeline %+v not match %+v", pipeline, actualPipeline)
}
}
}
// 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", devops.ResourcePluralPipeline) ||
action.Matches("watch", devops.ResourcePluralPipeline) ||
action.Matches("list", "namespaces") ||
action.Matches("watch", "namespaces")) {
continue
}
ret = append(ret, action)
}
return ret
}
func (f *fixture) expectUpdatePipelineAction(p *devops.Pipeline) {
action := core.NewUpdateAction(schema.GroupVersionResource{
Version: devops.SchemeGroupVersion.Version,
Resource: devops.ResourcePluralPipeline,
Group: devops.SchemeGroupVersion.Group,
}, p.Namespace, p)
f.actions = append(f.actions, action)
}
func getKey(p *devops.Pipeline, 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"
pipelineName := "test"
projectName := "test_project"
ns := newNamespace(nsName, projectName)
pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
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{pipeline}
f.run(getKey(pipeline, t))
}
func TestAddPipelineFinalizers(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
pipelineName := "test"
projectName := "test_project"
ns := newNamespace(nsName, projectName)
pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, false, false)
expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
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{pipeline}
f.expectUpdatePipelineAction(expectPipeline)
f.run(getKey(pipeline, t))
}
func TestCreatePipeline(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
pipelineName := "test"
projectName := "test_project"
ns := newNamespace(nsName, projectName)
pipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, false, false)
expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, true)
f.pipelineLister = append(f.pipelineLister, pipeline)
f.namespaceLister = append(f.namespaceLister, ns)
f.objects = append(f.objects, pipeline)
f.initDevOpsProject = nsName
f.expectPipeline = []*devops.Pipeline{expectPipeline}
f.run(getKey(pipeline, t))
}
func TestDeletePipeline(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
pipelineName := "test"
projectName := "test_project"
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))
}
func TestDeleteNotExistPipeline(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
pipelineName := "test"
projectName := "test_project"
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))
}
func TestUpdatePipelineConfig(t *testing.T) {
f := newFixture(t)
nsName := "test-123"
pipelineName := "test"
projectName := "test_project"
ns := newNamespace(nsName, projectName)
initPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{}, true, false)
modifiedPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{Type: "aa"}, true, false)
expectPipeline := newPipeline(nsName, pipelineName, devops.PipelineSpec{Type: "aa"}, true, true)
f.pipelineLister = append(f.pipelineLister, modifiedPipeline)
f.namespaceLister = append(f.namespaceLister, ns)
f.objects = append(f.objects, modifiedPipeline)
f.initDevOpsProject = nsName
f.initPipeline = []*devops.Pipeline{initPipeline}
f.expectPipeline = []*devops.Pipeline{expectPipeline}
f.run(getKey(modifiedPipeline, t))
}

View File

@@ -1,13 +0,0 @@
approvers:
- shaowenchen
- linuxsuren
reviewers:
- runzexia
- soulseen
- shaowenchen
- linuxsuren
labels:
- area/controller
- area/devops

View File

@@ -1,242 +0,0 @@
/*
Copyright 2020 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 s2ibinary
import (
"context"
"fmt"
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
devopsv1alpha1 "kubesphere.io/api/devops/v1alpha1"
devopsclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
devopsinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/devops/v1alpha1"
devopslisters "kubesphere.io/kubesphere/pkg/client/listers/devops/v1alpha1"
)
/**
s2ibinary-controller used to handle s2ibinary's delete logic.
s2ibinary creation and file upload provided by kubesphere/kapis
*/
type Controller struct {
client clientset.Interface
devopsClient devopsclient.Interface
eventBroadcaster record.EventBroadcaster
eventRecorder record.EventRecorder
s2iBinaryLister devopslisters.S2iBinaryLister
s2iBinarySynced cache.InformerSynced
workqueue workqueue.RateLimitingInterface
workerLoopPeriod time.Duration
s3Client s3.Interface
}
func NewController(client clientset.Interface,
devopsclientset devopsclient.Interface,
s2ibinInformer devopsinformers.S2iBinaryInformer,
s3Client s3.Interface) *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: "s2ibinary-controller"})
v := &Controller{
client: client,
devopsClient: devopsclientset,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "s2ibinary"),
s2iBinaryLister: s2ibinInformer.Lister(),
s2iBinarySynced: s2ibinInformer.Informer().HasSynced,
workerLoopPeriod: time.Second,
s3Client: s3Client,
}
v.eventBroadcaster = broadcaster
v.eventRecorder = recorder
s2ibinInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: v.enqueueS2iBinary,
UpdateFunc: func(oldObj, newObj interface{}) {
old := oldObj.(*devopsv1alpha1.S2iBinary)
new := newObj.(*devopsv1alpha1.S2iBinary)
if old.ResourceVersion == new.ResourceVersion {
return
}
v.enqueueS2iBinary(newObj)
},
DeleteFunc: v.enqueueS2iBinary,
})
return v
}
// enqueueS2iBinary 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 S2iBinary.
func (c *Controller) enqueueS2iBinary(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 s2ibinary")
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 s2ibinary controller")
defer klog.Info("shutting down s2ibinary controller")
if !cache.WaitForCacheSync(stopCh, c.s2iBinarySynced) {
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 Foo resource
// with the current status of the resource.
func (c *Controller) syncHandler(key string) error {
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
klog.Error(err, fmt.Sprintf("could not split s2ibin meta %s ", key))
return nil
}
s2ibin, err := c.s2iBinaryLister.S2iBinaries(namespace).Get(name)
if err != nil {
if errors.IsNotFound(err) {
klog.Info(fmt.Sprintf("s2ibin '%s' in work queue no longer exists ", key))
return nil
}
klog.Error(err, fmt.Sprintf("could not get s2ibin %s ", key))
return err
}
if s2ibin.ObjectMeta.DeletionTimestamp.IsZero() {
if !sliceutil.HasString(s2ibin.ObjectMeta.Finalizers, devopsv1alpha1.S2iBinaryFinalizerName) {
s2ibin.ObjectMeta.Finalizers = append(s2ibin.ObjectMeta.Finalizers, devopsv1alpha1.S2iBinaryFinalizerName)
_, err := c.devopsClient.DevopsV1alpha1().S2iBinaries(namespace).Update(context.Background(), s2ibin, metav1.UpdateOptions{})
if err != nil {
klog.Error(err, fmt.Sprintf("failed to update s2ibin %s ", key))
return err
}
}
} else {
if sliceutil.HasString(s2ibin.ObjectMeta.Finalizers, devopsv1alpha1.S2iBinaryFinalizerName) {
if err := c.deleteBinaryInS3(s2ibin); err != nil {
klog.Error(err, fmt.Sprintf("failed to delete resource %s in s3", key))
return err
}
s2ibin.ObjectMeta.Finalizers = sliceutil.RemoveString(s2ibin.ObjectMeta.Finalizers, func(item string) bool {
return item == devopsv1alpha1.S2iBinaryFinalizerName
})
_, err := c.devopsClient.DevopsV1alpha1().S2iBinaries(namespace).Update(context.Background(), s2ibin, metav1.UpdateOptions{})
if err != nil {
klog.Error(err, fmt.Sprintf("failed to update s2ibin %s ", key))
return err
}
}
}
return nil
}
func (c *Controller) deleteBinaryInS3(s2ibin *devopsv1alpha1.S2iBinary) error {
key := fmt.Sprintf("%s-%s", s2ibin.Namespace, s2ibin.Name)
err := c.s3Client.Delete(key)
if err != nil {
klog.Errorf("error happened while deleting %s, %v", key, err)
}
return nil
}

View File

@@ -1,254 +0,0 @@
/*
Copyright 2020 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 s2ibinary
import (
"reflect"
"testing"
"time"
fakes3 "kubesphere.io/kubesphere/pkg/simple/client/s3/fake"
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"
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"
s2i "kubesphere.io/api/devops/v1alpha1"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
)
var (
alwaysReady = func() bool { return true }
noResyncPeriodFunc = func() time.Duration { return 0 }
)
type fixture struct {
t *testing.T
client *fake.Clientset
kubeclient *k8sfake.Clientset
// Objects to put in the store.
s2ibinaryLister []*s2i.S2iBinary
actions []core.Action
// Objects from here preloaded into NewSimpleFake.
objects []runtime.Object
// Objects from here preloaded into s3
initS3Objects []*fakes3.Object
expectS3Objects []*fakes3.Object
}
func newFixture(t *testing.T) *fixture {
f := &fixture{}
f.t = t
f.objects = []runtime.Object{}
return f
}
func newS2iBinary(name string, spec s2i.S2iBinarySpec) *s2i.S2iBinary {
return &s2i.S2iBinary{
TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
},
Spec: spec,
}
}
func newDeletingS2iBinary(name string) *s2i.S2iBinary {
deleteTime := metav1.Now()
return &s2i.S2iBinary{
TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
Finalizers: []string{s2i.S2iBinaryFinalizerName},
DeletionTimestamp: &deleteTime,
},
}
}
func (f *fixture) newController() (*Controller, informers.SharedInformerFactory, *fakes3.FakeS3) {
f.client = fake.NewSimpleClientset(f.objects...)
f.kubeclient = k8sfake.NewSimpleClientset()
i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
s3I := fakes3.NewFakeS3(f.expectS3Objects...)
c := NewController(f.kubeclient, f.client, i.Devops().V1alpha1().S2iBinaries(), s3I)
c.s2iBinarySynced = alwaysReady
c.eventRecorder = &record.FakeRecorder{}
for _, f := range f.s2ibinaryLister {
i.Devops().V1alpha1().S2iBinaries().Informer().GetIndexer().Add(f)
}
return c, i, s3I
}
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(s2iBinaryName string, startInformers bool, expectError bool) {
c, i, s3I := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
i.Start(stopCh)
}
err := c.syncHandler(s2iBinaryName)
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")
}
actions := filterInformerActions(f.client.Actions())
for i, action := range actions {
if len(f.actions) < i+1 {
f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
break
}
expectedAction := f.actions[i]
checkAction(expectedAction, action, f.t)
}
if len(f.actions) > len(actions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
}
if len(s3I.Storage) != len(f.expectS3Objects) {
f.t.Errorf(" unexpected objects: %v", s3I.Storage)
}
}
// 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", s2i.ResourcePluralS2iBinary) ||
action.Matches("watch", s2i.ResourcePluralS2iBinary)) {
continue
}
ret = append(ret, action)
}
return ret
}
func (f *fixture) expectUpdateS2iBinaryAction(s2ibinary *s2i.S2iBinary) {
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iBinary}, s2ibinary.Namespace, s2ibinary)
f.actions = append(f.actions, action)
}
func getKey(s2ibinary *s2i.S2iBinary, t *testing.T) string {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(s2ibinary)
if err != nil {
t.Errorf("Unexpected error getting key for s2ibinary %v: %v", s2ibinary.Name, err)
return ""
}
return key
}
func TestDoNothing(t *testing.T) {
f := newFixture(t)
s2iBinary := newS2iBinary("test", s2i.S2iBinarySpec{})
f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
f.objects = append(f.objects, s2iBinary)
f.expectUpdateS2iBinaryAction(s2iBinary)
f.run(getKey(s2iBinary, t))
}
func TestDeleteS3Object(t *testing.T) {
f := newFixture(t)
s2iBinary := newDeletingS2iBinary("test")
f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
f.objects = append(f.objects, s2iBinary)
f.initS3Objects = []*fakes3.Object{{
Key: "default-test",
}}
f.expectS3Objects = []*fakes3.Object{}
f.expectUpdateS2iBinaryAction(s2iBinary)
f.run(getKey(s2iBinary, t))
}

View File

@@ -1,13 +0,0 @@
approvers:
- shaowenchen
- linuxsuren
reviewers:
- runzexia
- soulseen
- shaowenchen
- linuxsuren
labels:
- area/controller
- area/devops

View File

@@ -1,304 +0,0 @@
/*
Copyright 2020 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 s2irun
import (
"context"
"fmt"
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
devopsv1alpha1 "kubesphere.io/api/devops/v1alpha1"
devopsclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
devopsinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/devops/v1alpha1"
devopslisters "kubesphere.io/kubesphere/pkg/client/listers/devops/v1alpha1"
)
/**
s2irun-controller used to handle s2irun's delete logic.
s2irun creation and operation provided by s2ioperator
*/
type Controller struct {
client clientset.Interface
devopsClient devopsclient.Interface
eventBroadcaster record.EventBroadcaster
eventRecorder record.EventRecorder
s2iRunLister devopslisters.S2iRunLister
s2iRunSynced cache.InformerSynced
s2iBinaryLister devopslisters.S2iBinaryLister
s2iBinarySynced cache.InformerSynced
workqueue workqueue.RateLimitingInterface
workerLoopPeriod time.Duration
}
func NewS2iRunController(
client clientset.Interface,
devopsClientSet devopsclient.Interface,
s2iBinInformer devopsinformers.S2iBinaryInformer,
s2iRunInformer devopsinformers.S2iRunInformer) *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: "s2irun-controller"})
v := &Controller{
client: client,
devopsClient: devopsClientSet,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "s2irun"),
s2iBinaryLister: s2iBinInformer.Lister(),
s2iBinarySynced: s2iBinInformer.Informer().HasSynced,
s2iRunLister: s2iRunInformer.Lister(),
s2iRunSynced: s2iRunInformer.Informer().HasSynced,
workerLoopPeriod: time.Second,
}
v.eventBroadcaster = broadcaster
v.eventRecorder = recorder
s2iRunInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: v.enqueueS2iRun,
UpdateFunc: func(oldObj, newObj interface{}) {
old := oldObj.(*devopsv1alpha1.S2iRun)
new := newObj.(*devopsv1alpha1.S2iRun)
if old.ResourceVersion == new.ResourceVersion {
return
}
v.enqueueS2iRun(newObj)
},
DeleteFunc: v.enqueueS2iRun,
})
return v
}
// enqueueFoo 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 Foo.
func (c Controller) enqueueS2iRun(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 s2irun")
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 s2irun controller")
defer klog.Info("shutting down s2irun controller")
if !cache.WaitForCacheSync(stopCh, c.s2iBinarySynced) {
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 Foo resource
// with the current status of the resource.
func (c Controller) syncHandler(key string) error {
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
klog.Error(err, fmt.Sprintf("could not split s2irun meta %s ", key))
return nil
}
s2irun, err := c.s2iRunLister.S2iRuns(namespace).Get(name)
if err != nil {
if errors.IsNotFound(err) {
klog.Info(fmt.Sprintf("s2irun '%s' in work queue no longer exists ", key))
return nil
}
klog.Error(err, fmt.Sprintf("could not get s2irun %s ", key))
return err
}
if s2irun.Labels != nil {
_, ok := s2irun.Labels[devopsv1alpha1.S2iBinaryLabelKey]
if ok {
if s2irun.ObjectMeta.DeletionTimestamp.IsZero() {
if !sliceutil.HasString(s2irun.ObjectMeta.Finalizers, devopsv1alpha1.S2iBinaryFinalizerName) {
s2irun.ObjectMeta.Finalizers = append(s2irun.ObjectMeta.Finalizers, devopsv1alpha1.S2iBinaryFinalizerName)
_, err = c.devopsClient.DevopsV1alpha1().S2iRuns(namespace).Update(context.Background(), s2irun, metav1.UpdateOptions{})
if err != nil {
klog.Error(err, fmt.Sprintf("failed to update s2irun %s", key))
return err
}
}
} else {
if sliceutil.HasString(s2irun.ObjectMeta.Finalizers, devopsv1alpha1.S2iBinaryFinalizerName) {
if err := c.DeleteS2iBinary(s2irun); err != nil {
klog.Error(err, fmt.Sprintf("failed to delete s2ibin %s in", key))
return err
}
s2irun.ObjectMeta.Finalizers = sliceutil.RemoveString(s2irun.ObjectMeta.Finalizers, func(item string) bool {
return item == devopsv1alpha1.S2iBinaryFinalizerName
})
_, err = c.devopsClient.DevopsV1alpha1().S2iRuns(namespace).Update(context.Background(), s2irun, metav1.UpdateOptions{})
if err != nil {
klog.Error(err, fmt.Sprintf("failed to update s2irun %s ", key))
return err
}
}
}
}
}
return nil
}
/**
DeleteS2iBinary mainly cleans up two parts of S2iBinary
1. s2ibinary bound to s2irun
2. s2ibinary that has been created for more than 24 hours but has not been used
*/
func (c Controller) DeleteS2iBinary(s2irun *devopsv1alpha1.S2iRun) error {
s2iBinName := s2irun.Labels[devopsv1alpha1.S2iBinaryLabelKey]
s2iBin, err := c.s2iBinaryLister.S2iBinaries(s2irun.Namespace).Get(s2iBinName)
if err != nil {
if errors.IsNotFound(err) {
klog.Info(fmt.Sprintf("s2ibin '%s/%s' has been delted ", s2irun.Namespace, s2iBinName))
return nil
}
klog.Error(err, fmt.Sprintf("failed to get s2ibin %s/%s ", s2irun.Namespace, s2iBinName))
return err
}
err = c.devopsClient.DevopsV1alpha1().S2iBinaries(s2iBin.Namespace).Delete(context.Background(), s2iBinName, metav1.DeleteOptions{})
if err != nil {
if errors.IsNotFound(err) {
klog.Info(fmt.Sprintf("s2ibin '%s/%s' has been delted ", s2irun.Namespace, s2iBinName))
return nil
}
klog.Error(err, fmt.Sprintf("failed to delete s2ibin %s/%s ", s2irun.Namespace, s2iBinName))
return err
}
if err = c.cleanOtherS2iBinary(s2irun.Namespace); err != nil {
klog.Error(err, fmt.Sprintf("failed to clean s2ibinary in %s", s2irun.Namespace))
}
return nil
}
// cleanOtherS2iBinary clean up s2ibinary created for more than 24 hours without associated s2irun
func (c Controller) cleanOtherS2iBinary(namespace string) error {
s2iBins, err := c.s2iBinaryLister.S2iBinaries(namespace).List(labels.Everything())
if err != nil {
klog.Error(err, fmt.Sprintf("failed to list s2ibin in %s ", namespace))
return err
}
now := time.Now()
dayBefore := metav1.NewTime(now.Add(time.Hour * 24 * -1))
for _, s2iBin := range s2iBins {
if s2iBin.Status.Phase != devopsv1alpha1.StatusReady && s2iBin.CreationTimestamp.Before(&dayBefore) {
runs, err := c.s2iRunLister.S2iRuns(namespace).List(labels.SelectorFromSet(labels.Set{devopsv1alpha1.S2iBinaryLabelKey: s2iBin.Name}))
if err != nil {
klog.Error(err, fmt.Sprintf("failed to list s2irun in %s ", namespace))
return err
}
if len(runs) == 0 {
err = c.devopsClient.DevopsV1alpha1().S2iBinaries(namespace).Delete(context.Background(), s2iBin.Name, metav1.DeleteOptions{})
if err != nil {
if errors.IsNotFound(err) {
klog.Info(fmt.Sprintf("s2ibin '%s/%s' has been deleted ", namespace, s2iBin.Name))
return nil
}
klog.Error(err, fmt.Sprintf("failed to delete s2ibin %s/%s ", namespace, s2iBin.Name))
return err
}
}
}
}
return nil
}

View File

@@ -1,323 +0,0 @@
/*
Copyright 2020 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 s2irun
import (
"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"
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"
s2i "kubesphere.io/api/devops/v1alpha1"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
)
var (
alwaysReady = func() bool { return true }
noResyncPeriodFunc = func() time.Duration { return 0 }
)
type fixture struct {
t *testing.T
client *fake.Clientset
kubeclient *k8sfake.Clientset
// Objects to put in the store.
s2ibinaryLister []*s2i.S2iBinary
s2irunLister []*s2i.S2iRun
actions []core.Action
// Objects from here preloaded into NewSimpleFake.
objects []runtime.Object
}
func newFixture(t *testing.T) *fixture {
f := &fixture{}
f.t = t
f.objects = []runtime.Object{}
return f
}
func newS2iBinary(name string) *s2i.S2iBinary {
return &s2i.S2iBinary{
TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
},
Spec: s2i.S2iBinarySpec{},
}
}
func newS2iBinaryWithCreateTime(name string, createTime metav1.Time) *s2i.S2iBinary {
return &s2i.S2iBinary{
TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
CreationTimestamp: createTime,
},
Spec: s2i.S2iBinarySpec{},
}
}
func newS2iRun(name string, s2iBinaryName string) *s2i.S2iRun {
return &s2i.S2iRun{
TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
Labels: map[string]string{
s2i.S2iBinaryLabelKey: s2iBinaryName,
},
},
}
}
func newDeletetingS2iRun(name string, s2iBinaryName string) *s2i.S2iRun {
now := metav1.Now()
return &s2i.S2iRun{
TypeMeta: metav1.TypeMeta{APIVersion: s2i.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
Labels: map[string]string{
s2i.S2iBinaryLabelKey: s2iBinaryName,
},
Finalizers: []string{s2i.S2iBinaryFinalizerName},
DeletionTimestamp: &now,
},
}
}
func (f *fixture) newController() (*Controller, informers.SharedInformerFactory) {
f.client = fake.NewSimpleClientset(f.objects...)
f.kubeclient = k8sfake.NewSimpleClientset()
i := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc())
c := NewS2iRunController(f.kubeclient, f.client,
i.Devops().V1alpha1().S2iBinaries(), i.Devops().V1alpha1().S2iRuns())
c.s2iBinarySynced = alwaysReady
c.eventRecorder = &record.FakeRecorder{}
for _, f := range f.s2ibinaryLister {
i.Devops().V1alpha1().S2iBinaries().Informer().GetIndexer().Add(f)
}
for _, f := range f.s2irunLister {
i.Devops().V1alpha1().S2iRuns().Informer().GetIndexer().Add(f)
}
return c, i
}
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(s2iRunName string, startInformers bool, expectError bool) {
c, i := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
i.Start(stopCh)
}
err := c.syncHandler(s2iRunName)
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")
}
actions := filterInformerActions(f.client.Actions())
for i, action := range actions {
if len(f.actions) < i+1 {
f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
break
}
expectedAction := f.actions[i]
checkAction(expectedAction, action, f.t)
}
if len(f.actions) > len(actions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
}
}
// 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))
}
case core.DeleteActionImpl:
e, _ := expected.(core.DeleteActionImpl)
expName := e.GetName()
objectName := a.GetName()
if !reflect.DeepEqual(expName, objectName) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expName, objectName))
}
expNamespace := e.GetNamespace()
objectNamespace := a.GetNamespace()
if !reflect.DeepEqual(expNamespace, objectNamespace) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expNamespace, objectNamespace))
}
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", s2i.ResourcePluralS2iRun) ||
action.Matches("watch", s2i.ResourcePluralS2iRun) ||
action.Matches("list", s2i.ResourcePluralS2iBinary) ||
action.Matches("watch", s2i.ResourcePluralS2iBinary)) {
continue
}
ret = append(ret, action)
}
return ret
}
func (f *fixture) expectUpdateS2iRunAction(s2iRun *s2i.S2iRun) {
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iRun}, s2iRun.Namespace, s2iRun)
f.actions = append(f.actions, action)
}
func (f *fixture) expectDeleteS2iBinaryAction(s2iBinary *s2i.S2iBinary) {
action := core.NewDeleteAction(schema.GroupVersionResource{Resource: s2i.ResourcePluralS2iBinary}, s2iBinary.Namespace, s2iBinary.Name)
f.actions = append(f.actions, action)
}
func getKey(s2i *s2i.S2iRun, t *testing.T) string {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(s2i)
if err != nil {
t.Errorf("Unexpected error getting key for s2i %v: %v", s2i.Name, err)
return ""
}
return key
}
func TestDoNothing(t *testing.T) {
f := newFixture(t)
s2iBinary := newS2iBinary("test")
s2iRun := newS2iRun("test", s2iBinary.Name)
f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
f.s2irunLister = append(f.s2irunLister, s2iRun)
f.objects = append(f.objects, s2iBinary)
f.objects = append(f.objects, s2iRun)
f.expectUpdateS2iRunAction(s2iRun)
f.run(getKey(s2iRun, t))
}
func TestDeleteS2iBinary(t *testing.T) {
f := newFixture(t)
s2iBinary := newS2iBinary("test")
s2iRun := newDeletetingS2iRun("test", s2iBinary.Name)
f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
f.s2irunLister = append(f.s2irunLister, s2iRun)
f.objects = append(f.objects, s2iBinary)
f.objects = append(f.objects, s2iRun)
f.expectDeleteS2iBinaryAction(s2iBinary)
f.expectUpdateS2iRunAction(s2iRun)
f.run(getKey(s2iRun, t))
}
func TestDeleteOtherS2iBinary(t *testing.T) {
f := newFixture(t)
s2iBinary := newS2iBinary("test")
s2iRun := newDeletetingS2iRun("test", s2iBinary.Name)
otherS2iBinary := newS2iBinaryWithCreateTime("test2", metav1.NewTime(time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)))
f.s2ibinaryLister = append(f.s2ibinaryLister, s2iBinary)
f.s2ibinaryLister = append(f.s2ibinaryLister, otherS2iBinary)
f.s2irunLister = append(f.s2irunLister, s2iRun)
f.objects = append(f.objects, s2iBinary)
f.objects = append(f.objects, s2iRun)
f.objects = append(f.objects, otherS2iBinary)
f.expectDeleteS2iBinaryAction(s2iBinary)
f.expectDeleteS2iBinaryAction(otherS2iBinary)
f.expectUpdateS2iRunAction(s2iRun)
f.run(getKey(s2iRun, t))
}

View File

@@ -1,908 +0,0 @@
/*
Copyright 2020 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 (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"github.com/emicklei/go-restful"
"k8s.io/apiserver/pkg/authentication/user"
log "k8s.io/klog"
"k8s.io/klog/v2"
"kubesphere.io/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/server/params"
clientDevOps "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
)
const jenkinsHeaderPre = "X-"
func (h *ProjectPipelineHandler) GetPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
res, err := h.devopsOperator.GetPipeline(projectName, pipelineName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) getPipelinesByRequest(req *restful.Request) (api.ListResult, error) {
// this is a very trick way, but don't have a better solution for now
var (
start int
limit int
namespace string
)
// parse query from the request
pipelineFilter, namespace := parseNameFilterFromQuery(req.QueryParameter("q"))
// make sure we have an appropriate value
limit, start = params.ParsePaging(req)
return h.devopsOperator.ListPipelineObj(namespace, pipelineFilter, func(list []*v1alpha3.Pipeline, i int, j int) bool {
return strings.Compare(strings.ToUpper(list[i].Name), strings.ToUpper(list[j].Name)) < 0
}, limit, start)
}
func parseNameFilterFromQuery(query string) (filter devops.PipelineFilter, namespace string) {
var (
nameFilter string
)
for _, val := range strings.Split(query, ";") {
if strings.HasPrefix(val, "pipeline:") {
nsAndName := strings.TrimPrefix(val, "pipeline:")
filterMeta := strings.Split(nsAndName, "/")
if len(filterMeta) >= 2 {
namespace = filterMeta[0]
nameFilter = filterMeta[1] // the format is '*keyword*'
nameFilter = strings.TrimSuffix(nameFilter, "*")
nameFilter = strings.TrimPrefix(nameFilter, "*")
} else if len(filterMeta) > 0 {
namespace = filterMeta[0]
}
}
}
filter = func(pipeline *v1alpha3.Pipeline) bool {
return strings.Contains(pipeline.Name, nameFilter)
}
if nameFilter == "" {
filter = nil
}
return
}
func (h *ProjectPipelineHandler) ListPipelines(req *restful.Request, resp *restful.Response) {
objs, err := h.getPipelinesByRequest(req)
if err != nil {
parseErr(err, resp)
return
}
// get all pipelines which come from ks
pipelineList := &clientDevOps.PipelineList{
Total: objs.TotalItems,
Items: make([]clientDevOps.Pipeline, len(objs.Items)),
}
pipelineMap := make(map[string]int, pipelineList.Total)
for i := range objs.Items {
if pipeline, ok := objs.Items[i].(v1alpha3.Pipeline); !ok {
continue
} else {
pipelineMap[pipeline.Name] = i
pipelineList.Items[i] = clientDevOps.Pipeline{
Name: pipeline.Name,
Annotations: pipeline.Annotations,
}
}
}
// get all pipelines which come from Jenkins
// fill out the rest fields
if query, err := jenkins.ParseJenkinsQuery(req.Request.URL.RawQuery); err == nil {
query.Set("limit", "10000")
query.Set("start", "0")
req.Request.URL.RawQuery = query.Encode()
}
res, err := h.devopsOperator.ListPipelines(req.Request)
if err != nil {
log.Error(err)
} else {
for i := range res.Items {
if index, ok := pipelineMap[res.Items[i].Name]; ok {
// keep annotations field of pipelineList
annotations := pipelineList.Items[index].Annotations
pipelineList.Items[index] = res.Items[i]
pipelineList.Items[index].Annotations = annotations
}
}
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(pipelineList)
}
func (h *ProjectPipelineHandler) GetPipelineRun(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetPipelineRun(projectName, pipelineName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) ListPipelineRuns(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
res, err := h.devopsOperator.ListPipelineRuns(projectName, pipelineName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) StopPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
res, err := h.devopsOperator.StopPipeline(projectName, pipelineName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) ReplayPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
res, err := h.devopsOperator.ReplayPipeline(projectName, pipelineName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) RunPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
res, err := h.devopsOperator.RunPipeline(projectName, pipelineName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetArtifacts(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetArtifacts(projectName, pipelineName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetRunLog(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetRunLog(projectName, pipelineName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) GetStepLog(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
nodeId := req.PathParameter("node")
stepId := req.PathParameter("step")
res, header, err := h.devopsOperator.GetStepLog(projectName, pipelineName, runId, nodeId, stepId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
for k, v := range header {
if strings.HasPrefix(k, jenkinsHeaderPre) {
resp.AddHeader(k, v[0])
}
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) GetNodeSteps(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
nodeId := req.PathParameter("node")
res, err := h.devopsOperator.GetNodeSteps(projectName, pipelineName, runId, nodeId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetPipelineRunNodes(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetPipelineRunNodes(projectName, pipelineName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
// there're two situation here:
// 1. the particular submitters exist
// the users who are the owner of this Pipeline or the submitter of this Pipeline, or has the auth to create a DevOps project
// 2. no particular submitters
// only the owner of this Pipeline can approve or reject it
func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetail, pipe pipelineParam) {
var userInfo user.Info
var ok bool
var isAdmin bool
// check if current user belong to the admin group, grant it if it's true
if userInfo, ok = request.UserFrom(pipe.Context); ok {
createAuth := authorizer.AttributesRecord{
User: userInfo,
Verb: authorizer.VerbDelete,
Workspace: pipe.Workspace,
DevOps: pipe.ProjectName,
Resource: "devopsprojects",
ResourceRequest: true,
ResourceScope: request.DevOpsScope,
}
if decision, _, err := h.authorizer.Authorize(createAuth); err == nil {
isAdmin = decision == authorizer.DecisionAllow
} else {
// this is an expected case, printing the debug info for troubleshooting
klog.V(8).Infof("authorize failed with '%v', error is '%v'",
createAuth, err)
}
} else {
klog.V(6).Infof("cannot get the current user when checking the approvable with pipeline '%s/%s'",
pipe.ProjectName, pipe.Name)
return
}
var createdByCurrentUser bool // indicate if the current user is the owner
if pipeline, err := h.devopsOperator.GetPipelineObj(pipe.ProjectName, pipe.Name); err == nil {
if creator, ok := pipeline.GetAnnotations()[constants.CreatorAnnotationKey]; ok {
createdByCurrentUser = userInfo.GetName() == creator
} else {
klog.V(6).Infof("annotation '%s' is necessary but it is missing from '%s/%s'",
constants.CreatorAnnotationKey, pipe.ProjectName, pipe.Name)
}
} else {
klog.V(6).Infof("cannot find pipeline '%s/%s', error is '%v'", pipe.ProjectName, pipe.Name, err)
return
}
// check every input steps if it's approvable
for i, node := range nodes {
if node.State != clientDevOps.StatePaused {
continue
}
for j, step := range node.Steps {
if step.State != clientDevOps.StatePaused || step.Input == nil {
continue
}
nodes[i].Steps[j].Approvable = isAdmin || createdByCurrentUser || step.Input.Approvable(userInfo.GetName())
}
}
}
func (h *ProjectPipelineHandler) createdBy(projectName string, pipelineName string, currentUserName string) bool {
if pipeline, err := h.devopsOperator.GetPipelineObj(projectName, pipelineName); err == nil {
if creator, ok := pipeline.Annotations[constants.CreatorAnnotationKey]; ok {
return creator == currentUserName
}
} else {
log.V(4).Infof("cannot get pipeline %s/%s, error %#v", projectName, pipelineName, err)
}
return false
}
func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasPermit bool, err error) {
pipeParam := parsePipelineParam(req)
httpReq := &http.Request{
URL: req.Request.URL,
Header: req.Request.Header,
Form: req.Request.Form,
PostForm: req.Request.PostForm,
}
runId := req.PathParameter("run")
nodeId := req.PathParameter("node")
stepId := req.PathParameter("step")
branchName := req.PathParameter("branch")
// check if current user can approve this input
var res []clientDevOps.NodesDetail
if branchName == "" {
res, err = h.devopsOperator.GetNodesDetail(pipeParam.ProjectName, pipeParam.Name, runId, httpReq)
} else {
res, err = h.devopsOperator.GetBranchNodesDetail(pipeParam.ProjectName, pipeParam.Name, branchName, runId, httpReq)
}
if err == nil {
h.approvableCheck(res, parsePipelineParam(req))
for _, node := range res {
if node.ID != nodeId {
continue
}
for _, step := range node.Steps {
if step.ID != stepId || step.Input == nil {
continue
}
hasPermit = step.Approvable
break
}
break
}
} else {
log.V(4).Infof("cannot get nodes detail, error: %v", err)
err = errors.New("cannot get the submitters of current pipeline run")
return
}
return
}
func (h *ProjectPipelineHandler) SubmitInputStep(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
nodeId := req.PathParameter("node")
stepId := req.PathParameter("step")
var response []byte
var err error
var ok bool
if ok, err = h.hasSubmitPermission(req); !ok || err != nil {
msg := map[string]string{
"allow": "false",
"message": fmt.Sprintf("%v", err),
}
response, _ = json.Marshal(msg)
} else {
response, err = h.devopsOperator.SubmitInputStep(projectName, pipelineName, runId, nodeId, stepId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
}
resp.Write(response)
}
func (h *ProjectPipelineHandler) GetNodesDetail(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetNodesDetail(projectName, pipelineName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
h.approvableCheck(res, parsePipelineParam(req))
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetBranchPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
res, err := h.devopsOperator.GetBranchPipeline(projectName, pipelineName, branchName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetBranchPipelineRun(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetBranchPipelineRun(projectName, pipelineName, branchName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) StopBranchPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
res, err := h.devopsOperator.StopBranchPipeline(projectName, pipelineName, branchName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) ReplayBranchPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
res, err := h.devopsOperator.ReplayBranchPipeline(projectName, pipelineName, branchName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) RunBranchPipeline(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
res, err := h.devopsOperator.RunBranchPipeline(projectName, pipelineName, branchName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetBranchArtifacts(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetBranchArtifacts(projectName, pipelineName, branchName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetBranchRunLog(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetBranchRunLog(projectName, pipelineName, branchName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) GetBranchStepLog(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
nodeId := req.PathParameter("node")
stepId := req.PathParameter("step")
res, header, err := h.devopsOperator.GetBranchStepLog(projectName, pipelineName, branchName, runId, nodeId, stepId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
for k, v := range header {
if strings.HasPrefix(k, jenkinsHeaderPre) {
resp.AddHeader(k, v[0])
}
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) GetBranchNodeSteps(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
nodeId := req.PathParameter("node")
res, err := h.devopsOperator.GetBranchNodeSteps(projectName, pipelineName, branchName, runId, nodeId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetBranchPipelineRunNodes(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetBranchPipelineRunNodes(projectName, pipelineName, branchName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) SubmitBranchInputStep(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
nodeId := req.PathParameter("node")
stepId := req.PathParameter("step")
var response []byte
var err error
var ok bool
if ok, err = h.hasSubmitPermission(req); !ok || err != nil {
msg := map[string]string{
"allow": "false",
"message": fmt.Sprintf("%v", err),
}
response, _ = json.Marshal(msg)
} else {
response, err = h.devopsOperator.SubmitBranchInputStep(projectName, pipelineName, branchName, runId, nodeId, stepId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
}
resp.Write(response)
}
func (h *ProjectPipelineHandler) GetBranchNodesDetail(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
branchName := req.PathParameter("branch")
runId := req.PathParameter("run")
res, err := h.devopsOperator.GetBranchNodesDetail(projectName, pipelineName, branchName, runId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
h.approvableCheck(res, parsePipelineParam(req))
resp.WriteAsJson(res)
}
func parsePipelineParam(req *restful.Request) pipelineParam {
return pipelineParam{
Workspace: req.PathParameter("workspace"),
ProjectName: req.PathParameter("devops"),
Name: req.PathParameter("pipeline"),
Context: req.Request.Context(),
}
}
func (h *ProjectPipelineHandler) GetPipelineBranch(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
res, err := h.devopsOperator.GetPipelineBranch(projectName, pipelineName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) ScanBranch(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
res, err := h.devopsOperator.ScanBranch(projectName, pipelineName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) GetConsoleLog(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
res, err := h.devopsOperator.GetConsoleLog(projectName, pipelineName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) GetCrumb(req *restful.Request, resp *restful.Response) {
res, err := h.devopsOperator.GetCrumb(req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetSCMServers(req *restful.Request, resp *restful.Response) {
scmId := req.PathParameter("scm")
res, err := h.devopsOperator.GetSCMServers(scmId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetSCMOrg(req *restful.Request, resp *restful.Response) {
scmId := req.PathParameter("scm")
res, err := h.devopsOperator.GetSCMOrg(scmId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetOrgRepo(req *restful.Request, resp *restful.Response) {
scmId := req.PathParameter("scm")
organizationId := req.PathParameter("organization")
res, err := h.devopsOperator.GetOrgRepo(scmId, organizationId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) CreateSCMServers(req *restful.Request, resp *restful.Response) {
scmId := req.PathParameter("scm")
res, err := h.devopsOperator.CreateSCMServers(scmId, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) Validate(req *restful.Request, resp *restful.Response) {
scmId := req.PathParameter("scm")
res, err := h.devopsOperator.Validate(scmId, req.Request)
if err != nil {
log.Error(err)
if jErr, ok := err.(*devops.JkError); ok {
if jErr.Code != http.StatusUnauthorized {
resp.WriteError(jErr.Code, err)
} else {
resp.WriteHeader(http.StatusPreconditionRequired)
}
} else {
resp.WriteError(http.StatusInternalServerError, err)
}
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetNotifyCommit(req *restful.Request, resp *restful.Response) {
res, err := h.devopsOperator.GetNotifyCommit(req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) PostNotifyCommit(req *restful.Request, resp *restful.Response) {
res, err := h.devopsOperator.GetNotifyCommit(req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) GithubWebhook(req *restful.Request, resp *restful.Response) {
res, err := h.devopsOperator.GithubWebhook(req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Write(res)
}
func (h *ProjectPipelineHandler) CheckScriptCompile(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
pipelineName := req.PathParameter("pipeline")
resBody, err := h.devopsOperator.CheckScriptCompile(projectName, pipelineName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.WriteAsJson(resBody)
}
func (h *ProjectPipelineHandler) CheckCron(req *restful.Request, resp *restful.Response) {
projectName := req.PathParameter("devops")
res, err := h.devopsOperator.CheckCron(projectName, req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) ToJenkinsfile(req *restful.Request, resp *restful.Response) {
res, err := h.devopsOperator.ToJenkinsfile(req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) ToJson(req *restful.Request, resp *restful.Response) {
res, err := h.devopsOperator.ToJson(req.Request)
if err != nil {
parseErr(err, resp)
return
}
resp.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON)
resp.WriteAsJson(res)
}
func (h *ProjectPipelineHandler) GetProjectCredentialUsage(req *restful.Request, resp *restful.Response) {
projectId := req.PathParameter("devops")
credentialId := req.PathParameter("credential")
response, err := h.projectCredentialGetter.GetProjectCredentialUsage(projectId, credentialId)
if err != nil {
log.Errorf("%+v", err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(response)
return
}
func parseErr(err error, resp *restful.Response) {
log.Error(err)
if jErr, ok := err.(*devops.JkError); ok {
resp.WriteError(jErr.Code, err)
} else {
resp.WriteError(http.StatusInternalServerError, err)
}
return
}

View File

@@ -1,62 +0,0 @@
package v1alpha2
import (
"strings"
"testing"
"kubesphere.io/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/models/devops"
)
func TestParseNameFilterFromQuery(t *testing.T) {
table := []struct {
query string
pipeline *v1alpha3.Pipeline
expectFilter devops.PipelineFilter
expectNamespace string
message string
}{{
query: "type:pipeline;organization:jenkins;pipeline:serverjkq4c/*",
pipeline: &v1alpha3.Pipeline{},
expectFilter: nil,
expectNamespace: "serverjkq4c",
message: "query all pipelines with filter *",
}, {
query: "type:pipeline;organization:jenkins;pipeline:cccc/*abc*",
pipeline: &v1alpha3.Pipeline{},
expectFilter: func(pipeline *v1alpha3.Pipeline) bool {
return strings.Contains(pipeline.Name, "abc")
},
expectNamespace: "cccc",
message: "query all pipelines with filter abc",
}, {
query: "type:pipeline;organization:jenkins;pipeline:pai-serverjkq4c/*",
pipeline: &v1alpha3.Pipeline{},
expectFilter: nil,
expectNamespace: "pai-serverjkq4c",
message: "query all pipelines with filter *",
}, {
query: "type:pipeline;organization:jenkins;pipeline:defdef",
pipeline: &v1alpha3.Pipeline{},
expectFilter: nil,
expectNamespace: "defdef",
message: "query all pipelines with filter *",
}}
for i, item := range table {
filter, ns := parseNameFilterFromQuery(item.query)
if item.expectFilter == nil {
if filter != nil {
t.Fatalf("invalid filter, index: %d, message: %s", i, item.message)
}
} else {
if filter == nil || filter(item.pipeline) != item.expectFilter(item.pipeline) {
t.Fatalf("invalid filter, index: %d, message: %s", i, item.message)
}
}
if ns != item.expectNamespace {
t.Fatalf("invalid namespace, index: %d, message: %s", i, item.message)
}
}
}

View File

@@ -1,56 +0,0 @@
/*
Copyright 2020 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 (
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/models/devops"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
)
type ProjectPipelineHandler struct {
devopsOperator devops.DevopsOperator
projectCredentialGetter devops.ProjectCredentialGetter
authorizer authorizer.Authorizer
}
type PipelineSonarHandler struct {
pipelineSonarGetter devops.PipelineSonarGetter
}
func NewProjectPipelineHandler(devopsClient devopsClient.Interface, ksInformers externalversions.SharedInformerFactory,
authorizer authorizer.Authorizer) ProjectPipelineHandler {
return ProjectPipelineHandler{
devopsOperator: devops.NewDevopsOperator(devopsClient, nil, nil, ksInformers, nil),
projectCredentialGetter: devops.NewProjectCredentialOperator(devopsClient),
authorizer: authorizer,
}
}
func NewPipelineSonarHandler(devopsClient devopsClient.Interface, sonarClient sonarqube.SonarInterface) PipelineSonarHandler {
return PipelineSonarHandler{
pipelineSonarGetter: devops.NewPipelineSonarGetter(devopsClient, sonarClient),
}
}
func NewS2iBinaryHandler(client versioned.Interface, informers externalversions.SharedInformerFactory, s3Client s3.Interface) S2iBinaryHandler {
return S2iBinaryHandler{devops.NewS2iBinaryUploader(client, informers, s3Client)}
}

View File

@@ -1,49 +0,0 @@
/*
Copyright 2020 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"
)
func (h PipelineSonarHandler) GetPipelineSonarStatusHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
pipelineId := request.PathParameter("pipeline")
sonarStatus, err := h.pipelineSonarGetter.GetPipelineSonar(projectId, pipelineId)
if err != nil {
klog.Errorf("%+v", err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(sonarStatus)
}
func (h PipelineSonarHandler) GetMultiBranchesPipelineSonarStatusHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
pipelineId := request.PathParameter("pipeline")
branchId := request.PathParameter("branch")
sonarStatus, err := h.pipelineSonarGetter.GetMultiBranchPipelineSonar(projectId, pipelineId, branchId)
if err != nil {
klog.Errorf("%+v", err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(sonarStatus)
}

View File

@@ -17,745 +17,23 @@ limitations under the License.
package v1alpha2
import (
"context"
"fmt"
"net/url"
"strings"
"github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/klog"
devopsv1alpha1 "kubesphere.io/api/devops/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
"net/http"
"kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/kapis/generic"
)
const (
GroupName = "devops.kubesphere.io"
RespOK = "ok"
)
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
func AddToContainer(container *restful.Container, ksInformers externalversions.SharedInformerFactory, devopsClient devops.Interface,
sonarqubeClient sonarqube.SonarInterface, ksClient versioned.Interface, s3Client s3.Interface, endpoint string,
authorizer authorizer.Authorizer) error {
ws := runtime.NewWebService(GroupVersion)
err := AddPipelineToWebService(ws, devopsClient, ksInformers, authorizer)
func AddToContainer(container *restful.Container, endpoint string) error {
proxy, err := generic.NewGenericProxy(endpoint, GroupVersion.Group, GroupVersion.Version)
if err != nil {
return err
}
err = AddSonarToWebService(ws, devopsClient, sonarqubeClient)
if err != nil {
return err
}
err = AddS2IToWebService(ws, ksClient, ksInformers, s3Client)
if err != nil {
return err
}
err = AddJenkinsToContainer(ws, devopsClient, endpoint)
if err != nil {
return err
}
container.Add(ws)
return nil
}
func AddPipelineToWebService(webservice *restful.WebService, devopsClient devops.Interface, ksInformers externalversions.SharedInformerFactory,
authorizer authorizer.Authorizer) error {
projectPipelineEnable := devopsClient != nil
if projectPipelineEnable {
projectPipelineHandler := NewProjectPipelineHandler(devopsClient, ksInformers, authorizer)
webservice.Route(webservice.GET("/devops/{devops}/credentials/{credential}/usage").
To(projectPipelineHandler.GetProjectCredentialUsage).
Doc("Get the specified credential usage of the DevOps project").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsCredentialTag}).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("credential", "credential's ID, e.g. dockerhub-id")).
Returns(http.StatusOK, RespOK, devops.Credential{}))
// match Jenkins api "/blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}"
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}").
To(projectPipelineHandler.GetPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get the specified pipeline of the DevOps project").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Returns(http.StatusOK, RespOK, devops.Pipeline{}).
Writes(devops.Pipeline{}))
// match Jenkins api: "jenkins_api/blue/rest/search"
webservice.Route(webservice.GET("/search").
To(projectPipelineHandler.ListPipelines).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Search DevOps resource. More info: https://github.com/jenkinsci/blueocean-plugin/tree/master/blueocean-rest#get-pipelines-across-organization").
Param(webservice.QueryParameter("q", "query pipelines, condition for filtering.").
Required(true).
DataFormat("q=%s")).
Param(webservice.QueryParameter("filter", "Filter some types of jobs. e.g. no-folderwill not get a job of type folder").
Required(false).
DataFormat("filter=%s")).
Param(webservice.QueryParameter("start", "the item number that the search starts from.").
Required(false).
DataFormat("start=%d")).
Param(webservice.QueryParameter("limit", "the limit item count of the search.").
Required(false).
DataFormat("limit=%d")).
Returns(http.StatusOK, RespOK, devops.PipelineList{}).
Writes(devops.PipelineList{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/runs/{run}/
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}").
To(projectPipelineHandler.GetPipelineRun).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get details in the specified pipeline activity.").
Param(webservice.PathParameter("devops", "the name of devops project")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Returns(http.StatusOK, RespOK, devops.PipelineRun{}).
Writes(devops.PipelineRun{}))
// match Jenkins api "/blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/runs/"
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs").
To(projectPipelineHandler.ListPipelineRuns).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get all runs of the specified pipeline").
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.QueryParameter("start", "the item number that the search starts from").
Required(false).
DataFormat("start=%d")).
Param(webservice.QueryParameter("limit", "the limit item count of the search").
Required(false).
DataFormat("limit=%d")).
Param(webservice.QueryParameter("branch", "the name of branch, same as repository branch, will be filtered by branch.").
Required(false).
DataFormat("branch=%s")).
Returns(http.StatusOK, RespOK, devops.PipelineRunList{}).
Writes(devops.PipelineRunList{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/runs/{run}/stop/
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/runs/{run}/stop").
To(projectPipelineHandler.StopPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Stop pipeline").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.QueryParameter("blocking", "stop and between each retries will sleep.").
Required(false).
DataFormat("blocking=%t").
DefaultValue("blocking=false")).
Param(webservice.QueryParameter("timeOutInSecs", "the time of stop and between each retries sleep.").
Required(false).
DataFormat("timeOutInSecs=%d").
DefaultValue("timeOutInSecs=10")).
Returns(http.StatusOK, RespOK, devops.StopPipeline{}).
Writes(devops.StopPipeline{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/runs/{run}/Replay/
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/runs/{run}/replay").
To(projectPipelineHandler.ReplayPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Replay pipeline").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Returns(http.StatusOK, RespOK, devops.ReplayPipeline{}).
Writes(devops.ReplayPipeline{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/runs/
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/runs").
To(projectPipelineHandler.RunPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Run pipeline.").
Reads(devops.RunPayload{}).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Returns(http.StatusOK, RespOK, devops.RunPipeline{}).
Writes(devops.RunPipeline{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/runs/{run}/artifacts
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}/artifacts").
To(projectPipelineHandler.GetArtifacts).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get all artifacts in the specified pipeline.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.QueryParameter("start", "the item number that the search starts from.").
Required(false).
DataFormat("start=%d")).
Param(webservice.QueryParameter("limit", "the limit item count of the search.").
Required(false).
DataFormat("limit=%d")).
Returns(http.StatusOK, "The filed of \"Url\" in response can download artifacts", []devops.Artifacts{}).
Writes([]devops.Artifacts{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/runs/{run}/log/?start=0
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}/log").
To(projectPipelineHandler.GetRunLog).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get run logs of the specified pipeline activity.").
Produces("text/plain; charset=utf-8").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.QueryParameter("start", "the item number that the search starts from.").
Required(false).
DataFormat("start=%d").
DefaultValue("start=0")))
// match "/blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/runs/{run}/nodes/{node}/steps/{step}/log/?start=0"
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}/nodes/{node}/steps/{step}/log").
To(projectPipelineHandler.GetStepLog).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get pipelines step log.").
Produces("text/plain; charset=utf-8").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.PathParameter("node", "pipeline node ID, the stage in pipeline.")).
Param(webservice.PathParameter("step", "pipeline step ID, the step in pipeline.")).
Param(webservice.QueryParameter("start", "the item number that the search starts from.").
Required(false).
DataFormat("start=%d").
DefaultValue("start=0")))
// match /blue/rest/organizations/jenkins/pipelines/%s/%s/runs/%s/nodes/%s/steps/?limit=
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}/nodes/{node}/steps").
To(projectPipelineHandler.GetNodeSteps).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get all steps in the specified node.").
Param(webservice.PathParameter("devops", "the name of devops project")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build")).
Param(webservice.PathParameter("node", "pipeline node ID, the stage in pipeline.")).
Returns(http.StatusOK, RespOK, []devops.NodeSteps{}).
Writes([]devops.NodeSteps{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/runs/{run}/nodes/?limit=10000
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}/nodes").
To(projectPipelineHandler.GetPipelineRunNodes).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get all nodes in the specified activity. node is the stage in the pipeline task").
Param(webservice.PathParameter("devops", "the name of devops project")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build")).
Returns(http.StatusOK, RespOK, []devops.PipelineRunNodes{}).
Writes([]devops.PipelineRunNodes{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/runs/{run}/nodes/{node}/steps/{step}
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/runs/{run}/nodes/{node}/steps/{step}").
To(projectPipelineHandler.SubmitInputStep).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Proceed or Break the paused pipeline which is waiting for user input.").
Reads(devops.CheckPlayload{}).
Produces("text/plain; charset=utf-8").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.PathParameter("node", "pipeline node ID, the stage in pipeline.")).
Param(webservice.PathParameter("step", "pipeline step ID")))
// out of scm get all steps in nodes.
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}/nodesdetail").
To(projectPipelineHandler.GetNodesDetail).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get steps details inside a activity node. For a node, the steps which defined inside the node.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Returns(http.StatusOK, RespOK, []devops.NodesDetail{}).
Writes(devops.NodesDetail{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/branches/{branch}
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}").
To(projectPipelineHandler.GetBranchPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get the specified branch pipeline of the DevOps project").
Param(webservice.PathParameter("devops", "the name of devops project")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch")).
Returns(http.StatusOK, RespOK, devops.BranchPipeline{}).
Writes(devops.BranchPipeline{}))
// match Jenkins api "/blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/branches/{branch}/runs/{run}/"
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}").
To(projectPipelineHandler.GetBranchPipelineRun).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get details in the specified pipeline activity.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run id, the unique id for a pipeline once build.")).
Returns(http.StatusOK, RespOK, devops.PipelineRun{}).
Writes(devops.PipelineRun{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/stop/
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/stop").
To(projectPipelineHandler.StopBranchPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Stop the specified pipeline of the DevOps project.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.QueryParameter("blocking", "stop and between each retries will sleep.").
Required(false).
DataFormat("blocking=%t").
DefaultValue("blocking=false")).
Param(webservice.QueryParameter("timeOutInSecs", "the time of stop and between each retries sleep.").
Required(false).
DataFormat("timeOutInSecs=%d").
DefaultValue("timeOutInSecs=10")).
Returns(http.StatusOK, RespOK, devops.StopPipeline{}).
Writes(devops.StopPipeline{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/Replay/
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/replay").
To(projectPipelineHandler.ReplayBranchPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Replay the specified pipeline of the DevOps project").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Returns(http.StatusOK, RespOK, devops.ReplayPipeline{}).
Writes(devops.ReplayPipeline{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/branches/{}/runs/
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs").
To(projectPipelineHandler.RunBranchPipeline).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Run the specified pipeline of the DevOps project.").
Reads(devops.RunPayload{}).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Returns(http.StatusOK, RespOK, devops.RunPipeline{}).
Writes(devops.RunPipeline{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/branches/{branch}/runs/{run}/artifacts
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/artifacts").
To(projectPipelineHandler.GetBranchArtifacts).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get all artifacts generated from the specified run of the pipeline branch.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.QueryParameter("start", "the item number that the search starts from.").
Required(false).
DataFormat("start=%d")).
Param(webservice.QueryParameter("limit", "the limit item count of the search.").
Required(false).
DataFormat("limit=%d")).
Returns(http.StatusOK, "The filed of \"Url\" in response can download artifacts", []devops.Artifacts{}).
Writes([]devops.Artifacts{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/branches/{branch}/runs/{run}/log/?start=0
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/log").
To(projectPipelineHandler.GetBranchRunLog).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get run logs of the specified pipeline activity.").
Produces("text/plain; charset=utf-8").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.QueryParameter("start", "the item number that the search starts from.").
Required(false).
DataFormat("start=%d").
DefaultValue("start=0")))
// match "/blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/branches/{branch}/runs/{run}/nodes/{node}/steps/{step}/log/?start=0"
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/nodes/{node}/steps/{step}/log").
To(projectPipelineHandler.GetBranchStepLog).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get the step logs in the specified pipeline activity.").
Produces("text/plain; charset=utf-8").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run id, the unique id for a pipeline once build.")).
Param(webservice.PathParameter("node", "pipeline node id, the stage in pipeline.")).
Param(webservice.PathParameter("step", "pipeline step id, the step in pipeline.")).
Param(webservice.QueryParameter("start", "the item number that the search starts from.").
Required(false).
DataFormat("start=%d").
DefaultValue("start=0")))
// match /blue/rest/organizations/jenkins/pipelines/%s/%s/branches/%s/runs/%s/nodes/%s/steps/?limit=
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/nodes/{node}/steps").
To(projectPipelineHandler.GetBranchNodeSteps).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get all steps in the specified node.").
Param(webservice.PathParameter("devops", "the name of devops project")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.PathParameter("node", "pipeline node ID, the stage in pipeline.")).
Returns(http.StatusOK, RespOK, []devops.NodeSteps{}).
Writes([]devops.NodeSteps{}))
// match Jenkins api "/blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/branches/{branch}/runs/{run}/nodes"
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/nodes").
To(projectPipelineHandler.GetBranchPipelineRunNodes).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get run nodes.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run id, the unique id for a pipeline once build.")).
Param(webservice.QueryParameter("limit", "the limit item count of the search.").
Required(false).
DataFormat("limit=%d").
DefaultValue("limit=10000")).
Returns(http.StatusOK, RespOK, []devops.BranchPipelineRunNodes{}).
Writes([]devops.BranchPipelineRunNodes{}))
// /blue/rest/organizations/jenkins/pipelines/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/nodes/{node}/steps/{step}
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/nodes/{node}/steps/{step}").
To(projectPipelineHandler.SubmitBranchInputStep).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Proceed or Break the paused pipeline which waiting for user input.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Param(webservice.PathParameter("node", "pipeline node ID, the stage in pipeline.")).
Param(webservice.PathParameter("step", "pipeline step ID, the step in pipeline.")).
Reads(devops.CheckPlayload{}).
Produces("text/plain; charset=utf-8"))
// in scm get all steps in nodes.
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/runs/{run}/nodesdetail").
To(projectPipelineHandler.GetBranchNodesDetail).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get steps details in an activity node. For a node, the steps which is defined inside the node.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.PathParameter("branch", "the name of branch, same as repository branch.")).
Param(webservice.PathParameter("run", "pipeline run ID, the unique ID for a pipeline once build.")).
Returns(http.StatusOK, RespOK, []devops.NodesDetail{}).
Writes(devops.NodesDetail{}))
// match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/branches/?filter=&start&limit=
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches").
To(projectPipelineHandler.GetPipelineBranch).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("(MultiBranchesPipeline) Get all branches in the specified pipeline.").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.QueryParameter("filter", "filter remote scm. e.g. origin").
Required(false).
DataFormat("filter=%s")).
Param(webservice.QueryParameter("start", "the count of branches start.").
Required(false).
DataFormat("start=%d").DefaultValue("start=0")).
Param(webservice.QueryParameter("limit", "the count of branches limit.").
Required(false).
DataFormat("limit=%d").DefaultValue("limit=100")).
Returns(http.StatusOK, RespOK, []devops.PipelineBranch{}).
Writes([]devops.PipelineBranch{}))
// match /job/{devops}/job/{pipeline}/build?delay=0
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/scan").
To(projectPipelineHandler.ScanBranch).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Scan remote Repository, Start a build if have new branch.").
Produces("text/html; charset=utf-8").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")).
Param(webservice.QueryParameter("delay", "the delay time to scan").Required(false).DataFormat("delay=%d")))
// match /job/project-8QnvykoJw4wZ/job/test-1/indexing/consoleText
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/consolelog").
To(projectPipelineHandler.GetConsoleLog).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get scan reponsitory logs in the specified pipeline.").
Produces("text/plain; charset=utf-8").
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")))
// match /crumbIssuer/api/json/
webservice.Route(webservice.GET("/crumbissuer").
To(projectPipelineHandler.GetCrumb).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Doc("Get crumb issuer. A CrumbIssuer represents an algorithm to generate a nonce value, known as a crumb, to counter cross site request forgery exploits. Crumbs are typically hashes incorporating information that uniquely identifies an agent that sends a request, along with a guarded secret so that the crumb value cannot be forged by a third party.").
Returns(http.StatusOK, RespOK, devops.Crumb{}).
Writes(devops.Crumb{}))
// match "/blue/rest/organizations/jenkins/scm/%s/servers/"
webservice.Route(webservice.GET("/scms/{scm}/servers").
To(projectPipelineHandler.GetSCMServers).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsScmTag}).
Doc("List all servers in the jenkins.").
Param(webservice.PathParameter("scm", "The ID of the source configuration management (SCM).")).
Returns(http.StatusOK, RespOK, []devops.SCMServer{}).
Writes([]devops.SCMServer{}))
// match "/blue/rest/organizations/jenkins/scm/{scm}/organizations/?credentialId=github"
webservice.Route(webservice.GET("/scms/{scm}/organizations").
To(projectPipelineHandler.GetSCMOrg).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsScmTag}).
Doc("List all organizations of the specified source configuration management (SCM) such as Github.").
Param(webservice.PathParameter("scm", "the ID of the source configuration management (SCM).")).
Param(webservice.QueryParameter("credentialId", "credential ID for source configuration management (SCM).").Required(true).DataFormat("credentialId=%s")).
Returns(http.StatusOK, RespOK, []devops.SCMOrg{}).
Writes([]devops.SCMOrg{}))
// match "/blue/rest/organizations/jenkins/scm/{scm}/organizations/{organization}/repositories/?credentialId=&pageNumber&pageSize="
webservice.Route(webservice.GET("/scms/{scm}/organizations/{organization}/repositories").
To(projectPipelineHandler.GetOrgRepo).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsScmTag}).
Doc("List all repositories in the specified organization.").
Param(webservice.PathParameter("scm", "The ID of the source configuration management (SCM).")).
Param(webservice.PathParameter("organization", "organization ID, such as github username.")).
Param(webservice.QueryParameter("credentialId", "credential ID for SCM.").Required(true).DataFormat("credentialId=%s")).
Param(webservice.QueryParameter("pageNumber", "page number.").Required(true).DataFormat("pageNumber=%d")).
Param(webservice.QueryParameter("pageSize", "the item count of one page.").Required(true).DataFormat("pageSize=%d")).
Returns(http.StatusOK, RespOK, devops.OrgRepo{}).
Writes(devops.OrgRepo{}))
// match "/blue/rest/organizations/jenkins/scm/%s/servers/" create bitbucket server
webservice.Route(webservice.POST("/scms/{scm}/servers").
To(projectPipelineHandler.CreateSCMServers).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsScmTag}).
Doc("Create scm server if it does not exist in the Jenkins.").
Param(webservice.PathParameter("scm", "The ID of the source configuration management (SCM).")).
Reads(devops.CreateScmServerReq{}).
Returns(http.StatusOK, RespOK, devops.SCMServer{}).
Writes(devops.SCMServer{}))
// match "/blue/rest/organizations/jenkins/scm/github/validate/"
webservice.Route(webservice.POST("/scms/{scm}/verify").
To(projectPipelineHandler.Validate).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsScmTag}).
Doc("Validate the access token of the specified source configuration management (SCM) such as Github").
Param(webservice.PathParameter("scm", "the ID of the source configuration management (SCM).")).
Returns(http.StatusOK, RespOK, devops.Validates{}).
Writes(devops.Validates{}))
// match /git/notifyCommit/?url=
webservice.Route(webservice.GET("/webhook/git").
To(projectPipelineHandler.GetNotifyCommit).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsWebhookTag}).
Doc("Get commit notification by HTTP GET method. Git webhook will request here.").
Produces("text/plain; charset=utf-8").
Param(webservice.QueryParameter("url", "Git url").Required(true).DataFormat("url=%s")))
// Gitlab or some other scm managers can only use HTTP method. match /git/notifyCommit/?url=
webservice.Route(webservice.POST("/webhook/git").
To(projectPipelineHandler.PostNotifyCommit).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsWebhookTag}).
Doc("Get commit notification by HTTP POST method. Git webhook will request here.").
Consumes("application/json").
Produces("text/plain; charset=utf-8").
Param(webservice.QueryParameter("url", "Git url").Required(true).DataFormat("url=%s")))
webservice.Route(webservice.POST("/webhook/github").
To(projectPipelineHandler.GithubWebhook).
Consumes("application/x-www-form-urlencoded", "application/json").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsWebhookTag}).
Doc("Get commit notification. Github webhook will request here."))
webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/checkScriptCompile").
To(projectPipelineHandler.CheckScriptCompile).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline").DataFormat("pipeline=%s")).
Consumes("application/x-www-form-urlencoded", "charset=utf-8").
Produces("application/json", "charset=utf-8").
Doc("Check pipeline script compile.").
Reads(devops.ReqScript{}).
Returns(http.StatusOK, RespOK, devops.CheckScript{}).
Writes(devops.CheckScript{}))
webservice.Route(webservice.POST("/devops/{devops}/checkCron").
To(projectPipelineHandler.CheckCron).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Produces("application/json", "charset=utf-8").
Doc("Check cron script compile.").
Reads(devops.CronData{}).
Returns(http.StatusOK, RespOK, devops.CheckCronRes{}).
Writes(devops.CheckCronRes{}))
// match /pipeline-model-converter/toJenkinsfile
webservice.Route(webservice.POST("/tojenkinsfile").
To(projectPipelineHandler.ToJenkinsfile).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsJenkinsfileTag}).
Consumes("application/x-www-form-urlencoded").
Produces("application/json", "charset=utf-8").
Doc("Convert json to jenkinsfile format.").
Reads(devops.ReqJson{}).
Returns(http.StatusOK, RespOK, devops.ResJenkinsfile{}).
Writes(devops.ResJenkinsfile{}))
// match /pipeline-model-converter/toJson
/*
* Considering the following reasons, we use a generic data struct here.
* - A fixed go struct might need to change again once Jenkins has new features.
* - No refer requirement for the specific data struct
* Please read the official document if you want to know more details
* https://github.com/jenkinsci/pipeline-model-definition-plugin/blob/fc8d22192d7d3a17badc3b8af7191a84bb7fd4ca/EXTENDING.md#conversion-to-json-representation-from-jenkinsfile
*/
webservice.Route(webservice.POST("/tojson").
To(projectPipelineHandler.ToJson).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsJenkinsfileTag}).
Consumes("application/x-www-form-urlencoded").
Produces("application/json", "charset=utf-8").
Doc("Convert jenkinsfile to json format. Usually the frontend uses json to show or edit pipeline").
Reads(devops.ReqJenkinsfile{}).
Returns(http.StatusOK, RespOK, map[string]interface{}{}).
Writes(map[string]interface{}{}))
}
return nil
}
func AddSonarToWebService(webservice *restful.WebService, devopsClient devops.Interface, sonarClient sonarqube.SonarInterface) error {
sonarEnable := devopsClient != nil && sonarClient != nil
if sonarEnable {
sonarHandler := NewPipelineSonarHandler(devopsClient, sonarClient)
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/sonarstatus").
To(sonarHandler.GetPipelineSonarStatusHandler).
Doc("Get the sonar quality information for the specified pipeline of the DevOps project. More info: https://docs.sonarqube.org/7.4/user-guide/metric-definitions/").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of pipeline, e.g. sample-pipeline")).
Returns(http.StatusOK, RespOK, []sonarqube.SonarStatus{}).
Writes([]sonarqube.SonarStatus{}))
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/branches/{branch}/sonarstatus").
To(sonarHandler.GetMultiBranchesPipelineSonarStatusHandler).
Doc("Get the sonar quality check information for the specified pipeline branch of the DevOps project. More info: https://docs.sonarqube.org/7.4/user-guide/metric-definitions/").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
Param(webservice.PathParameter("pipeline", "the name of pipeline, e.g. sample-pipeline")).
Param(webservice.PathParameter("branch", "branch name, e.g. master")).
Returns(http.StatusOK, RespOK, []sonarqube.SonarStatus{}).
Writes([]sonarqube.SonarStatus{}))
}
return nil
}
func AddS2IToWebService(webservice *restful.WebService, ksClient versioned.Interface,
ksInformer externalversions.SharedInformerFactory, s3Client s3.Interface) error {
s2iEnable := ksClient != nil && ksInformer != nil && s3Client != nil
if s2iEnable {
s2iHandler := NewS2iBinaryHandler(ksClient, ksInformer, s3Client)
webservice.Route(webservice.PUT("/namespaces/{namespace}/s2ibinaries/{s2ibinary}/file").
To(s2iHandler.UploadS2iBinaryHandler).
Consumes("multipart/form-data").
Produces(restful.MIME_JSON).
Doc("Upload S2iBinary file").
Param(webservice.PathParameter("namespace", "the name of namespaces")).
Param(webservice.PathParameter("s2ibinary", "the name of s2ibinary")).
Param(webservice.FormParameter("s2ibinary", "file to upload")).
Param(webservice.FormParameter("md5", "md5 of file")).
Returns(http.StatusOK, RespOK, devopsv1alpha1.S2iBinary{}))
webservice.Route(webservice.GET("/namespaces/{namespace}/s2ibinaries/{s2ibinary}/file/{file}").
To(s2iHandler.DownloadS2iBinaryHandler).
Produces(restful.MIME_OCTET).
Doc("Download S2iBinary file").
Param(webservice.PathParameter("namespace", "the name of namespaces")).
Param(webservice.PathParameter("s2ibinary", "the name of s2ibinary")).
Param(webservice.PathParameter("file", "the name of binary file")).
Returns(http.StatusOK, RespOK, nil))
}
return nil
}
func AddJenkinsToContainer(webservice *restful.WebService, devopsClient devops.Interface, endpoint string) error {
if devopsClient == nil {
return nil
}
parse, err := url.Parse(endpoint)
if err != nil {
return err
}
parse.Path = strings.Trim(parse.Path, "/")
// this API does not belong any kind of auth scope, it should be removed in the future version
// see also pkg/apiserver/request/requestinfo.go
// Deprecated: Please use /devops/{devops}/jenkins/{path:*} instead
webservice.Route(webservice.GET("/jenkins/{path:*}").
Param(webservice.PathParameter("path", "Path stands for any suffix path.")).
To(func(request *restful.Request, response *restful.Response) {
u := request.Request.URL
u.Host = parse.Host
u.Scheme = parse.Scheme
jenkins.SetBasicBearTokenHeader(&request.Request.Header)
u.Path = strings.Replace(request.Request.URL.Path, fmt.Sprintf("/kapis/%s/%s/jenkins", GroupVersion.Group, GroupVersion.Version), "", 1)
httpProxy := proxy.NewUpgradeAwareHandler(u, http.DefaultTransport, false, false, &errorResponder{})
httpProxy.ServeHTTP(response, request.Request)
}).
Returns(http.StatusOK, RespOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsJenkinsTag}))
handlerWithDevOps := func(request *restful.Request, response *restful.Response) {
u := request.Request.URL
devops := request.PathParameter("devops")
u.Host = parse.Host
u.Scheme = parse.Scheme
jenkins.SetBasicBearTokenHeader(&request.Request.Header)
u.Path = strings.Replace(request.Request.URL.Path, fmt.Sprintf("/kapis/%s/%s/devops/%s/jenkins",
GroupVersion.Group, GroupVersion.Version, devops), "", 1)
httpProxy := proxy.NewUpgradeAwareHandler(u, http.DefaultTransport, false, false, &errorResponder{})
httpProxy.ServeHTTP(response, request.Request)
}
// some Jenkins API against with POST method
webservice.Route(webservice.GET("/devops/{devops}/jenkins/{path:*}").
Param(webservice.PathParameter("path", "Path stands for any suffix path.")).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
To(handlerWithDevOps).
Returns(http.StatusOK, RespOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsJenkinsTag}))
webservice.Route(webservice.POST("/devops/{devops}/jenkins/{path:*}").
Param(webservice.PathParameter("path", "Path stands for any suffix path.")).
Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")).
To(handlerWithDevOps).
Returns(http.StatusOK, RespOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsJenkinsTag}))
return nil
}
type pipelineParam struct {
Workspace string
ProjectName string
Name string
Context context.Context
}
type errorResponder struct{}
func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
klog.Error(err)
return proxy.AddToContainer(container)
}

View File

@@ -1,109 +0,0 @@
/*
Copyright 2020 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 (
"fmt"
"net/http"
"code.cloudfoundry.org/bytefmt"
"github.com/emicklei/go-restful"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/utils/hashutil"
)
type S2iBinaryHandler struct {
s2iUploader devops.S2iBinaryUploader
}
func (h S2iBinaryHandler) UploadS2iBinaryHandler(req *restful.Request, resp *restful.Response) {
ns := req.PathParameter("namespace")
name := req.PathParameter("s2ibinary")
err := req.Request.ParseMultipartForm(bytefmt.MEGABYTE * 20)
if err != nil {
klog.Errorf("%+v", err)
api.HandleBadRequest(resp, nil, err)
return
}
if len(req.Request.MultipartForm.File) == 0 {
err := restful.NewError(http.StatusBadRequest, "could not get file from form")
klog.Errorf("%+v", err)
api.HandleBadRequest(resp, nil, err)
return
}
if len(req.Request.MultipartForm.File["s2ibinary"]) == 0 {
err := restful.NewError(http.StatusBadRequest, "could not get file from form")
klog.Errorf("%+v", err)
api.HandleInternalError(resp, nil, err)
return
}
if len(req.Request.MultipartForm.File["s2ibinary"]) > 1 {
err := restful.NewError(http.StatusBadRequest, "s2ibinary should only have one file")
klog.Errorf("%+v", err)
api.HandleInternalError(resp, nil, err)
return
}
defer req.Request.MultipartForm.RemoveAll()
file, err := req.Request.MultipartForm.File["s2ibinary"][0].Open()
if err != nil {
klog.Error(err)
api.HandleInternalError(resp, nil, err)
return
}
filemd5, err := hashutil.GetMD5(file)
if err != nil {
klog.Error(err)
api.HandleInternalError(resp, nil, err)
return
}
md5, ok := req.Request.MultipartForm.Value["md5"]
if ok && len(req.Request.MultipartForm.Value["md5"]) > 0 {
if md5[0] != filemd5 {
err := restful.NewError(http.StatusBadRequest, fmt.Sprintf("md5 not match, origin: %+v, calculate: %+v", md5[0], filemd5))
klog.Error(err)
api.HandleInternalError(resp, nil, err)
return
}
}
s2ibin, err := h.s2iUploader.UploadS2iBinary(ns, name, filemd5, req.Request.MultipartForm.File["s2ibinary"][0])
if err != nil {
klog.Errorf("%+v", err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(s2ibin)
}
func (h S2iBinaryHandler) DownloadS2iBinaryHandler(req *restful.Request, resp *restful.Response) {
ns := req.PathParameter("namespace")
name := req.PathParameter("s2ibinary")
fileName := req.PathParameter("file")
url, err := h.s2iUploader.DownloadS2iBinary(ns, name, fileName)
if err != nil {
klog.Errorf("%+v", err)
api.HandleInternalError(resp, nil, err)
return
}
http.Redirect(resp.ResponseWriter, req.Request, url, http.StatusFound)
return
}

View File

@@ -1,383 +0,0 @@
/*
Copyright 2020 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 v1alpha3
import (
"github.com/emicklei/go-restful"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/klog"
"kubesphere.io/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/models/devops"
servererr "kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/server/params"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
)
type devopsHandler struct {
devops devops.DevopsOperator
}
func newDevOpsHandler(devopsClient devopsClient.Interface, k8sclient kubernetes.Interface, ksclient kubesphere.Interface,
ksInformers externalversions.SharedInformerFactory,
k8sInformers informers.SharedInformerFactory) *devopsHandler {
return &devopsHandler{
devops: devops.NewDevopsOperator(devopsClient, k8sclient, ksclient, ksInformers, k8sInformers),
}
}
// devopsproject handler about get/list/post/put/delete
func (h *devopsHandler) GetDevOpsProject(request *restful.Request, response *restful.Response) {
workspace := request.PathParameter("workspace")
devops := request.PathParameter("devops")
project, err := h.devops.GetDevOpsProject(workspace, devops)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(project)
}
func (h *devopsHandler) ListDevOpsProject(request *restful.Request, response *restful.Response) {
workspace := request.PathParameter("workspace")
limit, offset := params.ParsePaging(request)
projectList, err := h.devops.ListDevOpsProject(workspace, limit, offset)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(projectList)
}
func (h *devopsHandler) CreateDevOpsProject(request *restful.Request, response *restful.Response) {
workspace := request.PathParameter("workspace")
var devOpsProject v1alpha3.DevOpsProject
err := request.ReadEntity(&devOpsProject)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
return
}
created, err := h.devops.CreateDevOpsProject(workspace, &devOpsProject)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
} else if errors.IsConflict(err) {
api.HandleConflict(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(created)
}
func (h *devopsHandler) UpdateDevOpsProject(request *restful.Request, response *restful.Response) {
workspace := request.PathParameter("workspace")
var devOpsProject v1alpha3.DevOpsProject
err := request.ReadEntity(&devOpsProject)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
return
}
project, err := h.devops.UpdateDevOpsProject(workspace, &devOpsProject)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(project)
}
func (h *devopsHandler) DeleteDevOpsProject(request *restful.Request, response *restful.Response) {
workspace := request.PathParameter("workspace")
devops := request.PathParameter("devops")
err := h.devops.DeleteDevOpsProject(workspace, devops)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(servererr.None)
}
// pipeline handler about get/list/post/put/delete
func (h *devopsHandler) GetPipeline(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
pipeline := request.PathParameter("pipeline")
obj, err := h.devops.GetPipelineObj(devops, pipeline)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(obj)
}
func (h *devopsHandler) ListPipeline(request *restful.Request, response *restful.Response) {
devopsProject := request.PathParameter("devops")
limit, offset := params.ParsePaging(request)
objs, err := h.devops.ListPipelineObj(devopsProject, nil, nil, limit, offset)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(objs)
}
func (h *devopsHandler) CreatePipeline(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
var pipeline v1alpha3.Pipeline
err := request.ReadEntity(&pipeline)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
return
}
created, err := h.devops.CreatePipelineObj(devops, &pipeline)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(created)
}
func (h *devopsHandler) UpdatePipeline(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
var pipeline v1alpha3.Pipeline
err := request.ReadEntity(&pipeline)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
return
}
obj, err := h.devops.UpdatePipelineObj(devops, &pipeline)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(obj)
}
func (h *devopsHandler) DeletePipeline(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
pipeline := request.PathParameter("pipeline")
klog.V(8).Infof("ready to delete pipeline %s/%s", devops, pipeline)
err := h.devops.DeletePipelineObj(devops, pipeline)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(servererr.None)
}
//credential handler about get/list/post/put/delete
func (h *devopsHandler) GetCredential(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
credential := request.PathParameter("credential")
obj, err := h.devops.GetCredentialObj(devops, credential)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(obj)
}
func (h *devopsHandler) ListCredential(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
query := query.ParseQueryParameter(request)
objs, err := h.devops.ListCredentialObj(devops, query)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(objs)
}
func (h *devopsHandler) CreateCredential(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
var obj v1.Secret
err := request.ReadEntity(&obj)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
return
}
created, err := h.devops.CreateCredentialObj(devops, &obj)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(created)
}
func (h *devopsHandler) UpdateCredential(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
var obj v1.Secret
err := request.ReadEntity(&obj)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
return
}
updated, err := h.devops.UpdateCredentialObj(devops, &obj)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(updated)
}
func (h *devopsHandler) DeleteCredential(request *restful.Request, response *restful.Response) {
devops := request.PathParameter("devops")
credential := request.PathParameter("credential")
err := h.devops.DeleteCredentialObj(devops, credential)
if err != nil {
klog.Error(err)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
return
}
api.HandleBadRequest(response, request, err)
return
}
response.WriteEntity(servererr.None)
}

View File

@@ -19,173 +19,23 @@
package v1alpha3
import (
"net/http"
"github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"kubesphere.io/api/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/server/params"
devopsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/kapis/generic"
)
const (
GroupName = "devops.kubesphere.io"
RespOK = "ok"
)
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha3"}
func AddToContainer(container *restful.Container, devopsClient devopsClient.Interface,
k8sclient kubernetes.Interface, ksclient kubesphere.Interface,
ksInformers externalversions.SharedInformerFactory,
k8sInformers informers.SharedInformerFactory) error {
devopsEnable := devopsClient != nil
if devopsEnable {
ws := runtime.NewWebService(GroupVersion)
handler := newDevOpsHandler(devopsClient, k8sclient, ksclient, ksInformers, k8sInformers)
// credential
ws.Route(ws.GET("/devops/{devops}/credentials").
To(handler.ListCredential).
Param(ws.PathParameter("devops", "devops name")).
Param(ws.QueryParameter(query.ParameterName, "name used to do filtering").Required(false)).
Param(ws.QueryParameter(query.ParameterPage, "page").Required(false).DataFormat("page=%d").DefaultValue("page=1")).
Param(ws.QueryParameter(query.ParameterLimit, "limit").Required(false)).
Param(ws.QueryParameter(query.ParameterAscending, "sort parameters, e.g. ascending=false").Required(false).DefaultValue("ascending=false")).
Param(ws.QueryParameter(query.ParameterOrderBy, "sort parameters, e.g. orderBy=createTime")).
Doc("list the credentials of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{}}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.POST("/devops/{devops}/credentials").
To(handler.CreateCredential).
Param(ws.PathParameter("devops", "devops name")).
Doc("create the credential of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.Pipeline{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.GET("/devops/{devops}/credentials/{credential}").
To(handler.GetCredential).
Param(ws.PathParameter("devops", "project name")).
Param(ws.PathParameter("credential", "pipeline name")).
Doc("get the credential of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1.Secret{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.PUT("/devops/{devops}/credentials/{credential}").
To(handler.UpdateCredential).
Param(ws.PathParameter("devops", "project name")).
Param(ws.PathParameter("credential", "credential name")).
Doc("put the credential of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1.Secret{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.DELETE("/devops/{devops}/credentials/{credential}").
To(handler.DeleteCredential).
Param(ws.PathParameter("devops", "project name")).
Param(ws.PathParameter("credential", "credential name")).
Doc("delete the credential of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1.Secret{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}))
// pipeline
ws.Route(ws.GET("/devops/{devops}/pipelines").
To(handler.ListPipeline).
Param(ws.PathParameter("devops", "devops name")).
Param(ws.QueryParameter(params.PagingParam, "paging query, e.g. limit=100,page=1").
Required(false).
DataFormat("limit=%d,page=%d").
DefaultValue("limit=10,page=1")).
Doc("list the pipelines of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{}}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.POST("/devops/{devops}/pipelines").
To(handler.CreatePipeline).
Param(ws.PathParameter("devops", "devops name")).
Doc("create the pipeline of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.Pipeline{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.GET("/devops/{devops}/pipelines/{pipeline}").
To(handler.GetPipeline).
Operation("getPipelineByName").
Param(ws.PathParameter("devops", "project name")).
Param(ws.PathParameter("pipeline", "pipeline name")).
Doc("get the pipeline of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.Pipeline{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.PUT("/devops/{devops}/pipelines/{pipeline}").
To(handler.UpdatePipeline).
Param(ws.PathParameter("devops", "project name")).
Param(ws.PathParameter("pipeline", "pipeline name")).
Doc("put the pipeline of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.Pipeline{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.DELETE("/devops/{devops}/pipelines/{pipeline}").
To(handler.DeletePipeline).
Param(ws.PathParameter("devops", "project name")).
Param(ws.PathParameter("pipeline", "pipeline name")).
Doc("delete the pipeline of the specified devops for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.Pipeline{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}))
// devops
ws.Route(ws.GET("/workspaces/{workspace}/devops").
To(handler.ListDevOpsProject).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.QueryParameter(params.PagingParam, "paging query, e.g. limit=100,page=1").
Required(false).
DataFormat("limit=%d,page=%d").
DefaultValue("limit=10,page=1")).Doc("List the devopsproject of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, api.ListResult{Items: []interface{}{}}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.POST("/workspaces/{workspace}/devops").
To(handler.CreateDevOpsProject).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("Create the devopsproject of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.DevOpsProject{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.GET("/workspaces/{workspace}/devops/{devops}").
To(handler.GetDevOpsProject).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.PathParameter("devops", "project name")).
Doc("Get the devopsproject of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.DevOpsProject{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.PUT("/workspaces/{workspace}/devops/{devops}").
To(handler.UpdateDevOpsProject).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.PathParameter("devops", "project name")).
Doc("Put the devopsproject of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.DevOpsProject{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
ws.Route(ws.DELETE("/workspaces/{workspace}/devops/{devops}").
To(handler.DeleteDevOpsProject).
Param(ws.PathParameter("workspace", "workspace name")).
Param(ws.PathParameter("devops", "project name")).
Doc("Get the devopsproject of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, []v1alpha3.DevOpsProject{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsProjectTag}))
container.Add(ws)
func AddToContainer(container *restful.Container, endpoint string) error {
proxy, err := generic.NewGenericProxy(endpoint, GroupVersion.Group, GroupVersion.Version)
if err != nil {
return err
}
return nil
return proxy.AddToContainer(container)
}

View File

@@ -29,6 +29,7 @@ type Options struct {
Username string `json:",omitempty" yaml:"username" description:"Jenkins admin username"`
Password string `json:",omitempty" yaml:"password" description:"Jenkins admin password"`
MaxConnections int `json:"maxConnections,omitempty" yaml:"maxConnections" description:"Maximum connections allowed to connect to Jenkins"`
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint" description:"The endpoint of the ks-devops apiserver"`
}
// NewDevopsOptions returns a `zero` instance

View File

@@ -59,9 +59,7 @@ import (
terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/iam/group"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
fakedevops "kubesphere.io/kubesphere/pkg/simple/client/devops/fake"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
fakes3 "kubesphere.io/kubesphere/pkg/simple/client/s3/fake"
)
var output string
@@ -122,8 +120,8 @@ func generateSwaggerJson() []byte {
urlruntime.Must(oauth.AddToContainer(container, nil, nil, nil, nil, nil, nil))
urlruntime.Must(clusterkapisv1alpha1.AddToContainer(container, informerFactory.KubernetesSharedInformerFactory(),
informerFactory.KubeSphereSharedInformerFactory(), "", "", ""))
urlruntime.Must(devopsv1alpha2.AddToContainer(container, informerFactory.KubeSphereSharedInformerFactory(), &fakedevops.Devops{}, nil, clientsets.KubeSphere(), fakes3.NewFakeS3(), "", nil))
urlruntime.Must(devopsv1alpha3.AddToContainer(container, &fakedevops.Devops{}, clientsets.Kubernetes(), clientsets.KubeSphere(), informerFactory.KubeSphereSharedInformerFactory(), informerFactory.KubernetesSharedInformerFactory()))
urlruntime.Must(devopsv1alpha2.AddToContainer(container, ""))
urlruntime.Must(devopsv1alpha3.AddToContainer(container, ""))
urlruntime.Must(iamv1alpha2.AddToContainer(container, nil, nil, group.New(informerFactory, clientsets.KubeSphere(), clientsets.Kubernetes()), nil))
urlruntime.Must(monitoringv1alpha3.AddToContainer(container, clientsets.Kubernetes(), nil, nil, informerFactory, nil))
urlruntime.Must(openpitrixv1.AddToContainer(container, informerFactory, fake.NewSimpleClientset(), nil))