Merge pull request #4129 from LinuxSuRen/devops-proxy
Add proxy devops APIs request to ks-devops
This commit is contained in:
@@ -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
@@ -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())
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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, "")
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
approvers:
|
||||
- shaowenchen
|
||||
- linuxsuren
|
||||
|
||||
reviewers:
|
||||
- runzexia
|
||||
- soulseen
|
||||
- shaowenchen
|
||||
- linuxsuren
|
||||
|
||||
labels:
|
||||
- area/controller
|
||||
- area/devops
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
approvers:
|
||||
- shaowenchen
|
||||
- linuxsuren
|
||||
|
||||
reviewers:
|
||||
- runzexia
|
||||
- soulseen
|
||||
- shaowenchen
|
||||
- linuxsuren
|
||||
|
||||
labels:
|
||||
- area/controller
|
||||
- area/devops
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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-folder,will 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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user