diff --git a/.gitignore b/.gitignore index 39a879e5a..51ac57cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ Thumbs.db csrf_headers.txt /dashboard +/build-maker diff --git a/base/200-clusterrole-tenant.yaml b/base/200-clusterrole-tenant.yaml index 8d0fa445d..49ce70963 100644 --- a/base/200-clusterrole-tenant.yaml +++ b/base/200-clusterrole-tenant.yaml @@ -33,7 +33,20 @@ rules: - apiGroups: - dashboard.tekton.dev resources: + - builds + - builds/status - extensions + - projects + - projects/status + verbs: + - get + - list + - watch + - apiGroups: + - extensions + - apps + resources: + - ingresses verbs: - get - list diff --git a/base/202-build-crd.yaml b/base/202-build-crd.yaml new file mode 100644 index 000000000..e45bdf0f2 --- /dev/null +++ b/base/202-build-crd.yaml @@ -0,0 +1,59 @@ +# Copyright 2019 The Tekton 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. + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: builds.dashboard.tekton.dev + labels: + app.kubernetes.io/component: dashboard + app.kubernetes.io/instance: default + app.kubernetes.io/part-of: tekton-dashboard +spec: + group: dashboard.tekton.dev + versions: + - name: v1alpha1 + served: true + storage: true + preserveUnknownFields: false + validation: + openAPIV3Schema: + type: object + # One can use x-kubernetes-preserve-unknown-fields: true at the root + # of the schema (and inside any properties, additionalProperties) + # to get the traditional CRD behaviour that nothing is pruned, despite + # setting spec.preserveUnknownProperties: false. + # + # See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/ + # See issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + names: + kind: Build + plural: builds + categories: + - tekton + - tekton-dashboard + scope: Namespaced + # additionalPrinterColumns: + # - name: Repository URL + # type: string + # JSONPath: .spec.repositoryUrl + # - name: Age + # type: date + # JSONPath: .metadata.creationTimestamp + # Opt into the status subresource so metadata.generation + # starts to increment + subresources: + status: {} diff --git a/base/202-project-crd.yaml b/base/202-project-crd.yaml new file mode 100644 index 000000000..2ea337fa2 --- /dev/null +++ b/base/202-project-crd.yaml @@ -0,0 +1,64 @@ +# Copyright 2019 The Tekton 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. + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: projects.dashboard.tekton.dev + labels: + app.kubernetes.io/component: dashboard + app.kubernetes.io/instance: default + app.kubernetes.io/part-of: tekton-dashboard +spec: + group: dashboard.tekton.dev + versions: + - name: v1alpha1 + served: true + storage: true + preserveUnknownFields: false + validation: + openAPIV3Schema: + type: object + # One can use x-kubernetes-preserve-unknown-fields: true at the root + # of the schema (and inside any properties, additionalProperties) + # to get the traditional CRD behaviour that nothing is pruned, despite + # setting spec.preserveUnknownProperties: false. + # + # See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/ + # See issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + names: + kind: Project + plural: projects + categories: + - tekton + - tekton-dashboard + shortNames: + - project + scope: Namespaced + additionalPrinterColumns: + - name: Service account + type: string + JSONPath: .spec.serviceAccountName + - name: Ingress URL + type: string + JSONPath: .spec.ingress.host + - name: Age + type: date + JSONPath: .metadata.creationTimestamp + # Opt into the status subresource so metadata.generation + # starts to increment + subresources: + status: {} diff --git a/cmd/build-maker/Dockerfile b/cmd/build-maker/Dockerfile new file mode 100644 index 000000000..fd1ad50fc --- /dev/null +++ b/cmd/build-maker/Dockerfile @@ -0,0 +1,16 @@ + +FROM golang as goBuilder + +ARG GOARCH=amd64 + +USER root +WORKDIR /work +COPY . . +RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$GOARCH go build ./cmd/build-maker + +FROM alpine + +WORKDIR /work +COPY --from=goBuilder /work/build-maker . + +ENTRYPOINT ["./build-maker"] diff --git a/cmd/build-maker/main.go b/cmd/build-maker/main.go new file mode 100644 index 000000000..916456cad --- /dev/null +++ b/cmd/build-maker/main.go @@ -0,0 +1,147 @@ +/* +Copyright 2019-2020 The Tekton 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 main + +import ( + "os" + "strings" + + "github.com/spf13/pflag" + dashboardv1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardclientset "github.com/tektoncd/dashboard/pkg/client/clientset/versioned" + "github.com/tektoncd/dashboard/pkg/logging" + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + resourcev1alpha1 "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +var ( + kubeConfigPath = pflag.String("kube-config", "", "Path to kube config file") + file = pflag.String("file", "", "PipelineRun spec file") + namespace = pflag.String("namespace", "", "Target namespace") + ownerName = pflag.String("owner-name", "", "Owner project name") + ownerUid = pflag.String("owner-uid", "", "Owner project UID") + url = pflag.String("url", "", "Repository url") + revision = pflag.String("revision", "", "Revision") + logLevel = pflag.String("log-level", "info", "Minimum log level output by the logger") + logFormat = pflag.String("log-format", "json", "Format for log output (json or console)") + params = pflag.StringArray("param", nil, "Param int the form name=value") +) + +func main() { + pflag.Parse() + + logging.InitLogger(*logLevel, *logFormat) + + if *file == "" { + logging.Log.Panic("file must be provided") + } + + if *namespace == "" { + logging.Log.Panic("namespace must be provided") + } + + if *url == "" { + logging.Log.Panic("url must be provided") + } + + if *revision == "" { + logging.Log.Panic("revision must be provided") + } + + if f, err := os.Open(*file); err != nil { + logging.Log.Panicf("Error loading file (%s): %s", *file, err.Error()) + } else { + decoder := yaml.NewYAMLOrJSONDecoder(f, 4096) + + var pipelineSpec pipelinev1beta1.PipelineSpec + + if err := decoder.Decode(&pipelineSpec); err != nil { + logging.Log.Panicf("Error decoding file: %s", err.Error()) + } + + var cfg *rest.Config + + if *kubeConfigPath != "" { + cfg, err = clientcmd.BuildConfigFromFlags("", *kubeConfigPath) + if err != nil { + logging.Log.Panicf("Error building kubeconfig from %s: %s", *kubeConfigPath, err.Error()) + } + } else { + if cfg, err = rest.InClusterConfig(); err != nil { + logging.Log.Panicf("Error building kubeconfig: %s", err.Error()) + } + } + + dashboardClient, err := dashboardclientset.NewForConfig(cfg) + if err != nil { + logging.Log.Panicf("Error building dashboard clientset: %s", err.Error()) + } + + build := &dashboardv1alpha1.Build{ + Spec: dashboardv1alpha1.BuildSpec{ + PipelineSpec: pipelineSpec, + PipelineResourceSpec: resourcev1alpha1.PipelineResourceSpec{ + Type: "git", + Params: []resourcev1alpha1.ResourceParam{ + { + Name: "url", + Value: *url, + }, + { + Name: "revision", + Value: *revision, + }, + }, + }, + }, + } + + if params != nil { + for _, param := range *params { + nameValue := strings.Split(param, "=") + if len(nameValue) == 2 { + build.Spec.Params = append(build.Spec.Params, pipelinev1beta1.Param{ + Name: nameValue[0], + Value: pipelinev1beta1.NewArrayOrString(nameValue[1]), + }) + } + } + } + + build.GenerateName = "build-" + + if *ownerName != "" && *ownerUid != "" { + trueHelper := true + build.OwnerReferences = append(build.OwnerReferences, metav1.OwnerReference{ + APIVersion: "dashboard.tekton.dev/v1alpha1", + Kind: "Project", + Name: *ownerName, + UID: types.UID(*ownerUid), + Controller: &trueHelper, + BlockOwnerDeletion: &trueHelper, + }) + } + + if build, err := dashboardClient.DashboardV1alpha1().Builds(*namespace).Create(build); err != nil { + logging.Log.Panicf("Error creating build: %s", err.Error()) + } else { + logging.Log.Infof("Created build %s in namespace %s", build.Name, build.Namespace) + } + } +} diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index cea27b5be..b84592cfe 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -31,6 +31,7 @@ import ( "github.com/tektoncd/dashboard/pkg/router" pipelineclientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" resourceclientset "github.com/tektoncd/pipeline/pkg/client/resource/clientset/versioned" + triggersclientset "github.com/tektoncd/triggers/pkg/client/clientset/versioned" k8sclientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -102,6 +103,11 @@ func main() { logging.Log.Errorf("Error building pipeline clientset: %s", err.Error()) } + triggersClient, err := triggersclientset.NewForConfig(cfg) + if err != nil { + logging.Log.Errorf("Error building triggers clientset: %s", err.Error()) + } + dashboardClient, err := dashboardclientset.NewForConfig(cfg) if err != nil { logging.Log.Errorf("Error building dashboard clientset: %s", err.Error()) @@ -147,6 +153,7 @@ func main() { DashboardClient: dashboardClient, PipelineClient: pipelineClient, PipelineResourceClient: pipelineResourceClient, + TriggersClient: triggersClient, K8sClient: k8sClient, RouteClient: routeClient, Options: options, @@ -160,7 +167,9 @@ func main() { resyncDur := time.Second * 30 controllers.StartTektonControllers(resource.PipelineClient, resource.PipelineResourceClient, *tenantNamespace, resyncDur, ctx.Done()) controllers.StartKubeControllers(resource.K8sClient, resyncDur, *tenantNamespace, *readOnly, routerHandler, ctx.Done()) - controllers.StartDashboardControllers(resource.DashboardClient, resyncDur, *tenantNamespace, ctx.Done()) + controllers.StartDashboardControllers(resource.DashboardClient, resource.TriggersClient, resource.K8sClient, resyncDur, *tenantNamespace, ctx.Done()) + + controllers.StartRuntimeControllers(cfg) logging.Log.Infof("Creating server and entering wait loop") CSRF := csrf.Protect( diff --git a/go.mod b/go.mod index 5afb031a8..507d477a5 100644 --- a/go.mod +++ b/go.mod @@ -12,20 +12,21 @@ replace ( ) require ( - github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible github.com/emicklei/go-restful v2.12.0+incompatible github.com/google/go-cmp v0.4.0 github.com/gorilla/csrf v1.7.0 github.com/gorilla/websocket v1.4.2 github.com/openshift/api v3.9.0+incompatible // indirect github.com/openshift/client-go v0.0.0-20191125132246-f6563a70e19a + github.com/spf13/pflag v1.0.5 github.com/tektoncd/pipeline v0.12.1 github.com/tektoncd/plumbing v0.0.0-20200430135134-e53521e1d887 + github.com/tektoncd/triggers v0.6.0 go.uber.org/zap v1.15.0 - k8s.io/api v0.17.6 - k8s.io/apimachinery v0.17.6 + k8s.io/api v0.18.2 + k8s.io/apimachinery v0.18.2 k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible k8s.io/code-generator v0.18.0 knative.dev/pkg v0.0.0-20200529164702-389d28f9b67a - sigs.k8s.io/yaml v1.2.0 // indirect + sigs.k8s.io/controller-runtime v0.5.0 ) diff --git a/go.sum b/go.sum index d06d139f2..134edc537 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e contrib.go.opencensus.io/exporter/stackdriver v0.12.8/go.mod h1:XyyafDnFOsqoxHJgTFycKZMrRUrPThLh2iYTJF6uoO0= contrib.go.opencensus.io/exporter/stackdriver v0.12.9-0.20191108183826-59d068f8d8ff h1:g4QkFNN0ak+sCs/jqbhYLNkQaF1NVaKVoQ4Xm1RV3wM= contrib.go.opencensus.io/exporter/stackdriver v0.12.9-0.20191108183826-59d068f8d8ff/go.mod h1:XyyafDnFOsqoxHJgTFycKZMrRUrPThLh2iYTJF6uoO0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.9 h1:ZRVpDigsb+nVI/yps/NLDOYzYjFFmm3OCsBhmYocxR0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.9/go.mod h1:XyyafDnFOsqoxHJgTFycKZMrRUrPThLh2iYTJF6uoO0= contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= @@ -143,6 +145,7 @@ github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/ github.com/andygrunwald/go-gerrit v0.0.0-20190120104749-174420ebee6c/go.mod h1:0iuRQp6WJ44ts+iihy5E/WlPqfg5RNeQxOmzRkxCdtk= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= @@ -204,6 +207,7 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/clarketm/json v1.13.4/go.mod h1:ynr2LRfb0fQU34l07csRNBTcivjySLLiY1YzQqKVfdo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/sdk-go v0.0.0-20190509003705-56931988abe3/go.mod h1:j1nZWMLGg3om8SswStBoY6/SHvcLM19MuZqwDtMtmzs= +github.com/cloudevents/sdk-go v1.0.0/go.mod h1:3TkmM0cFqkhCHOq5JzzRU/RxRkwzoS8TZ+G448qVTog= github.com/cloudevents/sdk-go v1.1.2/go.mod h1:ss+jWJ88wypiewnPEzChSBzTYXGpdcILoN9YHk8uhTQ= github.com/cloudevents/sdk-go/v2 v2.0.0-preview8/go.mod h1:akZr/joO3DfDft2KZnI91LEs15NSKIBNPYcAMBQ1xbk= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -336,6 +340,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/zapr v0.1.1 h1:qXBXPDdNncunGs7XeEpsJt8wCjYBygluzfdLO0G5baE= github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -410,6 +415,7 @@ github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaL github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= @@ -458,6 +464,8 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Z github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.4.2/go.mod h1:0pIisECLUDurNyQcYRcNjhGp0j/yM6v617EmXsBJE3A= +github.com/google/cel-spec v0.4.0/go.mod h1:2pBM5cU4UKjbPDXBgwWkiwBsVgnxknuEJ7C5TDWwORQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -469,6 +477,7 @@ github.com/google/go-containerregistry v0.0.0-20200123184029-53ce695e4179/go.mod github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-github/v29 v29.0.3/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= +github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM= github.com/google/go-licenses v0.0.0-20191112164736-212ea350c932/go.mod h1:16wa6pRqNDUIhOtwF0GcROVqMeXHZJ7H6eGDFUh5Pfk= github.com/google/go-licenses v0.0.0-20200227160636-0fa8c766a591/go.mod h1:JWeTIGPLQ9gF618ZOdlUitd1gRR/l99WOkHOlmR/UVA= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -546,6 +555,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4= github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/grpc-ecosystem/grpc-gateway v1.13.0 h1:sBDQoHXrOlfPobnKw69FIKa1wg9qsLLvvQ/Y19WtFgI= +github.com/grpc-ecosystem/grpc-gateway v1.13.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -905,6 +916,7 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tektoncd/pipeline v0.8.0/go.mod h1:IZzJdiX9EqEMuUcgdnElozdYYRh0/ZRC+NKMLj1K3Yw= github.com/tektoncd/pipeline v0.10.1/go.mod h1:D2X0exT46zYx95BU7ByM8+erpjoN7thmUBvlKThOszU= +github.com/tektoncd/pipeline v0.11.3/go.mod h1:hlkH32S92+/UODROH0dmxzyuMxfRFp/Nc3e29MewLn8= github.com/tektoncd/pipeline v0.12.1 h1:x+3f0ScP9vmIcGQLzKbwPwBTcrqgDFfWEIaRqDN/doo= github.com/tektoncd/pipeline v0.12.1/go.mod h1:FLppx3k2Xlk8bsEYvlnp/wz3SK07rMFk4vaSAI6aTCM= github.com/tektoncd/plumbing v0.0.0-20191216083742-847dcf196de9/go.mod h1:QZHgU07PRBTRF6N57w4+ApRu8OgfYLFNqCDlfEZaD9Y= @@ -912,7 +924,12 @@ github.com/tektoncd/plumbing v0.0.0-20200217163359-cd0db6e567d2/go.mod h1:QZHgU0 github.com/tektoncd/plumbing v0.0.0-20200430135134-e53521e1d887 h1:crv70CBAJ2gZFSbf13aRVwdbjR2GYwTms/ZEok/SnFM= github.com/tektoncd/plumbing v0.0.0-20200430135134-e53521e1d887/go.mod h1:cZPJIeTIoP7UPTxQyTQLs7VE1TiXJSNj0te+If4Q+jI= github.com/tektoncd/plumbing/pipelinerun-logs v0.0.0-20191206114338-712d544c2c21/go.mod h1:S62EUWtqmejjJgUMOGB1CCCHRp6C706laH06BoALkzU= +github.com/tektoncd/triggers v0.6.0 h1:a2/SUTkkuEDd0M2Uo0pe/x0cjc+9aWauccN8IIBowT0= +github.com/tektoncd/triggers v0.6.0/go.mod h1:59Cz77wfFpPYXhzyx5IRqXw1JvX8Oja29/DkA4ivPPI= +github.com/tidwall/gjson v1.3.5/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible/go.mod h1:Smz/ZWfhKRcyDDChZkG3CyTHdj87lHzio/HOCkbndXM= @@ -1300,6 +1317,7 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200317114155-1f3552e48f24/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200326112834-f447254575fd h1:DVCc2PgW9UrvHGZGEv4Mt3uSeQtUrrs7r8pUw+bVwWI= google.golang.org/genproto v0.0.0-20200326112834-f447254575fd/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1429,6 +1447,7 @@ k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 h1:NeQXVJ2XFSkRoPzRo8AId01ZER+j8oV4SZADT4iBOXQ= k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= k8s.io/kubectl v0.17.2 h1:QZR8Q6lWiVRjwKslekdbN5WPMp53dS/17j5e+oi5XVU= @@ -1449,11 +1468,16 @@ k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b/go.mod h1:sZAwmy6armz5eXlNoLmJcl k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200124190032-861946025e34 h1:HjlUD6M0K3P8nRXmr2B9o4F9dUy9TCj/aEpReeyi6+k= k8s.io/utils v0.0.0-20200124190032-861946025e34/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= knative.dev/caching v0.0.0-20190719140829-2032732871ff/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= knative.dev/caching v0.0.0-20200116200605-67bca2c83dfa/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= +knative.dev/caching v0.0.0-20200228235451-13d271455c74/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= knative.dev/eventing-contrib v0.6.1-0.20190723221543-5ce18048c08b/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= +knative.dev/eventing-contrib v0.11.2/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= knative.dev/pkg v0.0.0-20191101194912-56c2594e4f11/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= knative.dev/pkg v0.0.0-20191111150521-6d806b998379/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= +knative.dev/pkg v0.0.0-20200207155214-fef852970f43/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= knative.dev/pkg v0.0.0-20200306230727-a56a6ea3fa56/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= knative.dev/pkg v0.0.0-20200428194351-90fc61bae7f7/go.mod h1:o+e8OVEJKIuvXPsGVPIautjXgs05xbos7G+QMRjuUps= knative.dev/pkg v0.0.0-20200505191044-3da93ebb24c2/go.mod h1:Q6sL35DdGs8hIQZKdaCXJGgY8f90BmNBKSb8z6d/BTM= @@ -1489,6 +1513,7 @@ sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1 sigs.k8s.io/structured-merge-diff v1.0.1 h1:LOs1LZWMsz1xs77Phr/pkB4LFaavH7IVq/3+WTN9XTA= sigs.k8s.io/structured-merge-diff v1.0.1/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/overlays/installer/base/kustomization.yaml b/overlays/installer/base/kustomization.yaml index 3eb1a33ce..3d8ca7f63 100644 --- a/overlays/installer/base/kustomization.yaml +++ b/overlays/installer/base/kustomization.yaml @@ -23,7 +23,9 @@ resources: - ../../../base/200-clusterrole-tenant.yaml - ../../../base/200-clusterrole-triggers.yaml - ../../../base/201-clusterrolebinding-backend.yaml + - ../../../base/202-build-crd.yaml - ../../../base/202-extension-crd.yaml + - ../../../base/202-project-crd.yaml - ../../../base/203-serviceaccount.yaml - ../../../base/300-deployment.yaml - ../../../base/300-service.yaml diff --git a/overlays/patches/read-write/clusterrole-tenant-patch.yaml b/overlays/patches/read-write/clusterrole-tenant-patch.yaml index 3a2d49785..2dc6d6725 100644 --- a/overlays/patches/read-write/clusterrole-tenant-patch.yaml +++ b/overlays/patches/read-write/clusterrole-tenant-patch.yaml @@ -75,3 +75,32 @@ - delete - patch - add +- op: add + path: /rules/- + value: + apiGroups: + - dashboard.tekton.dev + resources: + - builds + - builds/status + - extensions + - projects + - projects/status + verbs: + - create + - update + - delete + - patch +- op: add + path: /rules/- + value: + apiGroups: + - extensions + - apps + resources: + - ingresses + verbs: + - create + - update + - delete + - patch diff --git a/packages/utils/src/utils/router.js b/packages/utils/src/utils/router.js index 688261927..0eb35f7c1 100644 --- a/packages/utils/src/utils/router.js +++ b/packages/utils/src/utils/router.js @@ -20,6 +20,19 @@ export const paths = { about() { return '/about'; }, + builds: { + all() { + return '/builds'; + }, + byName() { + return byNamespace({ + path: '/builds/:buildName' + }); + }, + byNamespace() { + return byNamespace({ path: '/builds' }); + } + }, byNamespace, clusterTasks: { all() { @@ -66,6 +79,19 @@ export const paths = { return `/extensions/${name}`; } }, + projects: { + all() { + return '/projects'; + }, + byName() { + return byNamespace({ + path: '/projects/:projectName' + }); + }, + byNamespace() { + return byNamespace({ path: '/projects' }); + } + }, importResources() { return '/importresources'; }, diff --git a/pkg/apis/dashboard/v1alpha1/build_types.go b/pkg/apis/dashboard/v1alpha1/build_types.go new file mode 100644 index 000000000..dd9fa31f5 --- /dev/null +++ b/pkg/apis/dashboard/v1alpha1/build_types.go @@ -0,0 +1,41 @@ +package v1alpha1 + +import ( + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + resourcev1alpha1 "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// +k8s:openapi-gen=true +type Build struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + // +optional + Spec BuildSpec `json:"spec,omitempty"` + // +optional + Status BuildStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type BuildList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Build `json:"items"` +} + +type BuildSpec struct { + ServiceAccountName string `json:"serviceAccountName"` + PipelineResourceSpec resourcev1alpha1.PipelineResourceSpec `json:"pipelineResourceSpec"` + PipelineSpec pipelinev1beta1.PipelineSpec `json:"pipelineSpec"` + Params []pipelinev1beta1.Param `json:"params"` +} + +type BuildStatus struct { + PipelineRun *pipelinev1beta1.PipelineRunStatus `json:"pipelineRun"` +} diff --git a/pkg/apis/dashboard/v1alpha1/project_types.go b/pkg/apis/dashboard/v1alpha1/project_types.go new file mode 100644 index 000000000..037f6e47b --- /dev/null +++ b/pkg/apis/dashboard/v1alpha1/project_types.go @@ -0,0 +1,56 @@ +package v1alpha1 + +import ( + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// +k8s:openapi-gen=true +type Project struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + // +optional + Spec ProjectSpec `json:"spec,omitempty"` + // +optional + Status ProjectStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type ProjectList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Project `json:"items"` +} + +type ProjectSpec struct { + ServiceAccountName string `json:"serviceAccountName"` + Ingress *Ingress `json:"ingress"` + TriggerTemplate triggersv1alpha1.TriggerTemplateSpec `json:"triggerTemplate"` + TriggerBinding triggersv1alpha1.TriggerBindingSpec `json:"triggerBinding"` + Interceptors []*triggersv1alpha1.EventInterceptor `json:"interceptors"` +} + +type ProjectStatus struct { + TriggerTemplate *triggersv1alpha1.TriggerTemplateStatus `json:"triggerTemplate"` + TriggerBinding *triggersv1alpha1.TriggerBindingStatus `json:"triggerBinding"` + EventListener *triggersv1alpha1.EventListenerStatus `json:"eventListener"` + Ingress *extensionsv1beta1.IngressStatus `json:"ingress"` + TaskRuns map[string]*pipelinev1beta1.TaskRunStatus `json:"taskRuns"` + Builds map[string]*BuildStatus `json:"builds,omitempty"` +} + +type Ingress struct { + Host string `json:"host"` + Annotations map[string]string `json:"annotations"` + Labels map[string]string `json:"labels"` +} + +// project -> taskrun -> build -> taskrun -> pipelinerun diff --git a/pkg/apis/dashboard/v1alpha1/register.go b/pkg/apis/dashboard/v1alpha1/register.go index 31f19be0f..5fe289c29 100644 --- a/pkg/apis/dashboard/v1alpha1/register.go +++ b/pkg/apis/dashboard/v1alpha1/register.go @@ -46,8 +46,12 @@ var ( // Adds the list of known types to Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, + &Build{}, + &BuildList{}, &Extension{}, &ExtensionList{}, + &Project{}, + &ProjectList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/dashboard/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/dashboard/v1alpha1/zz_generated.deepcopy.go index 06d42965e..c5e0090b8 100644 --- a/pkg/apis/dashboard/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/dashboard/v1alpha1/zz_generated.deepcopy.go @@ -21,9 +21,119 @@ limitations under the License. package v1alpha1 import ( + v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Build) DeepCopyInto(out *Build) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Build. +func (in *Build) DeepCopy() *Build { + if in == nil { + return nil + } + out := new(Build) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Build) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BuildList) DeepCopyInto(out *BuildList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Build, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuildList. +func (in *BuildList) DeepCopy() *BuildList { + if in == nil { + return nil + } + out := new(BuildList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BuildList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BuildSpec) DeepCopyInto(out *BuildSpec) { + *out = *in + in.PipelineResourceSpec.DeepCopyInto(&out.PipelineResourceSpec) + in.PipelineSpec.DeepCopyInto(&out.PipelineSpec) + if in.Params != nil { + in, out := &in.Params, &out.Params + *out = make([]v1beta1.Param, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuildSpec. +func (in *BuildSpec) DeepCopy() *BuildSpec { + if in == nil { + return nil + } + out := new(BuildSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BuildStatus) DeepCopyInto(out *BuildStatus) { + *out = *in + if in.PipelineRun != nil { + in, out := &in.PipelineRun, &out.PipelineRun + *out = new(v1beta1.PipelineRunStatus) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuildStatus. +func (in *BuildStatus) DeepCopy() *BuildStatus { + if in == nil { + return nil + } + out := new(BuildStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Extension) DeepCopyInto(out *Extension) { *out = *in @@ -99,3 +209,194 @@ func (in *ExtensionSpec) DeepCopy() *ExtensionSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ingress) DeepCopyInto(out *Ingress) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ingress. +func (in *Ingress) DeepCopy() *Ingress { + if in == nil { + return nil + } + out := new(Ingress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Project) DeepCopyInto(out *Project) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Project. +func (in *Project) DeepCopy() *Project { + if in == nil { + return nil + } + out := new(Project) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Project) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectList) DeepCopyInto(out *ProjectList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Project, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectList. +func (in *ProjectList) DeepCopy() *ProjectList { + if in == nil { + return nil + } + out := new(ProjectList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProjectList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectSpec) DeepCopyInto(out *ProjectSpec) { + *out = *in + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = new(Ingress) + (*in).DeepCopyInto(*out) + } + in.TriggerTemplate.DeepCopyInto(&out.TriggerTemplate) + in.TriggerBinding.DeepCopyInto(&out.TriggerBinding) + if in.Interceptors != nil { + in, out := &in.Interceptors, &out.Interceptors + *out = make([]*triggersv1alpha1.EventInterceptor, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(triggersv1alpha1.EventInterceptor) + (*in).DeepCopyInto(*out) + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectSpec. +func (in *ProjectSpec) DeepCopy() *ProjectSpec { + if in == nil { + return nil + } + out := new(ProjectSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectStatus) DeepCopyInto(out *ProjectStatus) { + *out = *in + if in.TriggerTemplate != nil { + in, out := &in.TriggerTemplate, &out.TriggerTemplate + *out = new(triggersv1alpha1.TriggerTemplateStatus) + **out = **in + } + if in.TriggerBinding != nil { + in, out := &in.TriggerBinding, &out.TriggerBinding + *out = new(triggersv1alpha1.TriggerBindingStatus) + **out = **in + } + if in.EventListener != nil { + in, out := &in.EventListener, &out.EventListener + *out = new(triggersv1alpha1.EventListenerStatus) + (*in).DeepCopyInto(*out) + } + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = new(extensionsv1beta1.IngressStatus) + (*in).DeepCopyInto(*out) + } + if in.TaskRuns != nil { + in, out := &in.TaskRuns, &out.TaskRuns + *out = make(map[string]*v1beta1.TaskRunStatus, len(*in)) + for key, val := range *in { + var outVal *v1beta1.TaskRunStatus + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(v1beta1.TaskRunStatus) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + if in.Builds != nil { + in, out := &in.Builds, &out.Builds + *out = make(map[string]*BuildStatus, len(*in)) + for key, val := range *in { + var outVal *BuildStatus + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(BuildStatus) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectStatus. +func (in *ProjectStatus) DeepCopy() *ProjectStatus { + if in == nil { + return nil + } + out := new(ProjectStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/broadcaster/broadcaster.go b/pkg/broadcaster/broadcaster.go index fea181ffd..249a2a397 100644 --- a/pkg/broadcaster/broadcaster.go +++ b/pkg/broadcaster/broadcaster.go @@ -56,6 +56,12 @@ const ( ServiceAccountCreated MessageType = "ServiceAccountCreated" ServiceAccountDeleted MessageType = "ServiceAccountDeleted" ServiceAccountUpdated MessageType = "ServiceAccountUpdated" + ProjectCreated MessageType = "ProjectCreated" + ProjectDeleted MessageType = "ProjectDeleted" + ProjectUpdated MessageType = "ProjectUpdated" + BuildCreated MessageType = "BuildCreated" + BuildDeleted MessageType = "BuildDeleted" + BuildUpdated MessageType = "BuildUpdated" ) type SocketData struct { diff --git a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/build.go b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/build.go new file mode 100644 index 000000000..12a664e25 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/build.go @@ -0,0 +1,191 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "time" + + v1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + scheme "github.com/tektoncd/dashboard/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// BuildsGetter has a method to return a BuildInterface. +// A group's client should implement this interface. +type BuildsGetter interface { + Builds(namespace string) BuildInterface +} + +// BuildInterface has methods to work with Build resources. +type BuildInterface interface { + Create(*v1alpha1.Build) (*v1alpha1.Build, error) + Update(*v1alpha1.Build) (*v1alpha1.Build, error) + UpdateStatus(*v1alpha1.Build) (*v1alpha1.Build, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.Build, error) + List(opts v1.ListOptions) (*v1alpha1.BuildList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Build, err error) + BuildExpansion +} + +// builds implements BuildInterface +type builds struct { + client rest.Interface + ns string +} + +// newBuilds returns a Builds +func newBuilds(c *DashboardV1alpha1Client, namespace string) *builds { + return &builds{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the build, and returns the corresponding build object, and an error if there is any. +func (c *builds) Get(name string, options v1.GetOptions) (result *v1alpha1.Build, err error) { + result = &v1alpha1.Build{} + err = c.client.Get(). + Namespace(c.ns). + Resource("builds"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Builds that match those selectors. +func (c *builds) List(opts v1.ListOptions) (result *v1alpha1.BuildList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.BuildList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("builds"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested builds. +func (c *builds) Watch(opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("builds"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch() +} + +// Create takes the representation of a build and creates it. Returns the server's representation of the build, and an error, if there is any. +func (c *builds) Create(build *v1alpha1.Build) (result *v1alpha1.Build, err error) { + result = &v1alpha1.Build{} + err = c.client.Post(). + Namespace(c.ns). + Resource("builds"). + Body(build). + Do(). + Into(result) + return +} + +// Update takes the representation of a build and updates it. Returns the server's representation of the build, and an error, if there is any. +func (c *builds) Update(build *v1alpha1.Build) (result *v1alpha1.Build, err error) { + result = &v1alpha1.Build{} + err = c.client.Put(). + Namespace(c.ns). + Resource("builds"). + Name(build.Name). + Body(build). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *builds) UpdateStatus(build *v1alpha1.Build) (result *v1alpha1.Build, err error) { + result = &v1alpha1.Build{} + err = c.client.Put(). + Namespace(c.ns). + Resource("builds"). + Name(build.Name). + SubResource("status"). + Body(build). + Do(). + Into(result) + return +} + +// Delete takes name of the build and deletes it. Returns an error if one occurs. +func (c *builds) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("builds"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *builds) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + var timeout time.Duration + if listOptions.TimeoutSeconds != nil { + timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("builds"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Timeout(timeout). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched build. +func (c *builds) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Build, err error) { + result = &v1alpha1.Build{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("builds"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/dashboard_client.go b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/dashboard_client.go index 40d7aa490..53a6eeb6b 100644 --- a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/dashboard_client.go +++ b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/dashboard_client.go @@ -26,7 +26,9 @@ import ( type DashboardV1alpha1Interface interface { RESTClient() rest.Interface + BuildsGetter ExtensionsGetter + ProjectsGetter } // DashboardV1alpha1Client is used to interact with features provided by the dashboard.tekton.dev group. @@ -34,10 +36,18 @@ type DashboardV1alpha1Client struct { restClient rest.Interface } +func (c *DashboardV1alpha1Client) Builds(namespace string) BuildInterface { + return newBuilds(c, namespace) +} + func (c *DashboardV1alpha1Client) Extensions(namespace string) ExtensionInterface { return newExtensions(c, namespace) } +func (c *DashboardV1alpha1Client) Projects(namespace string) ProjectInterface { + return newProjects(c, namespace) +} + // NewForConfig creates a new DashboardV1alpha1Client for the given config. func NewForConfig(c *rest.Config) (*DashboardV1alpha1Client, error) { config := *c diff --git a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_build.go b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_build.go new file mode 100644 index 000000000..c20c3e9f0 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_build.go @@ -0,0 +1,140 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeBuilds implements BuildInterface +type FakeBuilds struct { + Fake *FakeDashboardV1alpha1 + ns string +} + +var buildsResource = schema.GroupVersionResource{Group: "dashboard.tekton.dev", Version: "v1alpha1", Resource: "builds"} + +var buildsKind = schema.GroupVersionKind{Group: "dashboard.tekton.dev", Version: "v1alpha1", Kind: "Build"} + +// Get takes name of the build, and returns the corresponding build object, and an error if there is any. +func (c *FakeBuilds) Get(name string, options v1.GetOptions) (result *v1alpha1.Build, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(buildsResource, c.ns, name), &v1alpha1.Build{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Build), err +} + +// List takes label and field selectors, and returns the list of Builds that match those selectors. +func (c *FakeBuilds) List(opts v1.ListOptions) (result *v1alpha1.BuildList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(buildsResource, buildsKind, c.ns, opts), &v1alpha1.BuildList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.BuildList{ListMeta: obj.(*v1alpha1.BuildList).ListMeta} + for _, item := range obj.(*v1alpha1.BuildList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested builds. +func (c *FakeBuilds) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(buildsResource, c.ns, opts)) + +} + +// Create takes the representation of a build and creates it. Returns the server's representation of the build, and an error, if there is any. +func (c *FakeBuilds) Create(build *v1alpha1.Build) (result *v1alpha1.Build, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(buildsResource, c.ns, build), &v1alpha1.Build{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Build), err +} + +// Update takes the representation of a build and updates it. Returns the server's representation of the build, and an error, if there is any. +func (c *FakeBuilds) Update(build *v1alpha1.Build) (result *v1alpha1.Build, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(buildsResource, c.ns, build), &v1alpha1.Build{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Build), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeBuilds) UpdateStatus(build *v1alpha1.Build) (*v1alpha1.Build, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(buildsResource, "status", c.ns, build), &v1alpha1.Build{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Build), err +} + +// Delete takes name of the build and deletes it. Returns an error if one occurs. +func (c *FakeBuilds) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(buildsResource, c.ns, name), &v1alpha1.Build{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBuilds) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(buildsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.BuildList{}) + return err +} + +// Patch applies the patch and returns the patched build. +func (c *FakeBuilds) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Build, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(buildsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Build{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Build), err +} diff --git a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_dashboard_client.go b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_dashboard_client.go index 549e099ab..72dd60cc0 100644 --- a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_dashboard_client.go +++ b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_dashboard_client.go @@ -28,10 +28,18 @@ type FakeDashboardV1alpha1 struct { *testing.Fake } +func (c *FakeDashboardV1alpha1) Builds(namespace string) v1alpha1.BuildInterface { + return &FakeBuilds{c, namespace} +} + func (c *FakeDashboardV1alpha1) Extensions(namespace string) v1alpha1.ExtensionInterface { return &FakeExtensions{c, namespace} } +func (c *FakeDashboardV1alpha1) Projects(namespace string) v1alpha1.ProjectInterface { + return &FakeProjects{c, namespace} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeDashboardV1alpha1) RESTClient() rest.Interface { diff --git a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_project.go b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_project.go new file mode 100644 index 000000000..455704a5c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/fake/fake_project.go @@ -0,0 +1,140 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeProjects implements ProjectInterface +type FakeProjects struct { + Fake *FakeDashboardV1alpha1 + ns string +} + +var projectsResource = schema.GroupVersionResource{Group: "dashboard.tekton.dev", Version: "v1alpha1", Resource: "projects"} + +var projectsKind = schema.GroupVersionKind{Group: "dashboard.tekton.dev", Version: "v1alpha1", Kind: "Project"} + +// Get takes name of the project, and returns the corresponding project object, and an error if there is any. +func (c *FakeProjects) Get(name string, options v1.GetOptions) (result *v1alpha1.Project, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(projectsResource, c.ns, name), &v1alpha1.Project{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Project), err +} + +// List takes label and field selectors, and returns the list of Projects that match those selectors. +func (c *FakeProjects) List(opts v1.ListOptions) (result *v1alpha1.ProjectList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(projectsResource, projectsKind, c.ns, opts), &v1alpha1.ProjectList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.ProjectList{ListMeta: obj.(*v1alpha1.ProjectList).ListMeta} + for _, item := range obj.(*v1alpha1.ProjectList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested projects. +func (c *FakeProjects) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(projectsResource, c.ns, opts)) + +} + +// Create takes the representation of a project and creates it. Returns the server's representation of the project, and an error, if there is any. +func (c *FakeProjects) Create(project *v1alpha1.Project) (result *v1alpha1.Project, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(projectsResource, c.ns, project), &v1alpha1.Project{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Project), err +} + +// Update takes the representation of a project and updates it. Returns the server's representation of the project, and an error, if there is any. +func (c *FakeProjects) Update(project *v1alpha1.Project) (result *v1alpha1.Project, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(projectsResource, c.ns, project), &v1alpha1.Project{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Project), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeProjects) UpdateStatus(project *v1alpha1.Project) (*v1alpha1.Project, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(projectsResource, "status", c.ns, project), &v1alpha1.Project{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Project), err +} + +// Delete takes name of the project and deletes it. Returns an error if one occurs. +func (c *FakeProjects) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(projectsResource, c.ns, name), &v1alpha1.Project{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeProjects) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(projectsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.ProjectList{}) + return err +} + +// Patch applies the patch and returns the patched project. +func (c *FakeProjects) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Project, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(projectsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Project{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Project), err +} diff --git a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/generated_expansion.go index ed63f52a8..1e885c8fc 100644 --- a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/generated_expansion.go @@ -18,4 +18,8 @@ limitations under the License. package v1alpha1 +type BuildExpansion interface{} + type ExtensionExpansion interface{} + +type ProjectExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/project.go b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/project.go new file mode 100644 index 000000000..00e46c67a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/dashboard/v1alpha1/project.go @@ -0,0 +1,191 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "time" + + v1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + scheme "github.com/tektoncd/dashboard/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ProjectsGetter has a method to return a ProjectInterface. +// A group's client should implement this interface. +type ProjectsGetter interface { + Projects(namespace string) ProjectInterface +} + +// ProjectInterface has methods to work with Project resources. +type ProjectInterface interface { + Create(*v1alpha1.Project) (*v1alpha1.Project, error) + Update(*v1alpha1.Project) (*v1alpha1.Project, error) + UpdateStatus(*v1alpha1.Project) (*v1alpha1.Project, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.Project, error) + List(opts v1.ListOptions) (*v1alpha1.ProjectList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Project, err error) + ProjectExpansion +} + +// projects implements ProjectInterface +type projects struct { + client rest.Interface + ns string +} + +// newProjects returns a Projects +func newProjects(c *DashboardV1alpha1Client, namespace string) *projects { + return &projects{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the project, and returns the corresponding project object, and an error if there is any. +func (c *projects) Get(name string, options v1.GetOptions) (result *v1alpha1.Project, err error) { + result = &v1alpha1.Project{} + err = c.client.Get(). + Namespace(c.ns). + Resource("projects"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Projects that match those selectors. +func (c *projects) List(opts v1.ListOptions) (result *v1alpha1.ProjectList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.ProjectList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("projects"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested projects. +func (c *projects) Watch(opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("projects"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch() +} + +// Create takes the representation of a project and creates it. Returns the server's representation of the project, and an error, if there is any. +func (c *projects) Create(project *v1alpha1.Project) (result *v1alpha1.Project, err error) { + result = &v1alpha1.Project{} + err = c.client.Post(). + Namespace(c.ns). + Resource("projects"). + Body(project). + Do(). + Into(result) + return +} + +// Update takes the representation of a project and updates it. Returns the server's representation of the project, and an error, if there is any. +func (c *projects) Update(project *v1alpha1.Project) (result *v1alpha1.Project, err error) { + result = &v1alpha1.Project{} + err = c.client.Put(). + Namespace(c.ns). + Resource("projects"). + Name(project.Name). + Body(project). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *projects) UpdateStatus(project *v1alpha1.Project) (result *v1alpha1.Project, err error) { + result = &v1alpha1.Project{} + err = c.client.Put(). + Namespace(c.ns). + Resource("projects"). + Name(project.Name). + SubResource("status"). + Body(project). + Do(). + Into(result) + return +} + +// Delete takes name of the project and deletes it. Returns an error if one occurs. +func (c *projects) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("projects"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *projects) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + var timeout time.Duration + if listOptions.TimeoutSeconds != nil { + timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("projects"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Timeout(timeout). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched project. +func (c *projects) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Project, err error) { + result = &v1alpha1.Project{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("projects"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/dashboard/v1alpha1/build.go b/pkg/client/informers/externalversions/dashboard/v1alpha1/build.go new file mode 100644 index 000000000..fff86fbf0 --- /dev/null +++ b/pkg/client/informers/externalversions/dashboard/v1alpha1/build.go @@ -0,0 +1,89 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + + dashboardv1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + versioned "github.com/tektoncd/dashboard/pkg/client/clientset/versioned" + internalinterfaces "github.com/tektoncd/dashboard/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/tektoncd/dashboard/pkg/client/listers/dashboard/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// BuildInformer provides access to a shared informer and lister for +// Builds. +type BuildInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.BuildLister +} + +type buildInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBuildInformer constructs a new informer for Build type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBuildInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBuildInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredBuildInformer constructs a new informer for Build type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredBuildInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.DashboardV1alpha1().Builds(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.DashboardV1alpha1().Builds(namespace).Watch(options) + }, + }, + &dashboardv1alpha1.Build{}, + resyncPeriod, + indexers, + ) +} + +func (f *buildInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBuildInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *buildInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&dashboardv1alpha1.Build{}, f.defaultInformer) +} + +func (f *buildInformer) Lister() v1alpha1.BuildLister { + return v1alpha1.NewBuildLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/dashboard/v1alpha1/interface.go b/pkg/client/informers/externalversions/dashboard/v1alpha1/interface.go index 275b96ad9..965f2ccd4 100644 --- a/pkg/client/informers/externalversions/dashboard/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/dashboard/v1alpha1/interface.go @@ -24,8 +24,12 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // Builds returns a BuildInformer. + Builds() BuildInformer // Extensions returns a ExtensionInformer. Extensions() ExtensionInformer + // Projects returns a ProjectInformer. + Projects() ProjectInformer } type version struct { @@ -39,7 +43,17 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// Builds returns a BuildInformer. +func (v *version) Builds() BuildInformer { + return &buildInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Extensions returns a ExtensionInformer. func (v *version) Extensions() ExtensionInformer { return &extensionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// Projects returns a ProjectInformer. +func (v *version) Projects() ProjectInformer { + return &projectInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/dashboard/v1alpha1/project.go b/pkg/client/informers/externalversions/dashboard/v1alpha1/project.go new file mode 100644 index 000000000..2b1e81c01 --- /dev/null +++ b/pkg/client/informers/externalversions/dashboard/v1alpha1/project.go @@ -0,0 +1,89 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + + dashboardv1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + versioned "github.com/tektoncd/dashboard/pkg/client/clientset/versioned" + internalinterfaces "github.com/tektoncd/dashboard/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/tektoncd/dashboard/pkg/client/listers/dashboard/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// ProjectInformer provides access to a shared informer and lister for +// Projects. +type ProjectInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.ProjectLister +} + +type projectInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewProjectInformer constructs a new informer for Project type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewProjectInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredProjectInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredProjectInformer constructs a new informer for Project type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredProjectInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.DashboardV1alpha1().Projects(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.DashboardV1alpha1().Projects(namespace).Watch(options) + }, + }, + &dashboardv1alpha1.Project{}, + resyncPeriod, + indexers, + ) +} + +func (f *projectInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredProjectInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *projectInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&dashboardv1alpha1.Project{}, f.defaultInformer) +} + +func (f *projectInformer) Lister() v1alpha1.ProjectLister { + return v1alpha1.NewProjectLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 4a4ab00f2..cbca6f5db 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -53,8 +53,12 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=dashboard.tekton.dev, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("builds"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Dashboard().V1alpha1().Builds().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("extensions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Dashboard().V1alpha1().Extensions().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("projects"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Dashboard().V1alpha1().Projects().Informer()}, nil } diff --git a/pkg/client/listers/dashboard/v1alpha1/build.go b/pkg/client/listers/dashboard/v1alpha1/build.go new file mode 100644 index 000000000..6a2819385 --- /dev/null +++ b/pkg/client/listers/dashboard/v1alpha1/build.go @@ -0,0 +1,94 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// BuildLister helps list Builds. +type BuildLister interface { + // List lists all Builds in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.Build, err error) + // Builds returns an object that can list and get Builds. + Builds(namespace string) BuildNamespaceLister + BuildListerExpansion +} + +// buildLister implements the BuildLister interface. +type buildLister struct { + indexer cache.Indexer +} + +// NewBuildLister returns a new BuildLister. +func NewBuildLister(indexer cache.Indexer) BuildLister { + return &buildLister{indexer: indexer} +} + +// List lists all Builds in the indexer. +func (s *buildLister) List(selector labels.Selector) (ret []*v1alpha1.Build, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Build)) + }) + return ret, err +} + +// Builds returns an object that can list and get Builds. +func (s *buildLister) Builds(namespace string) BuildNamespaceLister { + return buildNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// BuildNamespaceLister helps list and get Builds. +type BuildNamespaceLister interface { + // List lists all Builds in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.Build, err error) + // Get retrieves the Build from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.Build, error) + BuildNamespaceListerExpansion +} + +// buildNamespaceLister implements the BuildNamespaceLister +// interface. +type buildNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Builds in the indexer for a given namespace. +func (s buildNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Build, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Build)) + }) + return ret, err +} + +// Get retrieves the Build from the indexer for a given namespace and name. +func (s buildNamespaceLister) Get(name string) (*v1alpha1.Build, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("build"), name) + } + return obj.(*v1alpha1.Build), nil +} diff --git a/pkg/client/listers/dashboard/v1alpha1/expansion_generated.go b/pkg/client/listers/dashboard/v1alpha1/expansion_generated.go index 3125f9e3f..22994ebff 100644 --- a/pkg/client/listers/dashboard/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/dashboard/v1alpha1/expansion_generated.go @@ -18,6 +18,14 @@ limitations under the License. package v1alpha1 +// BuildListerExpansion allows custom methods to be added to +// BuildLister. +type BuildListerExpansion interface{} + +// BuildNamespaceListerExpansion allows custom methods to be added to +// BuildNamespaceLister. +type BuildNamespaceListerExpansion interface{} + // ExtensionListerExpansion allows custom methods to be added to // ExtensionLister. type ExtensionListerExpansion interface{} @@ -25,3 +33,11 @@ type ExtensionListerExpansion interface{} // ExtensionNamespaceListerExpansion allows custom methods to be added to // ExtensionNamespaceLister. type ExtensionNamespaceListerExpansion interface{} + +// ProjectListerExpansion allows custom methods to be added to +// ProjectLister. +type ProjectListerExpansion interface{} + +// ProjectNamespaceListerExpansion allows custom methods to be added to +// ProjectNamespaceLister. +type ProjectNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/dashboard/v1alpha1/project.go b/pkg/client/listers/dashboard/v1alpha1/project.go new file mode 100644 index 000000000..3da953ef7 --- /dev/null +++ b/pkg/client/listers/dashboard/v1alpha1/project.go @@ -0,0 +1,94 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ProjectLister helps list Projects. +type ProjectLister interface { + // List lists all Projects in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.Project, err error) + // Projects returns an object that can list and get Projects. + Projects(namespace string) ProjectNamespaceLister + ProjectListerExpansion +} + +// projectLister implements the ProjectLister interface. +type projectLister struct { + indexer cache.Indexer +} + +// NewProjectLister returns a new ProjectLister. +func NewProjectLister(indexer cache.Indexer) ProjectLister { + return &projectLister{indexer: indexer} +} + +// List lists all Projects in the indexer. +func (s *projectLister) List(selector labels.Selector) (ret []*v1alpha1.Project, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Project)) + }) + return ret, err +} + +// Projects returns an object that can list and get Projects. +func (s *projectLister) Projects(namespace string) ProjectNamespaceLister { + return projectNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ProjectNamespaceLister helps list and get Projects. +type ProjectNamespaceLister interface { + // List lists all Projects in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.Project, err error) + // Get retrieves the Project from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.Project, error) + ProjectNamespaceListerExpansion +} + +// projectNamespaceLister implements the ProjectNamespaceLister +// interface. +type projectNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Projects in the indexer for a given namespace. +func (s projectNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Project, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Project)) + }) + return ret, err +} + +// Get retrieves the Project from the indexer for a given namespace and name. +func (s projectNamespaceLister) Get(name string) (*v1alpha1.Project, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("project"), name) + } + return obj.(*v1alpha1.Project), nil +} diff --git a/pkg/controllers/controller.go b/pkg/controllers/controller.go index 3217bb38c..aba7f1aef 100644 --- a/pkg/controllers/controller.go +++ b/pkg/controllers/controller.go @@ -16,19 +16,27 @@ package controllers import ( "time" + dashboardv1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" dashboardclientset "github.com/tektoncd/dashboard/pkg/client/clientset/versioned" dashboardinformers "github.com/tektoncd/dashboard/pkg/client/informers/externalversions" dashboardcontroller "github.com/tektoncd/dashboard/pkg/controllers/dashboard" kubecontroller "github.com/tektoncd/dashboard/pkg/controllers/kubernetes" + runtimecontroller "github.com/tektoncd/dashboard/pkg/controllers/runtime" tektoncontroller "github.com/tektoncd/dashboard/pkg/controllers/tekton" "github.com/tektoncd/dashboard/pkg/logging" "github.com/tektoncd/dashboard/pkg/router" + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" tektonclientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" tektoninformers "github.com/tektoncd/pipeline/pkg/client/informers/externalversions" resourceclientset "github.com/tektoncd/pipeline/pkg/client/resource/clientset/versioned" resourceinformers "github.com/tektoncd/pipeline/pkg/client/resource/informers/externalversions" + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + triggersclientset "github.com/tektoncd/triggers/pkg/client/clientset/versioned" k8sinformers "k8s.io/client-go/informers" k8sclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/manager" + runtimesignals "sigs.k8s.io/controller-runtime/pkg/manager/signals" ) // StartTektonControllers creates and starts Tekton controllers @@ -74,11 +82,31 @@ func StartKubeControllers(clientset k8sclientset.Interface, resyncDur time.Durat } // StartDashboardControllers creates and starts Dashboard controllers -func StartDashboardControllers(clientset dashboardclientset.Interface, resyncDur time.Duration, tenantNamespace string, stopCh <-chan struct{}) { +func StartDashboardControllers(clientset dashboardclientset.Interface, triggersClient triggersclientset.Interface, k8sClient k8sclientset.Interface, resyncDur time.Duration, tenantNamespace string, stopCh <-chan struct{}) { logging.Log.Info("Creating Dashboard controllers") tenantInformerFactory := dashboardinformers.NewSharedInformerFactoryWithOptions(clientset, resyncDur, dashboardinformers.WithNamespace(tenantNamespace)) + dashboardcontroller.NewBuildController(tenantInformerFactory) dashboardcontroller.NewExtensionController(tenantInformerFactory) + dashboardcontroller.NewProjectController(tenantInformerFactory) // Started once all controllers have been registered logging.Log.Info("Starting Dashboard controllers") tenantInformerFactory.Start(stopCh) } + +func StartRuntimeControllers(config *rest.Config) { + // Setup a Manager + mgr, _ := manager.New(config, manager.Options{}) + + // Register scheme + dashboardv1alpha1.AddToScheme(mgr.GetScheme()) + pipelinev1beta1.AddToScheme(mgr.GetScheme()) + triggersv1alpha1.AddToScheme(mgr.GetScheme()) + + runtimecontroller.ForProjects(mgr) + runtimecontroller.ForBuilds(mgr) + + go func() { + mgr.Start(runtimesignals.SetupSignalHandler()) + }() + +} diff --git a/pkg/controllers/dashboard/build.go b/pkg/controllers/dashboard/build.go new file mode 100644 index 000000000..218233ca0 --- /dev/null +++ b/pkg/controllers/dashboard/build.go @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Tekton 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 dashboard + +import ( + "github.com/tektoncd/dashboard/pkg/broadcaster" + dashboardinformer "github.com/tektoncd/dashboard/pkg/client/informers/externalversions" + "github.com/tektoncd/dashboard/pkg/controllers/utils" + "github.com/tektoncd/dashboard/pkg/logging" +) + +// NewNamespaceController registers the K8s shared informer that reacts to +// create and delete events for namespaces +func NewBuildController(sharedDashboardInformerFactory dashboardinformer.SharedInformerFactory) { + logging.Log.Debug("In NewBuildController") + + utils.NewController( + "build", + sharedDashboardInformerFactory.Dashboard().V1alpha1().Builds().Informer(), + broadcaster.BuildCreated, + broadcaster.BuildUpdated, + broadcaster.BuildDeleted, + nil, + ) +} diff --git a/pkg/controllers/dashboard/project.go b/pkg/controllers/dashboard/project.go new file mode 100644 index 000000000..d8baa6357 --- /dev/null +++ b/pkg/controllers/dashboard/project.go @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Tekton 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 dashboard + +import ( + "github.com/tektoncd/dashboard/pkg/broadcaster" + dashboardinformer "github.com/tektoncd/dashboard/pkg/client/informers/externalversions" + "github.com/tektoncd/dashboard/pkg/controllers/utils" + "github.com/tektoncd/dashboard/pkg/logging" +) + +// NewNamespaceController registers the K8s shared informer that reacts to +// create and delete events for namespaces +func NewProjectController(sharedDashboardInformerFactory dashboardinformer.SharedInformerFactory) { + logging.Log.Debug("In NewProjectController") + + utils.NewController( + "project", + sharedDashboardInformerFactory.Dashboard().V1alpha1().Projects().Informer(), + broadcaster.ProjectCreated, + broadcaster.ProjectUpdated, + broadcaster.ProjectDeleted, + nil, + ) +} diff --git a/pkg/controllers/runtime/build.go b/pkg/controllers/runtime/build.go new file mode 100644 index 000000000..0ed14b53f --- /dev/null +++ b/pkg/controllers/runtime/build.go @@ -0,0 +1,74 @@ +/* +Copyright 2020 The Tekton 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 runtimecontroller + +import ( + "context" + + dashboardv1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + "github.com/tektoncd/dashboard/pkg/logging" + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func ForBuilds(mgr manager.Manager) { + ctrl.NewControllerManagedBy(mgr). + For(&dashboardv1alpha1.Build{}). + Owns(&pipelinev1beta1.PipelineRun{}). + Complete(reconcile.Func(func(request ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + client := mgr.GetClient() + scheme := mgr.GetScheme() + + logging.Log.Infof("Reconcile Build %+v", request) + + var build dashboardv1alpha1.Build + if err := client.Get(ctx, request.NamespacedName, &build); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + logging.Log.Errorf("unable to get Build: %s", err) + return ctrl.Result{}, err + } + + pipelineRun := &pipelinev1beta1.PipelineRun{ObjectMeta: objectMeta(request)} + if err := r(ctx, "TriggerTemplate", client, scheme, &build, pipelineRun, func() error { + pipelineRun.Spec = pipelinev1beta1.PipelineRunSpec{ + PipelineSpec: &build.Spec.PipelineSpec, + Resources: []pipelinev1beta1.PipelineResourceBinding{ + { + Name: "source", + ResourceSpec: &build.Spec.PipelineResourceSpec, + }, + }, + Params: build.Spec.Params, + } + return nil + }); err != nil { + return ctrl.Result{}, err + } + + build.Status.PipelineRun = &pipelineRun.Status + + if err := client.Status().Update(ctx, &build); err != nil { + logging.Log.Errorf("failed to update Build status: %s", err) + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil + })) +} diff --git a/pkg/controllers/runtime/project.go b/pkg/controllers/runtime/project.go new file mode 100644 index 000000000..db54481be --- /dev/null +++ b/pkg/controllers/runtime/project.go @@ -0,0 +1,208 @@ +/* +Copyright 2020 The Tekton 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 runtimecontroller + +import ( + "context" + + dashboardv1alpha1 "github.com/tektoncd/dashboard/pkg/apis/dashboard/v1alpha1" + "github.com/tektoncd/dashboard/pkg/logging" + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const projectOwnerKey = ".metadata.controller" + +var apiGVStr = dashboardv1alpha1.SchemeGroupVersion.String() + +func ForProjects(mgr manager.Manager) { + if err := mgr.GetFieldIndexer().IndexField(&pipelinev1beta1.TaskRun{}, projectOwnerKey, func(rawObj runtime.Object) []string { + obj := rawObj.(*pipelinev1beta1.TaskRun) + owner := metav1.GetControllerOf(obj) + if owner == nil { + return nil + } + if owner.APIVersion != apiGVStr || owner.Kind != "Project" { + return nil + } + return []string{owner.Name} + }); err != nil { + logging.Log.Errorf("failed to register indexer: %s", err) + } + + if err := mgr.GetFieldIndexer().IndexField(&dashboardv1alpha1.Build{}, projectOwnerKey, func(rawObj runtime.Object) []string { + obj := rawObj.(*dashboardv1alpha1.Build) + owner := metav1.GetControllerOf(obj) + if owner == nil { + return nil + } + if owner.APIVersion != apiGVStr || owner.Kind != "Project" { + return nil + } + return []string{owner.Name} + }); err != nil { + logging.Log.Errorf("failed to register indexer: %s", err) + } + + ctrl.NewControllerManagedBy(mgr). + For(&dashboardv1alpha1.Project{}). + Owns(&triggersv1alpha1.TriggerBinding{}). + Owns(&triggersv1alpha1.TriggerTemplate{}). + Owns(&triggersv1alpha1.EventListener{}). + Owns(&extensionsv1beta1.Ingress{}). + Owns(&dashboardv1alpha1.Build{}). + Owns(&pipelinev1beta1.TaskRun{}). + Complete(reconcile.Func(func(request ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + c := mgr.GetClient() + scheme := mgr.GetScheme() + + logging.Log.Infof("Reconcile Project %+v", request) + + var project dashboardv1alpha1.Project + if err := c.Get(ctx, request.NamespacedName, &project); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + logging.Log.Errorf("unable to get Project: %s", err) + return ctrl.Result{}, err + } + + triggerTemplate := &triggersv1alpha1.TriggerTemplate{ObjectMeta: objectMeta(request)} + if err := r(ctx, "TriggerTemplate", c, scheme, &project, triggerTemplate, func() error { + triggerTemplate.Spec = project.Spec.TriggerTemplate + return nil + }); err != nil { + return ctrl.Result{}, err + } + + triggerBinding := &triggersv1alpha1.TriggerBinding{ObjectMeta: objectMeta(request)} + if err := r(ctx, "TriggerBinding", c, scheme, &project, triggerBinding, func() error { + project.Spec.TriggerBinding.DeepCopyInto(&triggerBinding.Spec) + triggerBinding.Spec.Params = append( + triggerBinding.Spec.Params, + triggersv1alpha1.Param{ + Name: "ownername", + Value: project.Name, + }, + triggersv1alpha1.Param{ + Name: "owneruid", + Value: string(project.UID), + }, + ) + return nil + }); err != nil { + return ctrl.Result{}, err + } + + eventListener := &triggersv1alpha1.EventListener{ObjectMeta: objectMeta(request)} + if err := r(ctx, "EventListener", c, scheme, &project, eventListener, func() error { + eventListener.Spec = triggersv1alpha1.EventListenerSpec{ + ServiceAccountName: project.Spec.ServiceAccountName, + Triggers: []triggersv1alpha1.EventListenerTrigger{ + { + Interceptors: project.Spec.Interceptors, + Bindings: []*triggersv1alpha1.EventListenerBinding{ + { + Ref: project.GetName(), + }, + }, + Template: triggersv1alpha1.EventListenerTemplate{ + Name: triggerTemplate.Name, + }, + }, + }, + } + return nil + }); err != nil { + return ctrl.Result{}, err + } + + if project.Spec.Ingress != nil { + ingress := &extensionsv1beta1.Ingress{ObjectMeta: objectMeta(request)} + if err := r(ctx, "Ingress", c, scheme, &project, ingress, func() error { + ingress.Annotations = project.Spec.Ingress.Annotations + ingress.Labels = project.Spec.Ingress.Labels + ingress.Spec = extensionsv1beta1.IngressSpec{ + Rules: []extensionsv1beta1.IngressRule{ + { + Host: project.Spec.Ingress.Host, + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: "el-" + eventListener.GetName(), + ServicePort: intstr.FromInt(8080), + }, + }, + }, + }, + }, + }, + }, + } + return nil + }); err != nil { + return ctrl.Result{}, err + } + + project.Status.Ingress = &ingress.Status + } + + project.Status.EventListener = &eventListener.Status + project.Status.TriggerBinding = &triggerBinding.Status + project.Status.TriggerTemplate = &triggerTemplate.Status + + var taskRunList pipelinev1beta1.TaskRunList + if err := c.List(ctx, &taskRunList, client.InNamespace(request.Namespace), client.MatchingFields{projectOwnerKey: request.Name}); err != nil { + logging.Log.Errorf("failed to reconcile TaskRuns status: %s", err) + } + + taskRuns := make(map[string]*pipelinev1beta1.TaskRunStatus) + for _, taskRun := range taskRunList.Items { + ref := taskRun + taskRuns[taskRun.Name] = &ref.Status + } + project.Status.TaskRuns = taskRuns + + var buildList dashboardv1alpha1.BuildList + if err := c.List(ctx, &buildList, client.InNamespace(request.Namespace), client.MatchingFields{projectOwnerKey: request.Name}); err != nil { + logging.Log.Errorf("failed to reconcile Builds status: %s", err) + } + + builds := make(map[string]*dashboardv1alpha1.BuildStatus) + for _, build := range buildList.Items { + ref := build + builds[build.Name] = &ref.Status + } + project.Status.Builds = builds + + if err := c.Status().Update(ctx, &project); err != nil { + logging.Log.Errorf("failed to update Project status: %s", err) + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil + })) +} diff --git a/pkg/controllers/runtime/utils.go b/pkg/controllers/runtime/utils.go new file mode 100644 index 000000000..7a4553014 --- /dev/null +++ b/pkg/controllers/runtime/utils.go @@ -0,0 +1,48 @@ +/* +Copyright 2020 The Tekton 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 runtimecontroller + +import ( + "context" + + "github.com/tektoncd/dashboard/pkg/logging" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func r(ctx context.Context, kind string, c client.Client, scheme *runtime.Scheme, owner metav1.Object, obj runtime.Object, f controllerutil.MutateFn) error { + op, err := ctrl.CreateOrUpdate(ctx, c, obj, func() error { + if err := ctrl.SetControllerReference(owner, obj.(metav1.Object), scheme); err != nil { + logging.Log.Errorf("failed to set %s's owner reference: %s", kind, err) + return err + } + return f() + }) + if err != nil { + logging.Log.Errorf("failed to reconcile %s: %s", kind, err) + } else { + logging.Log.Infof("successfully reconciled %s: %s", kind, op) + } + return err +} + +func objectMeta(request ctrl.Request) metav1.ObjectMeta { + return metav1.ObjectMeta{ + Name: request.Name, + Namespace: request.Namespace, + } +} diff --git a/pkg/endpoints/types.go b/pkg/endpoints/types.go index 4bd897c29..6f2fb645b 100644 --- a/pkg/endpoints/types.go +++ b/pkg/endpoints/types.go @@ -7,6 +7,7 @@ import ( dashboardclientset "github.com/tektoncd/dashboard/pkg/client/clientset/versioned" pipelineclientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" resourceclientset "github.com/tektoncd/pipeline/pkg/client/resource/clientset/versioned" + triggersclientset "github.com/tektoncd/triggers/pkg/client/clientset/versioned" k8sclientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) @@ -50,6 +51,7 @@ type Resource struct { DashboardClient dashboardclientset.Interface PipelineClient pipelineclientset.Interface PipelineResourceClient resourceclientset.Interface + TriggersClient triggersclientset.Interface K8sClient k8sclientset.Interface RouteClient routeclientset.Interface Options Options diff --git a/rbac.yaml b/rbac.yaml new file mode 100644 index 000000000..f5adb3078 --- /dev/null +++ b/rbac.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: github-project + labels: + app.kubernetes.io/component: dashboard + app.kubernetes.io/instance: default + app.kubernetes.io/part-of: tekton-dashboard + +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: github-project + labels: + app.kubernetes.io/component: dashboard + app.kubernetes.io/instance: default + app.kubernetes.io/part-of: tekton-dashboard +rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: dashboard + app.kubernetes.io/instance: default + app.kubernetes.io/part-of: tekton-dashboard + name: github-project +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: github-project +subjects: + - kind: ServiceAccount + name: github-project diff --git a/scripts/prepare-kind-cluster b/scripts/prepare-kind-cluster index 2e624c38b..bb59f6ccd 100755 --- a/scripts/prepare-kind-cluster +++ b/scripts/prepare-kind-cluster @@ -44,13 +44,13 @@ install_ingress_nginx() { } install_pipelines() { - kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml + kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.14.3/release.yaml sleep 10 kubectl wait -n tekton-pipelines --for=condition=ready pod --selector=app.kubernetes.io/part-of=tekton-pipelines,app.kubernetes.io/component=controller --timeout=90s } install_triggers() { - kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml + kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/previous/v0.6.1/release.yaml sleep 10 kubectl wait -n tekton-pipelines --for=condition=ready pod --selector=app.kubernetes.io/part-of=tekton-triggers,app.kubernetes.io/component=controller --timeout=90s } @@ -66,7 +66,11 @@ case $1 in install_ingress_nginx install_pipelines install_triggers - ./scripts/installer install --log-format console $@ + ./scripts/installer install $@ --ingress-url tekton-dashboard.127.0.0.1.nip.io --log-format console + ;; + 'update'|u) + shift + ./scripts/installer install $@ --ingress-url tekton-dashboard.127.0.0.1.nip.io --log-format console ;; 'delete'|d) delete_cluster diff --git a/src/actions/builds.js b/src/actions/builds.js new file mode 100644 index 000000000..240d91973 --- /dev/null +++ b/src/actions/builds.js @@ -0,0 +1,32 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import { getBuild, getBuilds } from '../api'; +import { + fetchNamespacedCollection, + fetchNamespacedResource +} from './actionCreators'; + +export function fetchBuild({ name, namespace }) { + return fetchNamespacedResource('Build', getBuild, { + name, + namespace + }); +} + +export function fetchBuilds({ filters, namespace } = {}) { + return fetchNamespacedCollection('Build', getBuilds, { + filters, + namespace + }); +} diff --git a/src/actions/projects.js b/src/actions/projects.js new file mode 100644 index 000000000..44446defb --- /dev/null +++ b/src/actions/projects.js @@ -0,0 +1,32 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import { getProject, getProjects } from '../api'; +import { + fetchNamespacedCollection, + fetchNamespacedResource +} from './actionCreators'; + +export function fetchProject({ name, namespace }) { + return fetchNamespacedResource('Project', getProject, { + name, + namespace + }); +} + +export function fetchProjects({ filters, namespace } = {}) { + return fetchNamespacedCollection('Project', getProjects, { + filters, + namespace + }); +} diff --git a/src/api/index.js b/src/api/index.js index a25fc994a..df647a91d 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -305,6 +305,178 @@ export function getCondition({ name, namespace }) { return get(uri); } +export function getProjects({ filters = [], namespace } = {}) { + const uri = getTektonAPI( + 'projects', + { group: dashboardAPIGroup, namespace, version: 'v1alpha1' }, + getQueryParams(filters) + ); + return get(uri).then(checkData); +} + +export function getProject({ name, namespace }) { + const uri = getTektonAPI('projects', { + group: dashboardAPIGroup, + name, + namespace, + version: 'v1alpha1' + }); + return get(uri); +} + +export function createProject({ + name, + namespace, + ingress, + ingressLabels, + ingressAnnotations, + bindings, + interceptors, + serviceAccount +}) { + const payload = { + apiVersion: 'dashboard.tekton.dev/v1alpha1', + kind: 'Project', + metadata: { + name, + namespace + }, + spec: { + serviceAccountName: serviceAccount, + interceptors, + triggerBinding: { + params: [ + ...Object.keys(bindings).map(x => ({ name: x, value: bindings[x] })) + ] + }, + triggerTemplate: { + params: [ + ...Object.keys(bindings).map(x => ({ name: x })), + { + name: 'ownername' + }, + { + name: 'owneruid' + } + ], + resourcetemplates: [ + { + apiVersion: 'tekton.dev/v1beta1', + kind: 'TaskRun', + metadata: { + generateName: `${name}-`, + ownerReferences: [ + { + apiVersion: 'dashboard.tekton.dev/v1alpha1', + kind: 'Project', + name: '$(tt.params.ownername)', + uid: '$(tt.params.owneruid)', + controller: true, + blockOwnerDeletion: true + } + ] + }, + spec: { + serviceAccountName: serviceAccount, + resources: { + inputs: [ + { + name: 'source', + resourceSpec: { + type: 'git', + params: [ + { + name: 'revision', + value: '$(tt.params.gitrevision)' + }, + { + name: 'url', + value: '$(tt.params.gitrepositoryurl)' + } + ] + } + } + ] + }, + taskSpec: { + resources: { + inputs: [ + { + name: 'source', + type: 'git' + } + ] + }, + steps: [ + { + image: 'eddycharly/build-maker:test-4', + command: ['/work/build-maker'], + args: [ + `--namespace=${namespace}`, + '--file=$(resources.inputs.source.path)/.tekton.yaml', + '--url=$(tt.params.gitrepositoryurl)', + '--revision=$(tt.params.gitrevision)', + '--owner-name=$(tt.params.ownername)', + '--owner-uid=$(tt.params.owneruid)', + ...Object.keys(bindings).map( + x => `--param=${x}=$(tt.params.${x})` + ) + ] + } + ] + } + } + } + ] + } + } + }; + + if (ingress) { + payload.spec.ingress = { + host: ingress, + annotations: ingressAnnotations, + labels: ingressLabels + }; + } + + const uri = getTektonAPI('projects', { + group: dashboardAPIGroup, + namespace, + version: 'v1alpha1' + }); + return post(uri, payload); +} + +export function deleteProject({ name, namespace }) { + const uri = getTektonAPI('projects', { + group: dashboardAPIGroup, + name, + namespace, + version: 'v1alpha1' + }); + return deleteRequest(uri); +} + +export function getBuilds({ filters = [], namespace } = {}) { + const uri = getTektonAPI( + 'builds', + { group: dashboardAPIGroup, namespace, version: 'v1alpha1' }, + getQueryParams(filters) + ); + return get(uri).then(checkData); +} + +export function getBuild({ name, namespace }) { + const uri = getTektonAPI('builds', { + group: dashboardAPIGroup, + name, + namespace, + version: 'v1alpha1' + }); + return get(uri); +} + export function getPodLogURL({ container, name, namespace }) { let queryParams; if (container) { diff --git a/src/containers/App/App.js b/src/containers/App/App.js index 9d450c475..738f7a129 100644 --- a/src/containers/App/App.js +++ b/src/containers/App/App.js @@ -34,6 +34,8 @@ import { getErrorMessage, paths, urls } from '@tektoncd/dashboard-utils'; import { About, + Build, + Builds, ClusterTasks, ClusterTriggerBinding, ClusterTriggerBindings, @@ -52,6 +54,8 @@ import { PipelineRun, PipelineRuns, Pipelines, + Project, + Projects, ReadWriteRoute, ResourceList, Secret, @@ -300,6 +304,24 @@ export /* istanbul ignore next */ class App extends Component { path={paths.conditions.byName()} component={Condition} /> + + + + + + diff --git a/src/containers/Build/Build.js b/src/containers/Build/Build.js new file mode 100644 index 000000000..f2123bb07 --- /dev/null +++ b/src/containers/Build/Build.js @@ -0,0 +1,161 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import React, { Component } from 'react'; +import { injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { getTitle } from '@tektoncd/dashboard-utils'; +import { ResourceDetails, Table } from '@tektoncd/dashboard-components'; +import { + getBuild, + getBuildsErrorMessage, + getSelectedNamespace, + isWebSocketConnected +} from '../../reducers'; +import { fetchBuild } from '../../actions/builds'; + +export class BuildContainer extends Component { + componentDidMount() { + const { match } = this.props; + const { buildName: resourceName } = match.params; + document.title = getTitle({ + page: 'Build', + resourceName + }); + this.fetchData(); + } + + componentDidUpdate(prevProps) { + const { match, webSocketConnected } = this.props; + const { namespace, buildName } = match.params; + const { + match: prevMatch, + webSocketConnected: prevWebSocketConnected + } = prevProps; + const { + namespace: prevNamespace, + buildName: prevBuildName + } = prevMatch.params; + + if ( + namespace !== prevNamespace || + buildName !== prevBuildName || + (webSocketConnected && prevWebSocketConnected === false) + ) { + this.fetchData(); + } + } + + getAdditionalContent() { + const { build, intl } = this.props; + if (!build || !build.spec.params) { + return null; + } + + const headers = [ + { + key: 'name', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.name', + defaultMessage: 'Name' + }) + }, + { + key: 'default', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.default', + defaultMessage: 'Default' + }) + }, + { + key: 'type', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.type', + defaultMessage: 'Type' + }) + } + ]; + + const rows = build.spec.params.map( + ({ name, default: defaultValue, type = 'string' }) => ({ + id: name, + name, + default: defaultValue, + type + }) + ); + + return ( + + ); + } + + fetchData() { + const { match } = this.props; + const { namespace, buildName } = match.params; + this.props.fetchBuild({ name: buildName, namespace }); + } + + render() { + const { error, build } = this.props; + const additionalContent = this.getAdditionalContent(); + + return ( + + {additionalContent} + + ); + } +} + +BuildContainer.propTypes = { + match: PropTypes.shape({ + params: PropTypes.shape({ + buildName: PropTypes.string.isRequired + }).isRequired + }).isRequired +}; + +/* istanbul ignore next */ +function mapStateToProps(state, ownProps) { + const { match } = ownProps; + const { namespace: namespaceParam, buildName } = match.params; + + const namespace = namespaceParam || getSelectedNamespace(state); + const build = getBuild(state, { + name: buildName, + namespace + }); + return { + error: getBuildsErrorMessage(state), + build, + webSocketConnected: isWebSocketConnected(state) + }; +} + +const mapDispatchToProps = { + fetchBuild +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(BuildContainer)); diff --git a/src/containers/Build/index.js b/src/containers/Build/index.js new file mode 100644 index 000000000..5943df4a4 --- /dev/null +++ b/src/containers/Build/index.js @@ -0,0 +1,14 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +export { default } from './Build'; diff --git a/src/containers/Builds/Builds.js b/src/containers/Builds/Builds.js new file mode 100644 index 000000000..7d851e980 --- /dev/null +++ b/src/containers/Builds/Builds.js @@ -0,0 +1,252 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { injectIntl } from 'react-intl'; +import isEqual from 'lodash.isequal'; +import { + getErrorMessage, + getFilters, + getStatus, + getTitle, + urls +} from '@tektoncd/dashboard-utils'; +import { + FormattedDate, + FormattedDuration, + StatusIcon, + Table +} from '@tektoncd/dashboard-components'; +import { InlineNotification } from 'carbon-components-react'; + +import { LabelFilter } from '..'; +import { fetchBuilds } from '../../actions/builds'; +import { + getBuilds, + getBuildsErrorMessage, + getSelectedNamespace, + isFetchingBuilds, + isWebSocketConnected +} from '../../reducers'; + +export class Builds extends Component { + componentDidMount() { + document.title = getTitle({ page: 'Builds' }); + this.fetchData(); + } + + componentDidUpdate(prevProps) { + const { filters, namespace, webSocketConnected } = this.props; + const { + filters: prevFilters, + namespace: prevNamespace, + webSocketConnected: prevWebSocketConnected + } = prevProps; + + if ( + namespace !== prevNamespace || + (webSocketConnected && prevWebSocketConnected === false) || + !isEqual(filters, prevFilters) + ) { + this.fetchData(); + } + } + + fetchData() { + const { filters, namespace } = this.props; + this.props.fetchBuilds({ + filters, + namespace + }); + } + + render() { + const { + error, + builds, + loading, + intl, + namespace: selectedNamespace + } = this.props; + + const headers = [ + { + key: 'status', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.status', + defaultMessage: 'Status' + }) + }, + { + key: 'name', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.name', + defaultMessage: 'Name' + }) + }, + { + key: 'namespace', + header: 'Namespace' + }, + { + key: 'url', + header: 'Repository' + }, + { + key: 'revision', + header: 'Revision' + }, + { + key: 'serviceAccount', + header: 'Service account' + }, + { + key: 'createdTime', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.createdTime', + defaultMessage: 'Created' + }) + }, + { + key: 'duration', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.duration', + defaultMessage: 'Duration' + }) + } + ]; + + const getParam = (spec, key) => { + const param = spec.params.find(x => x.name === key); + if (param) { + return param.value; + } + return undefined; + }; + + const buildsFormatted = builds.map(build => { + const { lastTransitionTime, reason, status } = getStatus({ + status: build.status.pipelineRun + }); + let endTime = Date.now(); + if (status === 'False' || status === 'True') { + endTime = new Date(lastTransitionTime).getTime(); + } + return { + id: build.metadata.uid, + name: ( + + {build.metadata.name} + + ), + namespace: build.metadata.namespace, + url: getParam(build.spec.pipelineResourceSpec, 'url'), + revision: getParam(build.spec.pipelineResourceSpec, 'revision'), + serviceAccount: build.spec.serviceAccountName, + createdTime: ( + + ), + duration: ( + + ), + status: ( +
+
+ +
+
+ ) + }; + }); + + if (error) { + return ( + + ); + } + + return ( + <> +

Builds

+ +
+ + ); + } +} + +/* istanbul ignore next */ +function mapStateToProps(state, props) { + const { namespace: namespaceParam } = props.match.params; + const namespace = namespaceParam || getSelectedNamespace(state); + const filters = getFilters(props.location); + + return { + error: getBuildsErrorMessage(state), + filters, + builds: getBuilds(state, { filters, namespace }), + loading: isFetchingBuilds(state), + namespace, + webSocketConnected: isWebSocketConnected(state) + }; +} + +const mapDispatchToProps = { + fetchBuilds +}; + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Builds)); diff --git a/src/containers/Builds/index.js b/src/containers/Builds/index.js new file mode 100644 index 000000000..0a9664af1 --- /dev/null +++ b/src/containers/Builds/index.js @@ -0,0 +1,14 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +export { default } from './Builds'; diff --git a/src/containers/CreateProject/CreateProject.js b/src/containers/CreateProject/CreateProject.js new file mode 100644 index 000000000..ed2c6a0b5 --- /dev/null +++ b/src/containers/CreateProject/CreateProject.js @@ -0,0 +1,385 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import React from 'react'; +import { + Form, + FormGroup, + InlineNotification, + TextInput +} from 'carbon-components-react'; +import { ALL_NAMESPACES, generateId } from '@tektoncd/dashboard-utils'; +import { KeyValueList, Modal } from '@tektoncd/dashboard-components'; +import { injectIntl } from 'react-intl'; +import { NamespacesDropdown, ServiceAccountsDropdown } from '..'; +import { createProject } from '../../api'; +import { isValidLabel } from '../../utils'; + +import '../../scss/Create.scss'; + +const initialState = { + name: '', + namespace: '', + ingress: '', + ingressLabels: [], + invalidIngressLabels: {}, + ingressAnnotations: [], + invalidIngressAnnotations: {}, + bindings: [ + { + id: generateId(`label0-`), + key: 'gitrevision', + keyPlaceholder: 'key', + value: '$(body.head_commit.id)', + valuePlaceholder: 'value' + }, + { + id: generateId(`label1-`), + key: 'gitrepositoryurl', + keyPlaceholder: 'key', + value: '$(body.repository.url)', + valuePlaceholder: 'value' + } + ], + invalidBindings: {}, + serviceAccount: '', + submitError: '', + validationError: false +}; + +class CreateProject extends React.Component { + constructor(props) { + super(props); + this.state = this.initialState(); + } + + componentDidUpdate(prevProps) { + const { namespace, open } = this.props; + const { namespace: prevNamespace, open: prevOpen } = prevProps; + + if ((open && !prevOpen) || namespace !== prevNamespace) { + this.resetForm(); + } + } + + handleClose = () => { + this.props.onClose(); + }; + + handleNamespaceChange = ({ selectedItem }) => { + const { text = '' } = selectedItem || {}; + if (text !== this.state.namespace) { + this.setState({ + ...initialState, + namespace: text + }); + } + }; + + handleSubmit = event => { + event.preventDefault(); + + const { + name, + namespace, + ingress, + ingressLabels, + ingressAnnotations, + bindings, + serviceAccount + } = this.state; + + createProject({ + name, + namespace, + serviceAccount, + ingress, + ingressLabels: ingressLabels.reduce((acc, { key, value }) => { + acc[key] = value; + return acc; + }, {}), + ingressAnnotations: ingressAnnotations.reduce((acc, { key, value }) => { + acc[key] = value; + return acc; + }, {}), + bindings: bindings.reduce((acc, { key, value }) => { + acc[key] = value; + return acc; + }, {}), + interceptors: [ + { + github: { + eventTypes: ['push'] + } + } + ] + }) + .then(response => { + this.props.onSuccess(response); + }) + .catch(error => { + error.response.text().then(text => { + const statusCode = error.response.status; + let errorMessage = `error code ${statusCode}`; + if (text) { + errorMessage = `${text} (error code ${statusCode})`; + } + this.setState({ submitError: errorMessage }); + }); + }); + }; + + initialState = () => { + const { namespace } = this.props; + return { + ...initialState, + namespace: namespace !== ALL_NAMESPACES ? namespace : '' + }; + }; + + resetForm = () => { + this.setState(this.initialState()); + }; + + handleAddLabel = prop => { + this.setState(prevState => ({ + [prop]: [ + ...prevState[prop], + { + id: generateId(`label${prevState[prop].length}-`), + key: '', + keyPlaceholder: 'key', + value: '', + valuePlaceholder: 'value' + } + ] + })); + }; + + handleRemoveLabel = (prop, invalidProp, index) => { + this.setState(prevState => { + const labels = [...prevState[prop]]; + const invalidLabels = { ...prevState[invalidProp] }; + const removedLabel = labels[index]; + labels.splice(index, 1); + if (removedLabel.id in invalidLabels) { + delete invalidLabels[`${removedLabel.id}-key`]; + delete invalidLabels[`${removedLabel.id}-value`]; + } + return { + [prop]: labels, + [invalidProp]: invalidLabels + }; + }); + }; + + handleChangeLabel = (prop, invalidProp, { type, index, value }) => { + this.setState(prevState => { + const labels = [...prevState[prop]]; + labels[index][type] = value; + const invalidLabels = { ...prevState[invalidProp] }; + if (!isValidLabel(type, value)) { + invalidLabels[`${labels[index].id}-${type}`] = true; + } else { + delete invalidLabels[`${labels[index].id}-${type}`]; + } + return { + [prop]: labels, + [invalidProp]: invalidLabels + }; + }); + }; + + render() { + const { open, intl } = this.props; + const { + name, + namespace, + serviceAccount, + ingress, + ingressLabels, + invalidIngressLabels, + ingressAnnotations, + invalidIngressAnnotations, + bindings, + invalidBindings, + submitError, + validationError + } = this.state; + + return ( + + + {validationError && ( + + )} + {submitError !== '' && ( + this.setState({ submitError: '' })} + lowContrast + /> + )} + + + + + { + const { text } = selectedItem || {}; + this.setState({ serviceAccount: text }); + }} + /> + + + + this.setState({ name: value }) + } + /> + + + + this.setState({ ingress: value }) + } + /> + + + + this.handleChangeLabel( + 'ingressLabels', + 'invalidIngressLabels', + label + ) + } + onRemove={index => + this.handleRemoveLabel( + 'ingressLabels', + 'invalidIngressLabels', + index + ) + } + onAdd={() => this.handleAddLabel('ingressLabels')} + /> + + + + this.handleChangeLabel( + 'ingressAnnotations', + 'invalidIngressAnnotations', + label + ) + } + onRemove={index => + this.handleRemoveLabel( + 'ingressAnnotations', + 'invalidIngressAnnotations', + index + ) + } + onAdd={() => this.handleAddLabel('ingressAnnotations')} + /> + + + + this.handleChangeLabel('bindings', 'invalidBindings', label) + } + onRemove={index => + this.handleRemoveLabel('bindings', 'invalidBindings', index) + } + onAdd={() => this.handleAddLabel('bindings')} + /> + + + + ); + } +} + +CreateProject.defaultProps = { + open: false, + onClose: () => {}, + onSuccess: () => {} +}; + +export default injectIntl(CreateProject); diff --git a/src/containers/CreateProject/index.js b/src/containers/CreateProject/index.js new file mode 100644 index 000000000..110eeccc5 --- /dev/null +++ b/src/containers/CreateProject/index.js @@ -0,0 +1,14 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +export { default } from './CreateProject'; diff --git a/src/containers/Project/Project.js b/src/containers/Project/Project.js new file mode 100644 index 000000000..932a7bb8b --- /dev/null +++ b/src/containers/Project/Project.js @@ -0,0 +1,292 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import { injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { getStatus, getTitle, urls } from '@tektoncd/dashboard-utils'; +import { + FormattedDate, + FormattedDuration, + ResourceDetails, + StatusIcon, + Table +} from '@tektoncd/dashboard-components'; +import { + getProject, + getProjectsErrorMessage, + getSelectedNamespace, + isWebSocketConnected +} from '../../reducers'; +import { fetchProject } from '../../actions/projects'; + +export class ProjectContainer extends Component { + componentDidMount() { + const { match } = this.props; + const { projectName: resourceName } = match.params; + document.title = getTitle({ + page: 'Project', + resourceName + }); + this.fetchData(); + } + + componentDidUpdate(prevProps) { + const { match, webSocketConnected } = this.props; + const { namespace, projectName } = match.params; + const { + match: prevMatch, + webSocketConnected: prevWebSocketConnected + } = prevProps; + const { + namespace: prevNamespace, + projectName: prevProjectName + } = prevMatch.params; + + if ( + namespace !== prevNamespace || + projectName !== prevProjectName || + (webSocketConnected && prevWebSocketConnected === false) + ) { + this.fetchData(); + } + } + + getAdditionalContent() { + const { project, intl } = this.props; + if (!project) { + return null; + } + + const buildHeaders = [ + { + key: 'status', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.status', + defaultMessage: 'Status' + }) + }, + { + key: 'name', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.name', + defaultMessage: 'Name' + }) + }, + { + key: 'createdTime', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.createdTime', + defaultMessage: 'Created' + }) + }, + { + key: 'duration', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.duration', + defaultMessage: 'Duration' + }) + } + ]; + + const resourceHeaders = [ + { + key: 'name', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.name', + defaultMessage: 'Name' + }) + }, + { + key: 'value', + header: 'value' + } + ]; + + return ( + <> +
+ {project.metadata.name} + + ) + }, + { + id: 'event-listener-service', + name: 'Event listener service', + value: project.status.eventListener.configuration.generatedName + }, + { + id: 'event-listener-address', + name: 'Event listener address', + value: project.status.eventListener.address.url + }, + { + id: 'trigger-template', + name: 'Trigger template', + value: ( + + {project.metadata.name} + + ) + }, + { + id: 'trigger-binding', + name: 'Trigger binding', + value: ( + + {project.metadata.name} + + ) + } + ]} + /> + {project.status.builds && ( +
{ + console.log(project.status.builds[build].pipelineRun.startTime); + const { lastTransitionTime, reason, status } = getStatus({ + status: project.status.builds[build].pipelineRun + }); + let endTime = Date.now(); + if (status === 'False' || status === 'True') { + endTime = new Date(lastTransitionTime).getTime(); + } + return { + id: build, + name: ( + + {build} + + ), + status: ( +
+
+ +
+
+ ), + createdTime: ( + + ), + duration: ( + + ) + }; + })} + /> + )} + + ); + } + + fetchData() { + const { match } = this.props; + const { namespace, projectName } = match.params; + this.props.fetchProject({ name: projectName, namespace }); + } + + render() { + const { error, project } = this.props; + const additionalContent = this.getAdditionalContent(); + + return ( + + {additionalContent} + + ); + } +} + +ProjectContainer.propTypes = { + match: PropTypes.shape({ + params: PropTypes.shape({ + projectName: PropTypes.string.isRequired + }).isRequired + }).isRequired +}; + +/* istanbul ignore next */ +function mapStateToProps(state, ownProps) { + const { match } = ownProps; + const { namespace: namespaceParam, projectName } = match.params; + + const namespace = namespaceParam || getSelectedNamespace(state); + const project = getProject(state, { + name: projectName, + namespace + }); + return { + error: getProjectsErrorMessage(state), + project, + webSocketConnected: isWebSocketConnected(state) + }; +} + +const mapDispatchToProps = { + fetchProject +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(ProjectContainer)); diff --git a/src/containers/Project/index.js b/src/containers/Project/index.js new file mode 100644 index 000000000..0df21471b --- /dev/null +++ b/src/containers/Project/index.js @@ -0,0 +1,14 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +export { default } from './Project'; diff --git a/src/containers/Projects/Projects.js b/src/containers/Projects/Projects.js new file mode 100644 index 000000000..8f4622dea --- /dev/null +++ b/src/containers/Projects/Projects.js @@ -0,0 +1,304 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { injectIntl } from 'react-intl'; +import isEqual from 'lodash.isequal'; +import { Add16 as Add } from '@carbon/icons-react'; +import { + getErrorMessage, + getFilters, + getTitle, + urls +} from '@tektoncd/dashboard-utils'; +import { + FormattedDate, + RunDropdown, + Table +} from '@tektoncd/dashboard-components'; +import { InlineNotification } from 'carbon-components-react'; + +import { CreateProject, LabelFilter } from '..'; +import { fetchProjects } from '../../actions/projects'; +import { + getProjects, + getProjectsErrorMessage, + getSelectedNamespace, + isFetchingProjects, + isWebSocketConnected +} from '../../reducers'; +import { deleteProject } from '../../api'; + +const initialState = { + showCreateProject: false, + createdTaskRun: null, + submitError: '' +}; + +export class Projects extends Component { + constructor(props) { + super(props); + + // this.handleCreateTaskRunSuccess = this.handleCreateTaskRunSuccess.bind( + // this + // ); + + this.state = initialState; + } + + componentDidMount() { + document.title = getTitle({ page: 'Projects' }); + this.fetchData(); + } + + componentDidUpdate(prevProps) { + const { filters, namespace, webSocketConnected } = this.props; + const { + filters: prevFilters, + namespace: prevNamespace, + webSocketConnected: prevWebSocketConnected + } = prevProps; + + if ( + namespace !== prevNamespace || + (webSocketConnected && prevWebSocketConnected === false) || + !isEqual(filters, prevFilters) + ) { + this.fetchData(); + } + } + + toggleModal = showCreateProjectModal => { + this.setState({ showCreateProjectModal }); + }; + + deleteProject = project => { + const { name, namespace } = project.metadata; + deleteProject({ name, namespace }).catch(error => { + error.response.text().then(text => { + const statusCode = error.response.status; + let errorMessage = `error code ${statusCode}`; + if (text) { + errorMessage = `${text} (error code ${statusCode})`; + } + this.setState({ submitError: errorMessage }); + }); + }); + }; + + projectActions = () => { + const { intl } = this.props; + + return [ + { + actionText: 'Delete', + action: this.deleteProject, + danger: true, + modalProperties: { + danger: true, + heading: 'Delete TaskRun', + primaryButtonText: 'Delete', + secondaryButtonText: 'Cancel', + body: resource => + intl.formatMessage( + { + id: 'dashboard.deleteProject.body', + defaultMessage: + 'Are you sure you would like to delete Project {name}?' + }, + { name: resource.metadata.name } + ) + } + } + ]; + }; + + fetchData() { + const { filters, namespace } = this.props; + this.props.fetchProjects({ + filters, + namespace + }); + } + + render() { + const { + error, + projects, + loading, + intl, + namespace: selectedNamespace + } = this.props; + + const headers = [ + { + key: 'name', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.name', + defaultMessage: 'Name' + }) + }, + { + key: 'namespace', + header: 'Namespace' + }, + { + key: 'ingressURL', + header: 'Ingress URL' + }, + { + key: 'serviceAccount', + header: 'Service account' + }, + { + key: 'createdTime', + header: intl.formatMessage({ + id: 'dashboard.tableHeader.createdTime', + defaultMessage: 'Created' + }) + }, + { + key: 'actions', + header: '' + } + ]; + + const projectActions = this.projectActions(); + + const projectsFormatted = projects.map(project => ({ + id: project.metadata.uid, + name: ( + + {project.metadata.name} + + ), + namespace: project.metadata.namespace, + ingressURL: project.spec.ingress ? project.spec.ingress.host : null, + serviceAccount: project.spec.serviceAccountName, + createdTime: ( + + ), + actions: + })); + + if (error) { + return ( + + ); + } + + const toolbarButtons = [ + { + onClick: () => this.toggleModal(true), + text: intl.formatMessage({ + id: 'dashboard.actions.createButton', + defaultMessage: 'Create' + }), + icon: Add + } + ]; + + return ( + <> + {this.state.submitError && ( + + )} +

Projects

+ + this.toggleModal(false)} + onSuccess={() => this.toggleModal(false)} + namespace={selectedNamespace} + /> +
+ + ); + } +} + +/* istanbul ignore next */ +function mapStateToProps(state, props) { + const { namespace: namespaceParam } = props.match.params; + const namespace = namespaceParam || getSelectedNamespace(state); + const filters = getFilters(props.location); + + return { + error: getProjectsErrorMessage(state), + filters, + projects: getProjects(state, { filters, namespace }), + loading: isFetchingProjects(state), + namespace, + webSocketConnected: isWebSocketConnected(state) + }; +} + +const mapDispatchToProps = { + fetchProjects +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(Projects)); diff --git a/src/containers/Projects/index.js b/src/containers/Projects/index.js new file mode 100644 index 000000000..8015366fd --- /dev/null +++ b/src/containers/Projects/index.js @@ -0,0 +1,14 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +export { default } from './Projects'; diff --git a/src/containers/SideNav/SideNav.js b/src/containers/SideNav/SideNav.js index e7f74b7c2..643e50db7 100644 --- a/src/containers/SideNav/SideNav.js +++ b/src/containers/SideNav/SideNav.js @@ -145,6 +145,10 @@ class SideNav extends Component { this.setPath(urls.conditions.all()); return; } + if (currentURL.includes(urls.projects.all())) { + this.setPath(urls.projects.all()); + return; + } history.push('/'); }; @@ -160,6 +164,25 @@ class SideNav extends Component { aria-label="Main navigation" > + {this.props.isTriggersInstalled && ( + + } + to={this.getPath(urls.projects.all())} + > + Projects + + } + to={this.getPath(urls.builds.all())} + > + Builds + + + )} + createNamespacedReducer({ type: 'Build' }); + +export function getBuilds(state, namespace) { + return getCollection(state, namespace); +} + +export function getBuild(state, name, namespace) { + return getResource(state, name, namespace); +} + +export function getBuildsErrorMessage(state) { + return state.errorMessage; +} + +export function isFetchingBuilds(state) { + return state.isFetching; +} diff --git a/src/reducers/index.js b/src/reducers/index.js index 13a589ed4..0e4f5b53e 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -13,6 +13,7 @@ limitations under the License. import { combineReducers } from 'redux'; +import builds, * as buildsSelectors from './builds'; import clusterTasks, * as clusterTaskSelectors from './clusterTasks'; import conditions, * as conditionSelectors from './conditions'; import eventListeners, * as eventListenersSelectors from './eventListeners'; @@ -23,6 +24,7 @@ import notifications, * as notificationSelectors from './notifications'; import pipelines, * as pipelineSelectors from './pipelines'; import pipelineResources, * as pipelineResourcesSelectors from './pipelineResources'; import pipelineRuns, * as pipelineRunsSelectors from './pipelineRuns'; +import projects, * as projectsSelectors from './projects'; import properties, * as propertiesSelectors from './properties'; import secrets, * as secretSelectors from './secrets'; import triggerTemplates, * as triggerTemplatesSelectors from './triggerTemplates'; @@ -33,6 +35,7 @@ import tasks, * as taskSelectors from './tasks'; import taskRuns, * as taskRunsSelectors from './taskRuns'; export default combineReducers({ + builds: builds(), clusterTasks, conditions: conditions(), eventListeners: eventListeners(), @@ -43,6 +46,7 @@ export default combineReducers({ pipelines: pipelines(), pipelineResources: pipelineResources(), pipelineRuns: pipelineRuns(), + projects: projects(), properties, secrets, serviceAccounts: serviceAccounts(), @@ -322,6 +326,52 @@ export function isFetchingConditions(state) { return conditionSelectors.isFetchingConditions(state.conditions); } +export function getProject( + state, + { name, namespace = getSelectedNamespace(state) } +) { + return projectsSelectors.getProject(state.projects, name, namespace); +} + +export function getProjects( + state, + { filters = [], namespace = getSelectedNamespace(state) } = {} +) { + const resources = projectsSelectors.getProjects(state.projects, namespace); + return filterResources({ filters, resources }); +} + +export function getProjectsErrorMessage(state) { + return projectsSelectors.getProjectsErrorMessage(state.projects); +} + +export function isFetchingProjects(state) { + return projectsSelectors.isFetchingProjects(state.projects); +} + +export function getBuild( + state, + { name, namespace = getSelectedNamespace(state) } +) { + return buildsSelectors.getBuild(state.builds, name, namespace); +} + +export function getBuilds( + state, + { filters = [], namespace = getSelectedNamespace(state) } = {} +) { + const resources = buildsSelectors.getBuilds(state.builds, namespace); + return filterResources({ filters, resources }); +} + +export function getBuildsErrorMessage(state) { + return buildsSelectors.getBuildsErrorMessage(state.builds); +} + +export function isFetchingBuilds(state) { + return buildsSelectors.isFetchingBuilds(state.builds); +} + export function getSecrets( state, { filters = [], namespace = getSelectedNamespace(state) } = {} diff --git a/src/reducers/projects.js b/src/reducers/projects.js new file mode 100644 index 000000000..06ba1a3b6 --- /dev/null +++ b/src/reducers/projects.js @@ -0,0 +1,33 @@ +/* +Copyright 2020 The Tekton 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. +*/ + +import { createNamespacedReducer } from './reducerCreators'; +import { getCollection, getResource } from './selectorCreators'; + +export default () => createNamespacedReducer({ type: 'Project' }); + +export function getProjects(state, namespace) { + return getCollection(state, namespace); +} + +export function getProject(state, name, namespace) { + return getResource(state, name, namespace); +} + +export function getProjectsErrorMessage(state) { + return state.errorMessage; +} + +export function isFetchingProjects(state) { + return state.isFetching; +} diff --git a/test-project.sh b/test-project.sh new file mode 100755 index 000000000..5d9592439 --- /dev/null +++ b/test-project.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +curl --request POST \ + --header "Content-Type: application/json" \ + --header 'X-GitHub-Event: push' \ + --data '{"head_commit":{"id":"master"},"repository":{"url":"https://github.com/eddycharly/tekton-ci"}}' \ + http://tekton-ci.127.0.0.1.nip.io