├── .gcloudignore ├── eula.pdf ├── .gitignore ├── testfiles ├── serverless-simple.yaml ├── cfgfile-test.json └── serverless-test.yaml ├── .dockerignore ├── Dockerfile ├── main.go ├── pkg ├── resources │ ├── route │ │ ├── types.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── route_test.go │ │ └── list.go │ ├── revision │ │ ├── types.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── revision_test.go │ │ └── list.go │ ├── configuration │ │ ├── types.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── configuration_test.go │ │ └── list.go │ ├── clustertask │ │ ├── types.go │ │ ├── list.go │ │ └── get.go │ ├── channel │ │ ├── types.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── channel_test.go │ │ ├── create.go │ │ └── list.go │ ├── task │ │ ├── types.go │ │ ├── delete.go │ │ ├── list.go │ │ ├── task_test.go │ │ ├── get.go │ │ └── create.go │ ├── pipelineresource │ │ ├── types.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── pipelineresource_test.go │ │ └── create.go │ ├── taskrun │ │ ├── delete.go │ │ ├── types.go │ │ ├── get.go │ │ ├── taskrun_test.go │ │ ├── list.go │ │ └── create_test.go │ ├── credential │ │ ├── types.go │ │ ├── git-credentials.go │ │ └── registry-credentials.go │ └── service │ │ ├── delete.go │ │ ├── types.go │ │ ├── create_test.go │ │ ├── get.go │ │ ├── list.go │ │ ├── build_interface.go │ │ ├── pingsource.go │ │ ├── pingsource_test.go │ │ └── service_test.go ├── file │ ├── serverless_test.go │ ├── file_test.go │ ├── pod.go │ ├── serverless.go │ └── ops.go ├── log │ └── log.go ├── client │ ├── client_test.go │ └── client.go ├── generate │ ├── includes.go │ └── generate.go ├── printer │ └── printer.go └── push │ └── push.go ├── cloudbuild.yaml ├── scripts └── release-notes.sh ├── cmd ├── generate.go ├── push.go ├── set.go ├── cmd.go ├── delete.go ├── deploy.go └── get.go ├── Makefile ├── .circleci └── config.yml ├── go.mod └── HOWTO.md /.gcloudignore: -------------------------------------------------------------------------------- 1 | #!include:.dockerignore 2 | !Dockerfile 3 | -------------------------------------------------------------------------------- /eula.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/triggermesh/tm/HEAD/eula.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Continuous integration 2 | **/c.out 3 | **/tm-coverage.html 4 | **/tm-unit-tests.xml 5 | 6 | # Editors and IDEs 7 | *.swp 8 | *~ 9 | /*.sublime-project 10 | /.vscode/ 11 | 12 | # Build artifacts 13 | tm 14 | tm-darwin-amd64 15 | tm-linux-amd64 16 | tm-windows-amd64.exe 17 | -------------------------------------------------------------------------------- /testfiles/serverless-simple.yaml: -------------------------------------------------------------------------------- 1 | service: serverless-test 2 | description: "dry-run deployment test file" 3 | 4 | provider: 5 | name: triggermesh 6 | environment: 7 | FOO: BAR 8 | 9 | functions: 10 | bar: 11 | source: docker.io/hello-world 12 | description: "go function" 13 | environment: 14 | FUNCTION: bar 15 | -------------------------------------------------------------------------------- /testfiles/cfgfile-test.json: -------------------------------------------------------------------------------- 1 | {"apiVersion":"v1","clusters":[{"cluster":{"certificate-authority-data":"","server":"https://test.any"},"name":"triggermesh"}],"contexts":[{"context":{"cluster":"triggermesh","namespace":"testUserName","user":"testUserName"},"name":"default-context"}],"current-context":"default-context","kind":"Config","preferences":{},"users":[{"name":"testUserName","user":{"token":""}}]} -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Top level files and directories 2 | /.circleci/ 3 | /.dockerignore 4 | /.git/ 5 | /cloudbuild.yaml 6 | /doc/ 7 | /Dockerfile 8 | /scripts/ 9 | /testfiles/ 10 | 11 | # Patterns 12 | **/tm-coverage.html 13 | **/tm-unit-tests.xml 14 | **/README.md 15 | **/*_test.go 16 | **/.gitignore 17 | **/c.out 18 | 19 | # Binaries 20 | /tm 21 | /tm-darwin-amd64 22 | /tm-linux-amd64 23 | /tm-windows-amd64.exe 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18 AS build 2 | 3 | ENV CGO_ENABLED=0 4 | ENV GOOS=linux 5 | ENV GOARCH=amd64 6 | 7 | WORKDIR /tm 8 | 9 | COPY go.mod go.sum /tm/ 10 | RUN go mod download 11 | 12 | ARG GIT_TAG 13 | ENV GIT_TAG=${GIT_TAG:-unknown} 14 | 15 | COPY . . 16 | RUN make install && \ 17 | rm -rf ${GOPATH}/src && \ 18 | rm -rf ${HOME}/.cache 19 | 20 | FROM debian:stable-slim 21 | 22 | RUN apt-get update && \ 23 | apt-get install --no-install-recommends --no-install-suggests -y ca-certificates && \ 24 | rm -rf /var/lib/apt/lists/* 25 | 26 | COPY --from=build /go/bin/tm /bin/tm 27 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 TriggerMesh Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import "github.com/triggermesh/tm/cmd" 20 | 21 | func main() { 22 | cmd.Execute() 23 | } 24 | -------------------------------------------------------------------------------- /pkg/resources/route/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package route 15 | 16 | type Route struct { 17 | Name string 18 | Namespace string 19 | } 20 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: gcr.io/kaniko-project/executor:v1.3.0 3 | args: 4 | - --dockerfile=Dockerfile 5 | - --destination=gcr.io/$PROJECT_ID/tm:${COMMIT_SHA} 6 | - --destination=gcr.io/$PROJECT_ID/tm:${_KANIKO_IMAGE_TAG} 7 | - --cache-repo=gcr.io/$PROJECT_ID/tm/cache 8 | - --build-arg=GIT_TAG=${TAG_NAME} 9 | - --no-push=${_KANIKO_NO_PUSH} 10 | - --cache=${_KANIKO_USE_BUILD_CACHE} 11 | - --snapshotMode=redo 12 | - --use-new-run 13 | - ${_KANIKO_EXTRA_ARGS} 14 | waitFor: ['-'] 15 | 16 | timeout: 1800s 17 | 18 | substitutions: 19 | _KANIKO_IMAGE_TAG: "latest" 20 | _KANIKO_NO_PUSH: "false" 21 | _KANIKO_USE_BUILD_CACHE: "true" 22 | _KANIKO_EXTRA_ARGS: "" 23 | 24 | options: 25 | substitution_option: 'ALLOW_LOOSE' 26 | 27 | tags: 28 | - tm 29 | -------------------------------------------------------------------------------- /pkg/resources/revision/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package revision 16 | 17 | type Revision struct { 18 | Name string 19 | Namespace string 20 | } 21 | -------------------------------------------------------------------------------- /pkg/resources/configuration/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package configuration 16 | 17 | type Configuration struct { 18 | Name string 19 | Namespace string 20 | } 21 | -------------------------------------------------------------------------------- /pkg/resources/clustertask/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clustertask 16 | 17 | // ClusterTask represents tekton ClusterTask object 18 | type ClusterTask struct { 19 | Name string 20 | } 21 | -------------------------------------------------------------------------------- /pkg/resources/channel/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package channel 16 | 17 | // Channel represents knative channel object 18 | type Channel struct { 19 | Name string 20 | Namespace string 21 | } 22 | -------------------------------------------------------------------------------- /pkg/resources/task/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package task 16 | 17 | // Task represents tekton Task object 18 | type Task struct { 19 | File string 20 | GenerateName string 21 | Name string 22 | Namespace string 23 | FromLocalSource bool 24 | } 25 | -------------------------------------------------------------------------------- /pkg/resources/pipelineresource/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pipelineresource 16 | 17 | // Pipeline represents tekton Pipeline object 18 | type PipelineResource struct { 19 | Name string 20 | Namespace string 21 | Source Git 22 | } 23 | 24 | type Git struct { 25 | URL string 26 | Revision string 27 | } 28 | -------------------------------------------------------------------------------- /pkg/resources/task/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package task 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/triggermesh/tm/pkg/client" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | func (t *Task) Delete(clientset *client.ConfigSet) error { 25 | return clientset.TektonTasks.TektonV1beta1().Tasks(t.Namespace).Delete(context.Background(), t.Name, metav1.DeleteOptions{}) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/resources/taskrun/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package taskrun 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/triggermesh/tm/pkg/client" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | func (tr *TaskRun) Delete(clientset *client.ConfigSet) error { 25 | return clientset.TektonTasks.TektonV1beta1().TaskRuns(tr.Namespace).Delete(context.Background(), tr.Name, metav1.DeleteOptions{}) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/resources/configuration/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package configuration 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/triggermesh/tm/pkg/client" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | func (c *Configuration) Delete(clientset *client.ConfigSet) error { 25 | return clientset.Serving.ServingV1().Configurations(c.Namespace).Delete(context.Background(), c.Name, metav1.DeleteOptions{}) 26 | } 27 | -------------------------------------------------------------------------------- /testfiles/serverless-test.yaml: -------------------------------------------------------------------------------- 1 | service: serverless-foo 2 | description: "serverless.yaml parsing test" 3 | 4 | provider: 5 | name: triggermesh 6 | runtime: https://raw.githubusercontent.com/triggermesh/openfaas-runtime/master/go/openfaas-go-runtime.yaml 7 | environment: 8 | FOO: BAR 9 | 10 | functions: 11 | bar: 12 | handler: bar/main.go 13 | environment: 14 | FUNCTION: bar 15 | 16 | nodejs: 17 | handler: https://github.com/openfaas/faas 18 | runtime: https://raw.githubusercontent.com/triggermesh/openfaas-runtime/master/nodejs/openfaas-nodejs-runtime.yaml 19 | description: "nodejs fragment" 20 | buildargs: 21 | - DIRECTORY=sample-functions/BaseFunctions/node 22 | environment: 23 | FUNCTION: nodejs 24 | 25 | remote: 26 | handler: https://gitlab.com/tzununbekov/tesfunc 27 | description: "Go function with another repository as source" 28 | environment: 29 | FUNCTION: remote 30 | 31 | include: 32 | - foo/single-function.yaml 33 | - https://github.com/tzununbekov/serverless-include 34 | -------------------------------------------------------------------------------- /pkg/resources/route/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package route 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/triggermesh/tm/pkg/client" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // Route removes knative route object 25 | func (r *Route) Delete(clientset *client.ConfigSet) error { 26 | return clientset.Serving.ServingV1().Routes(r.Namespace).Delete(context.Background(), r.Name, metav1.DeleteOptions{}) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/resources/credential/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package credential 16 | 17 | type GitCreds struct { 18 | Namespace string 19 | // Host string 20 | Key string 21 | } 22 | 23 | // RegistryCreds contains docker registry credentials 24 | type RegistryCreds struct { 25 | Name string 26 | Namespace string 27 | Host string 28 | ProjectID string 29 | Username string 30 | Password string 31 | Pull bool 32 | Push bool 33 | } 34 | -------------------------------------------------------------------------------- /pkg/resources/channel/delete.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 TriggerMesh Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package channel 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/triggermesh/tm/pkg/client" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | ) 25 | 26 | // Delete removes knative inmemory channel object 27 | func (c *Channel) Delete(clientset *client.ConfigSet) error { 28 | return clientset.Eventing.MessagingV1().InMemoryChannels(c.Namespace).Delete(context.Background(), c.Name, metav1.DeleteOptions{}) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/resources/service/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/triggermesh/tm/pkg/client" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // Delete removes knative service object 25 | func (s *Service) Delete(clientset *client.ConfigSet) error { 26 | return clientset.Serving.ServingV1().Services(s.Namespace).Delete(context.Background(), s.Name, metav1.DeleteOptions{}) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/resources/revision/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package revision 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/triggermesh/tm/pkg/client" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // Revision remove knative revision object 25 | func (r *Revision) Delete(clientset *client.ConfigSet) error { 26 | return clientset.Serving.ServingV1().Revisions(r.Namespace).Delete(context.Background(), r.Name, metav1.DeleteOptions{}) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/file/serverless_test.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/spf13/afero" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestParseManifestValid(t *testing.T) { 13 | fixture, err := ioutil.ReadFile("../../testfiles/serverless-test.yaml") 14 | require.NoError(t, err) 15 | 16 | Aos = afero.NewMemMapFs() 17 | 18 | err = afero.WriteFile(Aos, "my-file.yml", fixture, 664) 19 | require.NoError(t, err) 20 | 21 | definition, err := ParseManifest("my-file.yml") 22 | require.NoError(t, err) 23 | 24 | assert.Equal(t, "serverless-foo", definition.Service) 25 | assert.Equal(t, ".", definition.Repository) 26 | } 27 | 28 | func TestParseManifestInvalid(t *testing.T) { 29 | Aos = afero.NewMemMapFs() 30 | 31 | err := afero.WriteFile(Aos, "my-file.yml", []byte("invalid-yaml"), 664) 32 | require.NoError(t, err) 33 | 34 | definition, err := ParseManifest("my-file.yml") 35 | 36 | assert.Contains(t, err.Error(), "yaml: unmarshal errors") 37 | assert.Empty(t, definition.Service) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/resources/pipelineresource/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pipelineresource 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/triggermesh/tm/pkg/client" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | func (plr *PipelineResource) Delete(clientset *client.ConfigSet) error { 25 | return clientset.TektonPipelines.TektonV1alpha1().PipelineResources(plr.Namespace).Delete(context.Background(), plr.Name, metav1.DeleteOptions{}) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/resources/clustertask/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clustertask 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 21 | "github.com/triggermesh/tm/pkg/client" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | ) 24 | 25 | // List return tekton ClusterTaskList object 26 | func (ct *ClusterTask) List(clientset *client.ConfigSet) (*v1beta1.ClusterTaskList, error) { 27 | return clientset.TektonTasks.TektonV1beta1().ClusterTasks().List(context.Background(), metav1.ListOptions{}) 28 | } 29 | -------------------------------------------------------------------------------- /scripts/release-notes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | RELEASE=${GIT_TAG:-$1} 4 | 5 | if [ -z "${RELEASE}" ]; then 6 | echo "Usage:" 7 | echo "./scripts/release-notes.sh v0.1.0" 8 | exit 1 9 | fi 10 | 11 | if ! git rev-list ${RELEASE} >/dev/null 2>&1; then 12 | echo "${RELEASE} does not exist" 13 | exit 14 | fi 15 | 16 | PREV_RELEASE=${PREV_RELEASE:-$(git describe --tags --abbrev=0 ${RELEASE}^)} 17 | PREV_RELEASE=${PREV_RELEASE:-$(git rev-list --max-parents=0 ${RELEASE}^)} 18 | NOTABLE_CHANGES=$(git cat-file -p ${RELEASE} | sed '/-----BEGIN PGP SIGNATURE-----/,//d' | tail -n +6) 19 | CHANGELOG=$(git log --no-merges --pretty=format:'- [%h] %s (%aN)' ${PREV_RELEASE}..${RELEASE}) 20 | if [ $? -ne 0 ]; then 21 | echo "Error creating changelog" 22 | exit 1 23 | fi 24 | 25 | cat < 0 { 54 | // image = item.Spec.Containers[0].Image 55 | // } 56 | age := duration.HumanDuration(time.Since(item.GetCreationTimestamp().Time)) 57 | ready := fmt.Sprintf("%v", item.IsReady()) 58 | readyCondition := item.Status.GetCondition(servingv1.RevisionConditionReady) 59 | reason := "" 60 | if readyCondition != nil { 61 | reason = readyCondition.Reason 62 | } 63 | 64 | row := []string{ 65 | namespace, 66 | name, 67 | // image, 68 | age, 69 | ready, 70 | reason, 71 | } 72 | 73 | return row 74 | } 75 | 76 | // List returns k8s list object 77 | func (r *Revision) List(clientset *client.ConfigSet) (*servingv1.RevisionList, error) { 78 | return clientset.Serving.ServingV1().Revisions(r.Namespace).List(context.Background(), metav1.ListOptions{}) 79 | } 80 | -------------------------------------------------------------------------------- /pkg/resources/service/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "time" 21 | 22 | "github.com/triggermesh/tm/pkg/client" 23 | "github.com/triggermesh/tm/pkg/printer" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/util/duration" 26 | servingv1 "knative.dev/serving/pkg/apis/serving/v1" 27 | ) 28 | 29 | // GetTable converts k8s list instance into printable object 30 | func (s *Service) GetTable(list *servingv1.ServiceList) printer.Table { 31 | table := printer.Table{ 32 | Headers: []string{ 33 | "Namespace", 34 | "Name", 35 | "Url", 36 | "Age", 37 | "Ready", 38 | "Reason", 39 | }, 40 | Rows: make([][]string, 0, len(list.Items)), 41 | } 42 | 43 | for _, item := range list.Items { 44 | table.Rows = append(table.Rows, s.row(&item)) 45 | } 46 | return table 47 | } 48 | 49 | func (s *Service) row(item *servingv1.Service) []string { 50 | name := item.Name 51 | namespace := item.Namespace 52 | url := item.Status.URL.String() 53 | // lastestRevision := item.Status.ConfigurationStatusFields.LatestReadyRevisionName 54 | age := duration.HumanDuration(time.Since(item.GetCreationTimestamp().Time)) 55 | ready := fmt.Sprintf("%v", item.IsReady()) 56 | readyCondition := item.Status.GetCondition(servingv1.ServiceConditionReady) 57 | reason := "" 58 | if readyCondition != nil { 59 | reason = readyCondition.Reason 60 | } 61 | 62 | row := []string{ 63 | namespace, 64 | name, 65 | url, 66 | // lastestRevision, 67 | age, 68 | ready, 69 | reason, 70 | } 71 | 72 | return row 73 | } 74 | 75 | // List returns k8s list object 76 | func (s *Service) List(clientset *client.ConfigSet) (*servingv1.ServiceList, error) { 77 | return clientset.Serving.ServingV1().Services(s.Namespace).List(context.Background(), metav1.ListOptions{}) 78 | } 79 | -------------------------------------------------------------------------------- /pkg/resources/configuration/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package configuration 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "time" 21 | 22 | "github.com/triggermesh/tm/pkg/client" 23 | "github.com/triggermesh/tm/pkg/printer" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/util/duration" 26 | servingv1 "knative.dev/serving/pkg/apis/serving/v1" 27 | ) 28 | 29 | // GetTable converts k8s list instance into printable object 30 | func (cf *Configuration) GetTable(list *servingv1.ConfigurationList) printer.Table { 31 | table := printer.Table{ 32 | Headers: []string{ 33 | "Namespace", 34 | "Name", 35 | // "Image", 36 | "Age", 37 | "Ready", 38 | "Reason", 39 | }, 40 | Rows: make([][]string, 0, len(list.Items)), 41 | } 42 | 43 | for _, item := range list.Items { 44 | table.Rows = append(table.Rows, cf.row(&item)) 45 | } 46 | return table 47 | } 48 | 49 | func (cf *Configuration) row(item *servingv1.Configuration) []string { 50 | name := item.Name 51 | namespace := item.Namespace 52 | // image := "" 53 | // if len(item.Spec.Template.Spec.Containers) > 0 { 54 | // image = item.Spec.Template.Spec.Containers[0].Image 55 | // } 56 | age := duration.HumanDuration(time.Since(item.GetCreationTimestamp().Time)) 57 | ready := fmt.Sprintf("%v", item.IsReady()) 58 | readyCondition := item.Status.GetCondition(servingv1.ConfigurationConditionReady) 59 | reason := "" 60 | if readyCondition != nil { 61 | reason = readyCondition.Reason 62 | } 63 | 64 | row := []string{ 65 | namespace, 66 | name, 67 | // image, 68 | age, 69 | ready, 70 | reason, 71 | } 72 | 73 | return row 74 | } 75 | 76 | // List returns k8s list object 77 | func (cf *Configuration) List(clientset *client.ConfigSet) (*servingv1.ConfigurationList, error) { 78 | return clientset.Serving.ServingV1().Configurations(cf.Namespace).List(context.Background(), metav1.ListOptions{}) 79 | } 80 | -------------------------------------------------------------------------------- /pkg/resources/service/build_interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "github.com/triggermesh/tm/pkg/client" 19 | "github.com/triggermesh/tm/pkg/file" 20 | "github.com/triggermesh/tm/pkg/resources/clustertask" 21 | "github.com/triggermesh/tm/pkg/resources/task" 22 | "github.com/triggermesh/tm/pkg/resources/taskrun" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | ) 25 | 26 | // Builder interface contains image build methods which are common for both 27 | // tekton pipelines and knative builds 28 | type Builder interface { 29 | Deploy(clientset *client.ConfigSet) (string, error) 30 | SetOwner(clientset *client.ConfigSet, owner metav1.OwnerReference) error 31 | Delete(clientset *client.ConfigSet) error 32 | } 33 | 34 | // NewBuilder checks Service build method (tekton task) 35 | // and returns corresponding builder interface 36 | func NewBuilder(clientset *client.ConfigSet, s *Service) Builder { 37 | if !file.IsLocal(s.Source) && !file.IsGit(s.Source) { 38 | clientset.Log.Debugf("source %q is not local file nor git URL\n", s.Source) 39 | return nil 40 | } 41 | 42 | if task.Exist(clientset, s.Runtime) || 43 | clustertask.Exist(clientset, s.Runtime) { 44 | return s.taskRun() 45 | } 46 | 47 | if file.IsRemote(s.Runtime) { 48 | clientset.Log.Debugf("runtime %q is seemed to be a remote file, downloading", s.Runtime) 49 | if localFile, err := file.Download(s.Runtime); err != nil { 50 | clientset.Log.Warnf("Warning! Cannot fetch runtime: %s\n", err) 51 | } else { 52 | s.Runtime = localFile 53 | } 54 | } 55 | 56 | return s.taskRun() 57 | } 58 | 59 | func (s *Service) taskRun() *taskrun.TaskRun { 60 | return &taskrun.TaskRun{ 61 | Name: s.Name, 62 | Namespace: s.Namespace, 63 | Params: s.BuildArgs, 64 | Function: taskrun.Source{ 65 | Path: s.Source, 66 | Revision: s.Revision, 67 | }, 68 | Task: taskrun.Resource{ 69 | Name: s.Runtime, 70 | }, 71 | Timeout: s.BuildTimeout, 72 | Wait: true, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pkg/resources/taskrun/create_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package taskrun 16 | 17 | import ( 18 | "errors" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/triggermesh/tm/pkg/client" 23 | ) 24 | 25 | func TestTaskRunDryDeployment(t *testing.T) { 26 | namespace := "test-namespace" 27 | // if ns, ok := os.LookupEnv("NAMESPACE"); ok { 28 | // namespace = ns 29 | // } 30 | 31 | client.Dry = true 32 | clientset, err := client.NewClient("../../../testfiles/cfgfile-test.json") 33 | assert.NoError(t, err) 34 | 35 | cases := []struct { 36 | taskrun TaskRun 37 | err error 38 | }{{ 39 | taskrun: TaskRun{ 40 | Name: "test-taskrun-success", 41 | Namespace: namespace, 42 | Task: Resource{ 43 | Name: "foo", 44 | }, 45 | }, 46 | err: nil, 47 | }, { 48 | taskrun: TaskRun{ 49 | Name: "", 50 | Namespace: namespace, 51 | }, 52 | err: errors.New("taskrun name cannot be empty"), 53 | }, { 54 | taskrun: TaskRun{ 55 | Name: "test-taskrun-empty-taskname", 56 | Namespace: namespace, 57 | Task: Resource{ 58 | Name: "", 59 | }, 60 | PipelineResource: Resource{ 61 | Name: "foo", 62 | }, 63 | }, 64 | err: errors.New("task name cannot be empty"), 65 | }, { 66 | taskrun: TaskRun{ 67 | Name: "test-taskrun-missing-resource", 68 | Namespace: namespace, 69 | Task: Resource{ 70 | Name: "foo", 71 | }, 72 | PipelineResource: Resource{ 73 | Name: "not-existing-resource", 74 | }, 75 | Function: Source{ 76 | Path: "https://github.com/golang/example", 77 | }, 78 | }, 79 | err: errors.New("pipelineresource \"not-existing-resource\" not found"), 80 | }} 81 | 82 | for _, tr := range cases { 83 | output, err := tr.taskrun.Deploy(&clientset) 84 | t.Logf("%s\n%s", tr.taskrun.Name, output) 85 | assert.Equal(t, tr.err, err) 86 | if output != "" { 87 | assert.Contains(t, output, "\"kind\": \"TaskRun\"") 88 | assert.Contains(t, output, "\"apiVersion\": \"tekton.dev/v1beta1\"") 89 | assert.Contains(t, output, "\"value\": \"knative.registry.svc.cluster.local/test-namespace/"+tr.taskrun.Name) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pkg/resources/service/pingsource.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/types" 24 | 25 | sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" 26 | duckv1 "knative.dev/pkg/apis/duck/v1" 27 | "knative.dev/pkg/kmeta" 28 | 29 | "github.com/triggermesh/tm/pkg/client" 30 | ) 31 | 32 | const serviceLabelKey = "cli.triggermesh.io/service" 33 | 34 | func (s *Service) pingSource(schedule, data string, owner kmeta.OwnerRefable) *sourcesv1.PingSource { 35 | return &sourcesv1.PingSource{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | GenerateName: s.Name + "-", 38 | Namespace: s.Namespace, 39 | Labels: map[string]string{ 40 | serviceLabelKey: s.Name, 41 | }, 42 | OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(owner)}, 43 | }, 44 | Spec: sourcesv1.PingSourceSpec{ 45 | Schedule: schedule, 46 | Data: data, 47 | SourceSpec: duckv1.SourceSpec{ 48 | Sink: duckv1.Destination{ 49 | Ref: &duckv1.KReference{ 50 | APIVersion: owner.GetGroupVersionKind().Version, 51 | Kind: owner.GetGroupVersionKind().Kind, 52 | Name: owner.GetObjectMeta().GetName(), 53 | Namespace: owner.GetObjectMeta().GetNamespace(), 54 | }, 55 | }, 56 | }, 57 | }, 58 | } 59 | } 60 | 61 | func (s *Service) createPingSource(ps *sourcesv1.PingSource, clientset *client.ConfigSet) error { 62 | _, err := clientset.Eventing.SourcesV1().PingSources(ps.Namespace).Create(context.Background(), ps, metav1.CreateOptions{}) 63 | if err != nil { 64 | return fmt.Errorf("cannot create PingSource %q: %w", ps.Name, err) 65 | } 66 | return nil 67 | } 68 | 69 | func (s *Service) removePingSources(uid types.UID, clientset *client.ConfigSet) error { 70 | err := clientset.Eventing.SourcesV1().PingSources(s.Namespace).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{ 71 | LabelSelector: serviceLabelKey + "=" + s.Name, 72 | }) 73 | if err != nil { 74 | if k8serrors.IsNotFound(err) { 75 | return nil 76 | } 77 | return fmt.Errorf("cannot remove owned PingSources: %w", err) 78 | } 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/resources/pipelineresource/pipelineresource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pipelineresource 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/triggermesh/tm/pkg/client" 23 | ) 24 | 25 | // func TestCreate(t *testing.T) { 26 | // namespace := "test-namespace" 27 | // if ns, ok := os.LookupEnv("NAMESPACE"); ok { 28 | // namespace = ns 29 | // } 30 | // testClient, err := client.NewClient(client.ConfigPath("")) 31 | // assert.NoError(t, err) 32 | 33 | // pipeline := &PipelineResource{Name: "foo-bar", Namespace: namespace} 34 | 35 | // err = pipeline.Deploy(&testClient) 36 | // assert.NoError(t, err) 37 | 38 | // pipeline = &PipelineResource{Name: "foo-bar", Namespace: namespace} 39 | 40 | // err = pipeline.Deploy(&testClient) 41 | // assert.Error(t, err) 42 | 43 | // result, err := pipeline.Get(&testClient) 44 | // assert.NoError(t, err) 45 | // assert.Equal(t, "foo-bar", result.Name) 46 | 47 | // err = pipeline.Delete(&testClient) 48 | // assert.NoError(t, err) 49 | // } 50 | 51 | func TestList(t *testing.T) { 52 | namespace := "test-namespace" 53 | if ns, ok := os.LookupEnv("NAMESPACE"); ok { 54 | namespace = ns 55 | } 56 | testClient, err := client.NewClient(client.ConfigPath("")) 57 | assert.NoError(t, err) 58 | 59 | pipeline := &PipelineResource{Name: "Foo", Namespace: namespace} 60 | 61 | _, err = pipeline.List(&testClient) 62 | assert.NoError(t, err) 63 | } 64 | 65 | func TestGet(t *testing.T) { 66 | namespace := "test-namespace" 67 | if ns, ok := os.LookupEnv("NAMESPACE"); ok { 68 | namespace = ns 69 | } 70 | testClient, err := client.NewClient(client.ConfigPath("")) 71 | assert.NoError(t, err) 72 | 73 | pipeline := &PipelineResource{Name: "Foo", Namespace: namespace} 74 | result, err := pipeline.Get(&testClient) 75 | assert.Error(t, err) 76 | assert.Equal(t, "", result.Name) 77 | } 78 | 79 | func TestDelete(t *testing.T) { 80 | namespace := "test-namespace" 81 | if ns, ok := os.LookupEnv("NAMESPACE"); ok { 82 | namespace = ns 83 | } 84 | testClient, err := client.NewClient(client.ConfigPath("")) 85 | assert.NoError(t, err) 86 | 87 | pipeline := &PipelineResource{Name: "Foo", Namespace: namespace} 88 | err = pipeline.Delete(&testClient) 89 | assert.Error(t, err) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/client/client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 TriggerMesh Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package client 15 | 16 | import ( 17 | "io/ioutil" 18 | "os" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestUsername(t *testing.T) { 25 | assert := assert.New(t) 26 | 27 | c := []byte(`{"apiVersion":"v1","clusters":[{"cluster":{"certificate-authority-data":"==","server":""},"name":"test"}],"contexts":[{"context":{"cluster":"triggermesh","namespace":"testnamespace","user":"testuser"},"name":"default-context"}],"current-context":"default-context","kind":"Config","preferences":{},"users":[{"name":"testuser","user":{"token":""}}]}`) 28 | d := []byte(`{"apiVersion":"v1","clusters":[{"cluster":{"certificate-authority-data":"==","server":""},"name":"test"}],"contexts":[{"context":{"cluster":"test","namespace":"default","user":"testuser"},"name":"default-context"}],"current-context":"default-context","kind":"Config","preferences":{},"users":[{"name":"testuser","user":{"token":""}}]}`) 29 | 30 | ioutil.WriteFile("config.json", c, 0644) 31 | 32 | ioutil.WriteFile("default.json", d, 0644) 33 | 34 | testCases := []struct { 35 | input string 36 | output string 37 | }{ 38 | {"", "default"}, 39 | {"random.json", "default"}, 40 | {"config.json", "testnamespace"}, 41 | {"default.json", "default"}, 42 | } 43 | 44 | for _, tc := range testCases { 45 | namespace := getNamespace(tc.input) 46 | assert.Equal(tc.output, namespace) 47 | } 48 | 49 | os.Remove("config.json") 50 | os.Remove("default.json") 51 | } 52 | 53 | func TestNewClient(t *testing.T) { 54 | _, err := NewClient("../../testfiles/cfgfile-test.json") 55 | assert.NoError(t, err) 56 | 57 | _, err = NewClient("") 58 | assert.Error(t, err) 59 | } 60 | 61 | func TestConfigPath(t *testing.T) { 62 | // path := ConfigPath("") 63 | // home := os.Getenv("HOME") 64 | // assert.Equal(t, home+"/.tm/config.json", path) 65 | 66 | path := ConfigPath("../../testfiles/cfgfile-test.json") 67 | assert.Equal(t, "../../testfiles/cfgfile-test.json", path) 68 | 69 | // os.Setenv("KUBECONFIG", "../../testfiles/cfgfile-test.json") 70 | // path = ConfigPath("") 71 | // assert.Equal(t, home+"/.tm/config.json", path) 72 | // os.Unsetenv("KUBECONFIG") 73 | } 74 | 75 | func TestGetInClusterNamespace(t *testing.T) { 76 | namespace := getInClusterNamespace() 77 | assert.Equal(t, "default", namespace) 78 | } 79 | -------------------------------------------------------------------------------- /pkg/resources/credential/git-credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package credential 16 | 17 | import ( 18 | "bufio" 19 | "context" 20 | "fmt" 21 | "os" 22 | 23 | "github.com/triggermesh/tm/pkg/client" 24 | corev1 "k8s.io/api/core/v1" 25 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | ) 28 | 29 | var secretName = "git-ssh-key" 30 | 31 | func (g *GitCreds) CreateGitCreds(clientset *client.ConfigSet) error { 32 | if len(g.Key) == 0 { 33 | g.readStdin() 34 | } 35 | secretData := map[string]string{ 36 | "ssh-privatekey": g.Key, 37 | } 38 | secret := corev1.Secret{ 39 | Type: "kubernetes.io/ssh-auth", 40 | TypeMeta: metav1.TypeMeta{ 41 | Kind: "Secret", 42 | APIVersion: "v1", 43 | }, 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: secretName, 46 | Namespace: g.Namespace, 47 | Annotations: map[string]string{ 48 | "build.knative.dev/git-0": "*", 49 | }, 50 | }, 51 | StringData: secretData, 52 | } 53 | ctx := context.Background() 54 | _, err := clientset.Core.CoreV1().Secrets(client.Namespace).Create(ctx, &secret, metav1.CreateOptions{}) 55 | if k8serrors.IsAlreadyExists(err) { 56 | oldSecret, err := clientset.Core.CoreV1().Secrets(client.Namespace).Get(ctx, secretName, metav1.GetOptions{}) 57 | if err != nil { 58 | return err 59 | } 60 | oldSecret.StringData = secretData 61 | if _, err := clientset.Core.CoreV1().Secrets(client.Namespace).Update(ctx, oldSecret, metav1.UpdateOptions{}); err != nil { 62 | return err 63 | } 64 | } else if err != nil { 65 | return err 66 | } 67 | sa, err := clientset.Core.CoreV1().ServiceAccounts(client.Namespace).Get(ctx, "default", metav1.GetOptions{}) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | for _, v := range sa.Secrets { 73 | if v.Name == secretName { 74 | return nil 75 | } 76 | } 77 | sa.Secrets = append(sa.Secrets, corev1.ObjectReference{ 78 | Name: secretName, 79 | Namespace: client.Namespace, 80 | }) 81 | _, err = clientset.Core.CoreV1().ServiceAccounts(client.Namespace).Update(ctx, sa, metav1.UpdateOptions{}) 82 | return err 83 | } 84 | 85 | func (g *GitCreds) readStdin() { 86 | var key string 87 | fmt.Printf("SSH key:\n") 88 | scanner := bufio.NewScanner(os.Stdin) 89 | for scanner.Scan() { 90 | line := scanner.Text() 91 | key = fmt.Sprintf("%s\n%s", key, line) 92 | } 93 | g.Key = key 94 | } 95 | -------------------------------------------------------------------------------- /cmd/set.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "log" 19 | 20 | "github.com/spf13/cobra" 21 | "github.com/triggermesh/tm/pkg/client" 22 | ) 23 | 24 | // setCmd represents the set command 25 | var setCmd = &cobra.Command{ 26 | Use: "set", 27 | Short: "Set resource parameters", 28 | } 29 | 30 | func newSetCmd(clientset *client.ConfigSet) *cobra.Command { 31 | setCmd.AddCommand(cmdSetRegistryCreds(clientset)) 32 | setCmd.AddCommand(cmdSetGitCreds(clientset)) 33 | return setCmd 34 | } 35 | 36 | func cmdSetRegistryCreds(clientset *client.ConfigSet) *cobra.Command { 37 | setRegistryCredsCmd := &cobra.Command{ 38 | Use: "registry-auth", 39 | Short: "Create secret with registry credentials", 40 | Args: cobra.ExactArgs(1), 41 | Run: func(cmd *cobra.Command, args []string) { 42 | rc.Name = args[0] 43 | rc.Namespace = client.Namespace 44 | if err := rc.CreateRegistryCreds(clientset); err != nil { 45 | log.Fatalln(err) 46 | } 47 | clientset.Log.Infoln("Registry credentials set") 48 | }, 49 | } 50 | 51 | setRegistryCredsCmd.Flags().StringVar(&rc.Host, "registry", "", "Registry host address") 52 | setRegistryCredsCmd.Flags().StringVar(&rc.ProjectID, "project", "", "If set, use this value instead of the username in image names. Example: gcr.io/") 53 | setRegistryCredsCmd.Flags().StringVar(&rc.Username, "username", "", "Registry username") 54 | setRegistryCredsCmd.Flags().StringVar(&rc.Password, "password", "", "Registry password") 55 | setRegistryCredsCmd.Flags().BoolVar(&rc.Pull, "pull", false, "Indicates if this token must be used for pull operations only") 56 | setRegistryCredsCmd.Flags().BoolVar(&rc.Push, "push", false, "Indicates if this token must be used for push operations only") 57 | return setRegistryCredsCmd 58 | } 59 | 60 | func cmdSetGitCreds(clientset *client.ConfigSet) *cobra.Command { 61 | setGitCredsCmd := &cobra.Command{ 62 | Use: "git-auth", 63 | Short: "Create secret with git credentials", 64 | Run: func(cmd *cobra.Command, args []string) { 65 | gc.Namespace = client.Namespace 66 | if err := gc.CreateGitCreds(clientset); err != nil { 67 | log.Fatalln(err) 68 | } 69 | clientset.Log.Infoln("Git credentials created") 70 | }, 71 | } 72 | 73 | // Use one ssh key for all git sources for now 74 | // setGitCredsCmd.Flags().StringVar(&g.Host, "host", "", "Git host address") 75 | // setGitCredsCmd.Flags().StringVar(&g.Key, "key", "", "SSH private key") 76 | return setGitCredsCmd 77 | } 78 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE = tm 2 | PACKAGE_DESC = Triggermesh CLI 3 | TARGETS ?= darwin/amd64 linux/amd64 windows/amd64 4 | DIST_DIR ?= 5 | 6 | GIT_REPO = github.com/triggermesh/tm 7 | GIT_TAG ?= $(shell git for-each-ref refs/tags --sort=-taggerdate --format='%(refname:short)' --count=1) 8 | 9 | DOCKER = docker 10 | IMAGE_REPO ?= gcr.io/triggermesh 11 | IMAGE ?= $(IMAGE_REPO)/$(shell basename $(GIT_REPO)):$(GIT_TAG) 12 | 13 | GO ?= go 14 | GOFMT ?= gofmt 15 | GOLINT ?= golint 16 | GOTEST ?= gotestsum --junitfile $(OUTPUT_DIR)$(PACKAGE)-unit-tests.xml --format pkgname-and-test-fails -- 17 | GOTOOL ?= go tool 18 | GLIDE ?= glide 19 | 20 | LDFLAGS += -s -w -X $(GIT_REPO)/cmd.version=${GIT_TAG} 21 | 22 | .PHONY: help mod-download build release install test coverage lint vet fmt fmt-test image clean 23 | 24 | all: build 25 | 26 | help: ## Display this help 27 | @awk 'BEGIN {FS = ":.*?## "; printf "\n$(PACKAGE_DESC)\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z0-9._-]+:.*?## / {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 28 | 29 | mod-download: ## Download go modules 30 | $(GO) mod download 31 | 32 | build: ## Build the tm binary 33 | $(GO) build -v -mod=readonly -ldflags="$(LDFLAGS)" $(GIT_REPO) 34 | 35 | install: ## Install the binary 36 | $(GO) install -v -mod=readonly -ldflags="$(LDFLAGS)" $(GIT_REPO) 37 | 38 | release: ## Build release binaries 39 | @set -e ; \ 40 | for platform in $(TARGETS); do \ 41 | GOOS=$${platform%/*} ; \ 42 | GOARCH=$${platform#*/} ; \ 43 | RELEASE_BINARY=$(PACKAGE)-$${GOOS}-$${GOARCH} ; \ 44 | [ $${GOOS} = "windows" ] && RELEASE_BINARY=$${RELEASE_BINARY}.exe ; \ 45 | echo "GOOS=$${GOOS} GOARCH=$${GOARCH} $(GO) build -v -mod=readonly -ldflags="$(LDFLAGS)" -o $(DIST_DIR)$${RELEASE_BINARY} $(GIT_REPO)" ; \ 46 | GOOS=$${GOOS} GOARCH=$${GOARCH} $(GO) build -v -mod=readonly -ldflags="$(LDFLAGS)" -o $(DIST_DIR)$${RELEASE_BINARY} $(GIT_REPO) ; \ 47 | done 48 | 49 | test: ## Run unit tests 50 | $(GOTEST) -v -timeout 20m -p=1 -cover -coverprofile=c.out $(GIT_REPO)/... 51 | 52 | coverage: ## Generate code coverage 53 | $(GOTOOL) cover -html=c.out -o $(OUTPUT_DIR)$(PACKAGE)-coverage.html 54 | 55 | lint: ## Link source files 56 | $(GOLINT) -set_exit_status $(shell $(GLIDE) novendor) 57 | 58 | vet: ## Vet source files 59 | $(GO) vet $(GIT_REPO)/... 60 | 61 | fmt: ## Format source files 62 | $(GOFMT) -s -w $(shell $(GO) list -f '{{$$d := .Dir}}{{range .GoFiles}}{{$$d}}/{{.}} {{end}} {{$$d := .Dir}}{{range .TestGoFiles}}{{$$d}}/{{.}} {{end}}' $(GIT_REPO)/...) 63 | 64 | fmt-test: ## Check source formatting 65 | @test -z $(shell $(GOFMT) -l $(shell $(GO) list -f '{{$$d := .Dir}}{{range .GoFiles}}{{$$d}}/{{.}} {{end}} {{$$d := .Dir}}{{range .TestGoFiles}}{{$$d}}/{{.}} {{end}}' $(GIT_REPO)/...)) 66 | 67 | image: ## Builds the container image 68 | $(DOCKER) build . -t $(IMAGE) --build-arg GIT_TAG=$(GIT_TAG) 69 | 70 | clean: ## Clean build artifacts 71 | @$(RM) -v $(PACKAGE) 72 | @$(RM) -v $(PACKAGE)-unit-tests.xml 73 | @$(RM) -v c.out $(PACKAGE)-coverage.html 74 | @for platform in $(TARGETS); do \ 75 | GOOS=$${platform%/*} ; \ 76 | GOARCH=$${platform#*/} ; \ 77 | RELEASE_BINARY=$(PACKAGE)-$${GOOS}-$${GOARCH} ; \ 78 | [ $${GOOS} = "windows" ] && RELEASE_BINARY=$${RELEASE_BINARY}.exe ; \ 79 | $(RM) -v $(DIST_DIR)$${RELEASE_BINARY}; \ 80 | done 81 | -------------------------------------------------------------------------------- /pkg/resources/service/pingsource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "context" 19 | "os" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | 25 | "github.com/triggermesh/tm/pkg/client" 26 | "github.com/triggermesh/tm/pkg/file" 27 | ) 28 | 29 | func TestPingSource(t *testing.T) { 30 | namespace := "test-namespace" 31 | if ns, ok := os.LookupEnv("NAMESPACE"); ok { 32 | namespace = ns 33 | } 34 | 35 | testCases := []struct { 36 | name string 37 | service *Service 38 | pingSourceCreated bool 39 | reason string 40 | }{ 41 | { 42 | name: "valid pingsource", 43 | service: &Service{ 44 | Name: "valid-pingsource", 45 | Source: "gcr.io/google-samples/hello-app:1.0", 46 | Schedule: []file.Schedule{ 47 | { 48 | Cron: "*/1 * * * *", 49 | JSONData: `{"some":"data"}`, 50 | }, 51 | }, 52 | }, 53 | pingSourceCreated: true, 54 | }, { 55 | name: "malformed schedule", 56 | service: &Service{ 57 | Name: "malformed-schedule", 58 | Source: "gcr.io/google-samples/hello-app:1.0", 59 | Schedule: []file.Schedule{ 60 | { 61 | Cron: "not a schedule", 62 | }, 63 | }, 64 | }, 65 | pingSourceCreated: false, 66 | }, 67 | } 68 | 69 | client.Dry = false 70 | client.Wait = false 71 | serviceClient, err := client.NewClient(client.ConfigPath("")) 72 | assert.NoError(t, err) 73 | 74 | for _, tc := range testCases { 75 | t.Run(tc.name, func(t *testing.T) { 76 | tc.service.Namespace = namespace 77 | _, err := tc.service.Deploy(&serviceClient) 78 | assert.NoError(t, err) 79 | defer tc.service.Delete(&serviceClient) 80 | 81 | psList, err := serviceClient.Eventing.SourcesV1().PingSources(tc.service.Namespace).List(context.Background(), metav1.ListOptions{ 82 | LabelSelector: serviceLabelKey + "=" + tc.service.Name, 83 | }) 84 | assert.NoError(t, err) 85 | 86 | if !tc.pingSourceCreated { 87 | assert.Len(t, psList.Items, 0) 88 | return 89 | } 90 | 91 | // multiple schedules are not supported in tests 92 | assert.Len(t, psList.Items, 1) 93 | ps := psList.Items[0] 94 | 95 | assert.Equal(t, ps.Spec.Sink.Ref.Name, tc.service.Name) 96 | assert.Equal(t, ps.Spec.Sink.Ref.Namespace, tc.service.Namespace) 97 | 98 | assert.Len(t, ps.OwnerReferences, 1) 99 | assert.Equal(t, ps.OwnerReferences[0].Kind, "Service") 100 | assert.Equal(t, ps.OwnerReferences[0].Name, tc.service.Name) 101 | 102 | assert.Equal(t, ps.Spec.Data, tc.service.Schedule[0].JSONData) 103 | assert.Equal(t, ps.Spec.Schedule, tc.service.Schedule[0].Cron) 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pkg/file/file_test.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | // ParseServerlessYAML accepts serverless yaml file path and returns decoded structure 10 | func TestParseManifest(t *testing.T) { 11 | 12 | definition, err := ParseManifest("../../testfiles/serverless-test.yaml") 13 | assert.NoError(t, err) 14 | 15 | assert.Equal(t, "serverless-foo", definition.Service) 16 | assert.Equal(t, "serverless.yaml parsing test", definition.Description) 17 | } 18 | 19 | func TestRandString(t *testing.T) { 20 | testCases := []int{1, 2, 3, 10, 0} 21 | for _, tc := range testCases { 22 | result := RandString(tc) 23 | assert.Equal(t, tc, len(result)) 24 | } 25 | } 26 | 27 | func TestIsLocal(t *testing.T) { 28 | testCases := []struct { 29 | path string 30 | result bool 31 | }{ 32 | {"", false}, 33 | {"/", true}, 34 | {"../../testfiles/cfgfile-test.json", true}, 35 | } 36 | for _, tc := range testCases { 37 | result := IsLocal(tc.path) 38 | assert.Equal(t, tc.result, result) 39 | } 40 | } 41 | 42 | func TestIsRemote(t *testing.T) { 43 | testCases := []struct { 44 | path string 45 | result bool 46 | }{ 47 | {"", false}, 48 | {"https://", true}, 49 | {"http://", true}, 50 | {"git@", true}, 51 | {"google.com", true}, 52 | {"google", false}, 53 | } 54 | for _, tc := range testCases { 55 | result := IsRemote(tc.path) 56 | assert.Equal(t, tc.result, result) 57 | } 58 | } 59 | 60 | func TestIsGit(t *testing.T) { 61 | testCases := []struct { 62 | path string 63 | result bool 64 | }{ 65 | {"git@", true}, //should not be true? 66 | {".git", true}, //should not be true? 67 | {"git@github.com:triggermesh/tm.git", true}, 68 | {"https://github.com/triggermesh/tm.git", true}, 69 | {"https://github.com/triggermesh/tm", true}, 70 | {"github.com/triggermesh/tm", true}, 71 | {"https://triggermesh.com/", false}, 72 | {"some-random-string", false}, 73 | } 74 | for _, tc := range testCases { 75 | result := IsGit(tc.path) 76 | assert.Equal(t, tc.result, result, "isGit(%s) expected to be %v, got %v", tc.path, tc.result, result) 77 | } 78 | } 79 | 80 | // IsRegistry return true if path "behaves" like URL to docker registry 81 | func TestIsRegistry(t *testing.T) { 82 | testCases := []struct { 83 | path string 84 | result bool 85 | }{ 86 | {"google.com", false}, 87 | {"https://triggermesh.com/", false}, 88 | {"registry.hub.docker.com/test/testcase", true}, 89 | } 90 | for _, tc := range testCases { 91 | result := IsRegistry(tc.path) 92 | assert.Equal(t, tc.result, result) 93 | } 94 | } 95 | 96 | // Download receives URL and return path to saved file 97 | // func TestDownload(t *testing.T) { 98 | // path, err := Download("https://github.com/triggermesh/tm") 99 | // assert.NoError(t, err) 100 | 101 | // if _, err := os.Stat(path); os.IsNotExist(err) { 102 | // t.Errorf("Clone failed. Expecting folder at %v", path) 103 | // } 104 | // os.Remove(path) 105 | // } 106 | 107 | // Clone runs `git clone` operation for specified URL and returns local path to repository root directory 108 | // func TestClone(t *testing.T) { 109 | // path, err := Clone("https://github.com/triggermesh/tm") 110 | // assert.NoError(t, err) 111 | 112 | // if _, err := os.Stat(path); os.IsNotExist(err) { 113 | // t.Errorf("Clone failed. Expecting folder at %v", path) 114 | // } 115 | // os.Remove(path) 116 | // } 117 | -------------------------------------------------------------------------------- /pkg/resources/pipelineresource/create.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pipelineresource 16 | 17 | import ( 18 | "context" 19 | 20 | v1alpha1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" 21 | "github.com/triggermesh/tm/pkg/client" 22 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | ) 25 | 26 | // Deploy creates tekton PipelineResource with provided git URL 27 | func (plr *PipelineResource) Deploy(clientset *client.ConfigSet) (*v1alpha1.PipelineResource, error) { 28 | pipelineResourceObject := plr.newObject(clientset) 29 | if client.Dry { 30 | return &pipelineResourceObject, nil 31 | } 32 | return plr.createOrUpdate(pipelineResourceObject, clientset) 33 | } 34 | 35 | func (plr *PipelineResource) newObject(clientset *client.ConfigSet) v1alpha1.PipelineResource { 36 | return v1alpha1.PipelineResource{ 37 | TypeMeta: metav1.TypeMeta{ 38 | Kind: "Pipeline", 39 | APIVersion: "tekton.dev/v1alpha1", 40 | }, 41 | ObjectMeta: metav1.ObjectMeta{ 42 | Name: plr.Name, 43 | Namespace: plr.Namespace, 44 | }, 45 | Spec: v1alpha1.PipelineResourceSpec{ 46 | Type: v1alpha1.PipelineResourceTypeGit, 47 | Params: []v1alpha1.ResourceParam{ 48 | {Name: "url", Value: plr.Source.URL}, 49 | {Name: "revision", Value: plr.Source.Revision}, 50 | }, 51 | }, 52 | } 53 | } 54 | 55 | func (plr *PipelineResource) createOrUpdate(pipelineResourceObject v1alpha1.PipelineResource, clientset *client.ConfigSet) (*v1alpha1.PipelineResource, error) { 56 | var pipeline *v1alpha1.PipelineResource 57 | res, err := clientset.TektonPipelines.TektonV1alpha1().PipelineResources(plr.Namespace).Create(context.Background(), &pipelineResourceObject, metav1.CreateOptions{}) 58 | if k8serrors.IsAlreadyExists(err) { 59 | pipeline, err = clientset.TektonPipelines.TektonV1alpha1().PipelineResources(plr.Namespace).Get(context.Background(), pipelineResourceObject.ObjectMeta.Name, metav1.GetOptions{}) 60 | if err != nil { 61 | return res, err 62 | } 63 | pipelineResourceObject.ObjectMeta.ResourceVersion = pipeline.GetResourceVersion() 64 | res, err = clientset.TektonPipelines.TektonV1alpha1().PipelineResources(plr.Namespace).Update(context.Background(), &pipelineResourceObject, metav1.UpdateOptions{}) 65 | } 66 | return res, err 67 | } 68 | 69 | // SetOwner updates PipelineResource object with provided owner reference 70 | func (plr *PipelineResource) SetOwner(clientset *client.ConfigSet, owner metav1.OwnerReference) error { 71 | pplresource, err := clientset.TektonPipelines.TektonV1alpha1().PipelineResources(plr.Namespace).Get(context.Background(), plr.Name, metav1.GetOptions{}) 72 | if err != nil { 73 | return err 74 | } 75 | pplresource.SetOwnerReferences([]metav1.OwnerReference{owner}) 76 | _, err = clientset.TektonPipelines.TektonV1alpha1().PipelineResources(plr.Namespace).Update(context.Background(), pplresource, metav1.UpdateOptions{}) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /pkg/generate/includes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package generate 16 | 17 | // SamplesTable is a set of service samples with its dependencies, names, fucntions, etc 18 | type SamplesTable map[string]service 19 | 20 | type service struct { 21 | source string 22 | runtime string 23 | function string 24 | handler string 25 | apiGateway bool 26 | dependencies []stuff 27 | } 28 | 29 | type stuff struct { 30 | name string 31 | data string 32 | } 33 | 34 | const ( 35 | manifestName = "serverless.yaml" 36 | 37 | pythonFunc = `import json 38 | import datetime 39 | def endpoint(event, context): 40 | current_time = datetime.datetime.now().time() 41 | body = { 42 | "message": "Hello, the current time is " + str(current_time) 43 | } 44 | response = { 45 | "statusCode": 200, 46 | "body": json.dumps(body) 47 | } 48 | return response` 49 | golangFunc = `package main 50 | 51 | import ( 52 | "fmt" 53 | "context" 54 | "github.com/aws/aws-lambda-go/lambda" 55 | ) 56 | 57 | type MyEvent struct { 58 | Name string 59 | } 60 | 61 | func HandleRequest(ctx context.Context, name MyEvent) (string, error) { 62 | return fmt.Sprintf("Hello %s!", name.Name ), nil 63 | } 64 | 65 | func main() { 66 | lambda.Start(HandleRequest) 67 | }` 68 | rubyFunc = `def endpoint(event:, context:) 69 | hash = {date: Time.new} 70 | { statusCode: 200, body: JSON.generate(hash) } 71 | end` 72 | nodejsFunc = `async function justWait() { 73 | return new Promise((resolve, reject) => setTimeout(resolve, 100)); 74 | } 75 | 76 | module.exports.sayHelloAsync = async (event) => { 77 | await justWait(); 78 | return {hello: event && event.name || "Missing a name property in the event's JSON body"}; 79 | };` 80 | ) 81 | 82 | // NewTable returns map with runtime name as key and service structure as value 83 | func NewTable() *SamplesTable { 84 | return &SamplesTable{ 85 | "python": service{ 86 | source: "handler.py", 87 | runtime: "https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/python37/runtime.yaml", 88 | function: pythonFunc, 89 | handler: "handler.endpoint", 90 | apiGateway: true, 91 | }, 92 | "go": service{ 93 | source: "main.go", 94 | runtime: "https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/go/runtime.yaml", 95 | function: golangFunc, 96 | }, 97 | "ruby": service{ 98 | source: "handler.rb", 99 | runtime: "https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/ruby25/runtime.yaml", 100 | function: rubyFunc, 101 | handler: "handler.endpoint", 102 | apiGateway: true, 103 | }, 104 | "node": service{ 105 | source: "handler.js", 106 | runtime: "https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/node10/runtime.yaml", 107 | function: nodejsFunc, 108 | handler: "handler.sayHelloAsync", 109 | }, 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /pkg/file/pod.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 TriggerMesh Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package file 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "os" 25 | "path" 26 | "strings" 27 | 28 | "github.com/mholt/archiver" 29 | "github.com/triggermesh/tm/pkg/client" 30 | "k8s.io/client-go/tools/remotecommand" 31 | ) 32 | 33 | // Copy contains information to copy local path to remote destination 34 | type Copy struct { 35 | Pod string 36 | Container string 37 | Namespace string 38 | Source string 39 | Destination string 40 | } 41 | 42 | // Upload receives Copy structure, creates tarball of local source path and uploads it to active (un)tar process on remote pod 43 | func (c *Copy) Upload(clientset *client.ConfigSet) error { 44 | uploadPath := "/tmp/tm/upload" 45 | if err := os.MkdirAll(uploadPath, os.ModePerm); err != nil { 46 | return err 47 | } 48 | 49 | tar := path.Join(uploadPath, RandString(10)+".tar") 50 | if err := archiver.DefaultTar.Archive([]string{c.Source}, tar); err != nil { 51 | return err 52 | } 53 | clientset.Log.Debugf("sources are packed into %q archive, opening reader\n", tar) 54 | 55 | fileReader, err := os.Open(tar) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | clientset.Log.Debugf("starting remote untar proccess") 61 | command := fmt.Sprintf("tar -xvf - -C /home") 62 | stdout, stderr, err := c.RemoteExec(clientset, command, fileReader) 63 | clientset.Log.Debugf("stdout:\n%s", stdout) 64 | clientset.Log.Debugf("stderr:\n%s", stderr) 65 | return err 66 | } 67 | 68 | // RemoteExec executes command on remote pod and returns stdout and stderr output 69 | func (c *Copy) RemoteExec(clientset *client.ConfigSet, command string, file io.Reader) (string, string, error) { 70 | var commandLine string 71 | for _, v := range strings.Fields(command) { 72 | commandLine = fmt.Sprintf("%s&command=%s", commandLine, v) 73 | } 74 | if c.Container != "" { 75 | commandLine = fmt.Sprintf("&container=%s%s", c.Container, commandLine) 76 | } 77 | stdin := "false" 78 | if file != nil { 79 | stdin = "true" 80 | } 81 | // workaround to form correct URL 82 | urlAndParams := strings.Split(clientset.Core.RESTClient().Post().URL().String(), "?") 83 | url := fmt.Sprintf("%sapi/v1/namespaces/%s/pods/%s/exec?stderr=true&stdin=%s&stdout=true%s", urlAndParams[0], c.Namespace, c.Pod, stdin, commandLine) 84 | if len(urlAndParams) == 2 { 85 | url = fmt.Sprintf("%s&%s", url, urlAndParams[1]) 86 | } 87 | clientset.Log.Debugf("remote exec request URL: %q", url) 88 | req, err := http.NewRequest("POST", url, nil) 89 | if err != nil { 90 | return "", "", err 91 | } 92 | 93 | exec, err := remotecommand.NewSPDYExecutor(clientset.Config, "POST", req.URL) 94 | if err != nil { 95 | return "", "", err 96 | } 97 | var stdout, stderr bytes.Buffer 98 | err = exec.Stream(remotecommand.StreamOptions{ 99 | Stdin: file, 100 | Stdout: &stdout, 101 | Stderr: &stderr, 102 | Tty: false, 103 | }) 104 | if err != nil { 105 | return "", "", err 106 | } 107 | 108 | return stdout.String(), stderr.String(), nil 109 | } 110 | -------------------------------------------------------------------------------- /pkg/file/serverless.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "path/filepath" 7 | 8 | "github.com/spf13/afero" 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // Definition represents serverless.yaml file structure 13 | type Definition struct { 14 | Service string `yaml:"service,omitempty"` 15 | Description string `yaml:"description,omitempty"` 16 | Provider TriggermeshProvider `yaml:"provider,omitempty"` 17 | Repository string `yaml:"repository,omitempty"` 18 | Functions map[string]Function `yaml:"functions,omitempty"` 19 | Include []string `yaml:"include,omitempty"` 20 | } 21 | 22 | // TriggermeshProvider structure contains serverless provider parameters specific to triggermesh 23 | type TriggermeshProvider struct { 24 | Name string `yaml:"name,omitempty"` 25 | PullPolicy string `yaml:"pull-policy,omitempty"` 26 | Namespace string `yaml:"namespace,omitempty"` 27 | Runtime string `yaml:"runtime,omitempty"` 28 | Buildtimeout string `yaml:"buildtimeout,omitempty"` 29 | Environment map[string]string `yaml:"environment,omitempty"` 30 | EnvSecrets []string `yaml:"env-secrets,omitempty"` 31 | Annotations map[string]string `yaml:"annotations,omitempty"` 32 | 33 | // registry configs moved to client Configset 34 | // these variables kept for backward compatibility 35 | Registry string `yaml:"registry,omitempty"` 36 | RegistrySecret string `yaml:"registry-secret,omitempty"` 37 | } 38 | 39 | // Function describes function definition in serverless format 40 | type Function struct { 41 | Handler string `yaml:"handler,omitempty"` 42 | Source string `yaml:"source,omitempty"` 43 | Revision string `yaml:"revision,omitempty"` 44 | Runtime string `yaml:"runtime,omitempty"` 45 | Concurrency int `yaml:"concurrency,omitempty"` 46 | Buildargs []string `yaml:"buildargs,omitempty"` 47 | Description string `yaml:"description,omitempty"` 48 | Labels []string `yaml:"labels,omitempty"` 49 | Environment map[string]string `yaml:"environment,omitempty"` 50 | EnvSecrets []string `yaml:"env-secrets,omitempty"` 51 | Annotations map[string]string `yaml:"annotations,omitempty"` 52 | Schedule []Schedule `yaml:"schedule,omitempty"` 53 | } 54 | 55 | // Schedule struct contains a data in JSON format and a cron 56 | // that defines how often events should be sent to a function. 57 | // Description string may be used to explain events purpose. 58 | type Schedule struct { 59 | Cron string 60 | JSONData string 61 | Description string 62 | } 63 | 64 | // Aos returns filesystem object with standard set of os methods implemented by afero package 65 | var Aos = afero.NewOsFs() 66 | 67 | // ParseManifest accepts serverless yaml file path and returns decoded structure 68 | func ParseManifest(path string) (Definition, error) { 69 | var definition Definition 70 | 71 | exists, err := afero.Exists(Aos, path) 72 | 73 | if !exists || err != nil { 74 | return definition, errors.New("could not find manifest file") 75 | } 76 | 77 | data, err := afero.ReadFile(Aos, path) 78 | 79 | if err != nil { 80 | return definition, err 81 | } 82 | 83 | definition.Repository = filepath.Base(filepath.Dir(path)) 84 | err = yaml.UnmarshalStrict(data, &definition) 85 | 86 | return definition, err 87 | } 88 | 89 | // Validate function verifies that provided service Definition object contains required set of keys and values 90 | func (definition Definition) Validate() error { 91 | if definition.Provider.Name != "" && definition.Provider.Name != "triggermesh" { 92 | return fmt.Errorf("%s provider is not supported", definition.Provider.Name) 93 | } 94 | 95 | if len(definition.Service) == 0 { 96 | return errors.New("Service name can't be empty") 97 | } 98 | 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /pkg/resources/service/service_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "net/url" 21 | "os" 22 | "strings" 23 | "testing" 24 | "time" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "github.com/triggermesh/tm/pkg/client" 28 | ) 29 | 30 | // timeout in seconds to check that resulting service is reachable 31 | const dialTimeout = 10 * time.Second 32 | 33 | func TestDeployAndDelete(t *testing.T) { 34 | namespace := "test-namespace" 35 | if ns, ok := os.LookupEnv("NAMESPACE"); ok { 36 | namespace = ns 37 | } 38 | 39 | testCases := []struct { 40 | name string 41 | service *Service 42 | wantErr bool 43 | }{ 44 | { 45 | name: "service from image", 46 | service: &Service{ 47 | Name: "test-service-from-image", 48 | Source: "gcr.io/google-samples/hello-app:1.0", 49 | }, 50 | }, { 51 | name: "image not found", 52 | service: &Service{ 53 | Name: "test-missing-source", 54 | Source: "https://404", 55 | }, 56 | wantErr: true, 57 | }, { 58 | name: "runtime not found", 59 | service: &Service{ 60 | Name: "test-missing-runtime", 61 | Source: "https://github.com/serverless/examples", 62 | Runtime: "https://404", 63 | }, 64 | wantErr: true, 65 | }, 66 | } 67 | 68 | client.Dry = false 69 | client.Wait = true 70 | client.Debug = true 71 | serviceClient, err := client.NewClient(client.ConfigPath("")) 72 | assert.NoError(t, err) 73 | 74 | for _, tc := range testCases { 75 | t.Run(tc.name, func(t *testing.T) { 76 | tc.service.Namespace = namespace 77 | output, err := tc.service.Deploy(&serviceClient) 78 | if tc.wantErr { 79 | assert.Error(t, err) 80 | // some of the failed cases may leave kservices 81 | // so we'll try remove them 82 | tc.service.Delete(&serviceClient) 83 | return 84 | } 85 | 86 | address := strings.TrimPrefix(output, fmt.Sprintf("Service %s URL: ", tc.service.Name)) 87 | addr, err := url.Parse(address) 88 | assert.NoError(t, err) 89 | switch addr.Scheme { 90 | case "https": 91 | address = addr.Host + ":443" 92 | case "http": 93 | address = addr.Host + ":80" 94 | default: 95 | t.Error("malformed service URL scheme", addr.Scheme) 96 | return 97 | } 98 | conn, err := net.DialTimeout("tcp", address, dialTimeout) 99 | assert.NoError(t, err) 100 | assert.NoError(t, conn.Close()) 101 | 102 | err = tc.service.Delete(&serviceClient) 103 | assert.NoError(t, err) 104 | }) 105 | } 106 | } 107 | 108 | func TestList(t *testing.T) { 109 | namespace := "test-namespace" 110 | if ns, ok := os.LookupEnv("NAMESPACE"); ok { 111 | namespace = ns 112 | } 113 | serviceClient, err := client.NewClient(client.ConfigPath("")) 114 | assert.NoError(t, err) 115 | 116 | s := &Service{Name: "foo", Namespace: namespace} 117 | 118 | _, err = s.List(&serviceClient) 119 | assert.NoError(t, err) 120 | } 121 | 122 | func TestGet(t *testing.T) { 123 | namespace := "test-namespace" 124 | if ns, ok := os.LookupEnv("NAMESPACE"); ok { 125 | namespace = ns 126 | } 127 | serviceClient, err := client.NewClient(client.ConfigPath("")) 128 | assert.NoError(t, err) 129 | 130 | s := &Service{Name: "foo", Namespace: namespace} 131 | result, err := s.Get(&serviceClient) 132 | assert.Error(t, err) 133 | assert.Equal(t, "", result.Name) 134 | } 135 | -------------------------------------------------------------------------------- /pkg/resources/credential/registry-credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package credential 16 | 17 | import ( 18 | "bufio" 19 | "context" 20 | "fmt" 21 | "os" 22 | "strings" 23 | 24 | "github.com/triggermesh/tm/pkg/client" 25 | "golang.org/x/crypto/ssh/terminal" 26 | corev1 "k8s.io/api/core/v1" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | ) 29 | 30 | // SetRegistryCreds creates Secret with docker registry credentials json which later can be mounted as config.json file 31 | func (c *RegistryCreds) CreateRegistryCreds(clientset *client.ConfigSet) error { 32 | secrets := make(map[string]string) 33 | if !gitlabCI() && (len(c.Password) == 0 || len(c.Host) == 0 || len(c.Username) == 0) { 34 | if err := c.readStdin(); err != nil { 35 | return err 36 | } 37 | } 38 | secret := fmt.Sprintf("{\"project\":%q,\"auths\":{%q:{\"username\":%q,\"password\":%q}}}", c.ProjectID, c.Host, c.Username, c.Password) 39 | ctx := context.Background() 40 | if s, err := clientset.Core.CoreV1().Secrets(c.Namespace).Get(ctx, c.Name, metav1.GetOptions{}); err == nil { 41 | for k, v := range s.Data { 42 | secrets[k] = string(v) 43 | } 44 | if err = clientset.Core.CoreV1().Secrets(c.Namespace).Delete(ctx, c.Name, metav1.DeleteOptions{}); err != nil { 45 | return err 46 | } 47 | } 48 | 49 | if c.Pull || c.Pull == c.Push { 50 | secrets[".dockerconfigjson"] = secret 51 | } 52 | if c.Push || c.Push == c.Pull { 53 | secrets["config.json"] = secret 54 | } 55 | if _, ok := secrets[".dockerconfigjson"]; !ok { 56 | secrets[".dockerconfigjson"] = "{}" 57 | } 58 | newSecret := corev1.Secret{ 59 | Type: "kubernetes.io/dockerconfigjson", 60 | TypeMeta: metav1.TypeMeta{ 61 | Kind: "Secret", 62 | APIVersion: "v1", 63 | }, 64 | ObjectMeta: metav1.ObjectMeta{ 65 | Name: c.Name, 66 | Namespace: client.Namespace, 67 | }, 68 | StringData: secrets, 69 | } 70 | if _, err := clientset.Core.CoreV1().Secrets(client.Namespace).Create(ctx, &newSecret, metav1.CreateOptions{}); err != nil { 71 | return err 72 | } 73 | 74 | if c.Pull || c.Pull == c.Push { 75 | sa, err := clientset.Core.CoreV1().ServiceAccounts(client.Namespace).Get(ctx, "default", metav1.GetOptions{}) 76 | if err != nil { 77 | return err 78 | } 79 | sa.ImagePullSecrets = []corev1.LocalObjectReference{ 80 | {Name: c.Name}, 81 | } 82 | if _, err := clientset.Core.CoreV1().ServiceAccounts(client.Namespace).Update(ctx, sa, metav1.UpdateOptions{}); err != nil { 83 | return err 84 | } 85 | } 86 | return nil 87 | } 88 | 89 | func (c *RegistryCreds) readStdin() error { 90 | reader := bufio.NewReader(os.Stdin) 91 | if len(c.Host) == 0 { 92 | fmt.Printf("Registry: ") 93 | text, err := reader.ReadString('\n') 94 | if err != nil { 95 | return err 96 | } 97 | c.Host = strings.Replace(text, "\n", "", -1) 98 | } 99 | if len(c.Username) == 0 { 100 | fmt.Print("Username: ") 101 | text, err := reader.ReadString('\n') 102 | if err != nil { 103 | return err 104 | } 105 | c.Username = strings.Replace(text, "\n", "", -1) 106 | } 107 | if len(c.Password) == 0 { 108 | fmt.Print("Password: ") 109 | text, err := terminal.ReadPassword(int(os.Stdin.Fd())) 110 | if err != nil { 111 | return err 112 | } 113 | fmt.Println() 114 | c.Password = string(text) 115 | } 116 | return nil 117 | } 118 | 119 | func gitlabCI() bool { 120 | if ci, _ := os.LookupEnv("GITLAB_CI"); ci == "true" { 121 | return true 122 | } 123 | return false 124 | } 125 | -------------------------------------------------------------------------------- /pkg/generate/generate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package generate 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/triggermesh/tm/pkg/client" 22 | "github.com/triggermesh/tm/pkg/file" 23 | "gopkg.in/yaml.v2" 24 | ) 25 | 26 | // Project structure contains generic fields to generate sample knative service 27 | type Project struct { 28 | Name string 29 | Namespace string 30 | Runtime string 31 | } 32 | 33 | // Generate accept Project object and creates tm-deployable project structure 34 | // with all required manifests, functions, etc 35 | func (p *Project) Generate(clientset *client.ConfigSet) error { 36 | p.Runtime = strings.TrimLeft(p.Runtime, "-") 37 | samples := NewTable() 38 | if p.Runtime == "" || p.Runtime == "h" || p.Runtime == "help" { 39 | return p.help(samples) 40 | } 41 | sample, exist := (*samples)[p.Runtime] 42 | if !exist { 43 | return p.help(samples) 44 | } 45 | 46 | var buildArgs []string 47 | if sample.handler != "" { 48 | buildArgs = append(buildArgs, fmt.Sprintf("HANDLER=%s", sample.handler)) 49 | } 50 | 51 | provider := file.TriggermeshProvider{ 52 | Name: "triggermesh", 53 | // Registry: clientset.Registry.Host, 54 | // RegistrySecret: clientset.Registry.Secret, 55 | } 56 | 57 | functionName := fmt.Sprintf("%s-function", p.Runtime) 58 | functions := map[string]file.Function{ 59 | functionName: { 60 | Source: sample.source, 61 | Runtime: sample.runtime, 62 | Buildargs: buildArgs, 63 | Environment: map[string]string{ 64 | "foo": "bar", 65 | }, 66 | }, 67 | } 68 | 69 | if sample.apiGateway { 70 | functions[functionName].Environment["EVENT"] = "API_GATEWAY" 71 | } 72 | 73 | template := file.Definition{ 74 | Service: p.Name, 75 | Description: "Sample knative service", 76 | Provider: provider, 77 | Functions: functions, 78 | } 79 | 80 | if template.Service == "" { 81 | template.Service = fmt.Sprintf("%s-demo-service", p.Runtime) 82 | } 83 | 84 | manifest, err := yaml.Marshal(&template) 85 | if err != nil { 86 | return err 87 | } 88 | if client.Dry { 89 | fmt.Printf("%s/%s:\n---\n%s\n\n", p.Runtime, sample.source, sample.function) 90 | fmt.Printf("%s/%s:\n---\n%s\n", p.Runtime, manifestName, manifest) 91 | return nil 92 | } 93 | 94 | path := p.Name 95 | if path == "" { 96 | path = p.Runtime 97 | } 98 | 99 | if err := file.MakeDir(path); err != nil { 100 | return err 101 | } 102 | 103 | for _, dep := range sample.dependencies { 104 | if err := file.Write(fmt.Sprintf("%s/%s", path, dep.name), dep.data); err != nil { 105 | return fmt.Errorf("writing dependencies to file: %s", err) 106 | } 107 | } 108 | if err := file.Write(fmt.Sprintf("%s/%s", path, sample.source), sample.function); err != nil { 109 | return fmt.Errorf("writing function to file: %s", err) 110 | } 111 | if err := file.Write(fmt.Sprintf("%s/%s", path, manifestName), string(manifest)); err != nil { 112 | return fmt.Errorf("writing manifest to file: %s", err) 113 | } 114 | fmt.Printf("Sample %s project has been created\n", p.Runtime) 115 | fmt.Printf("%s/%s\t\t- function code\n%s/%s\t\t- service manifest\n", path, sample.source, path, manifestName) 116 | fmt.Printf("You can deploy this project using \"tm deploy -f %s --wait\" command\n", path) 117 | return nil 118 | } 119 | 120 | func (p *Project) help(samples *SamplesTable) error { 121 | fmt.Printf("Please specify one of available runtimes (e.g. \"tm generate --go\"):\n") 122 | for runtime := range *samples { 123 | fmt.Printf("--%s\n", runtime) 124 | } 125 | return fmt.Errorf("runtime not found") 126 | } 127 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | 21 | "github.com/spf13/cobra" 22 | "github.com/triggermesh/tm/pkg/client" 23 | "github.com/triggermesh/tm/pkg/generate" 24 | "github.com/triggermesh/tm/pkg/resources/channel" 25 | "github.com/triggermesh/tm/pkg/resources/configuration" 26 | "github.com/triggermesh/tm/pkg/resources/credential" 27 | "github.com/triggermesh/tm/pkg/resources/pipelineresource" 28 | "github.com/triggermesh/tm/pkg/resources/revision" 29 | "github.com/triggermesh/tm/pkg/resources/route" 30 | "github.com/triggermesh/tm/pkg/resources/service" 31 | "github.com/triggermesh/tm/pkg/resources/task" 32 | "github.com/triggermesh/tm/pkg/resources/taskrun" 33 | 34 | // Required for configs with gcp auth provider 35 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 36 | ) 37 | 38 | var ( 39 | version string 40 | err error 41 | kubeConf string 42 | clientset client.ConfigSet 43 | yaml string 44 | concurrency int 45 | debug bool 46 | registryHost string 47 | registrySecret string 48 | registrySkipTLS bool 49 | 50 | c channel.Channel 51 | t task.Task 52 | tr taskrun.TaskRun 53 | plr pipelineresource.PipelineResource 54 | p generate.Project 55 | s service.Service 56 | r revision.Revision 57 | rt route.Route 58 | cf configuration.Configuration 59 | gc credential.GitCreds 60 | rc credential.RegistryCreds 61 | ) 62 | 63 | // tmCmd represents the base command when called without any subcommands 64 | var tmCmd = &cobra.Command{ 65 | Use: "tm", 66 | Short: "Triggermesh CLI", 67 | Version: version, 68 | } 69 | 70 | // Execute runs main CLI command 71 | func Execute() { 72 | if err := tmCmd.Execute(); err != nil { 73 | log.Fatalln(err) 74 | } 75 | } 76 | 77 | func init() { 78 | cobra.OnInitialize(initConfig) 79 | tmCmd.PersistentFlags().StringVar(&kubeConf, "config", "", "k8s config file") 80 | tmCmd.PersistentFlags().StringVarP(&client.Namespace, "namespace", "n", "", "User namespace") 81 | tmCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug output") 82 | tmCmd.PersistentFlags().StringVar(®istryHost, "registry-host", "knative.registry.svc.cluster.local", "Docker registry host address") 83 | tmCmd.PersistentFlags().StringVar(®istrySecret, "registry-secret", "", "K8s secret name to use as image registry credentials") 84 | tmCmd.PersistentFlags().BoolVar(®istrySkipTLS, "registry-skip-tls", false, "Accept untrusted registries certificates") 85 | tmCmd.PersistentFlags().StringVarP(&client.Output, "output", "o", "", "Output format") 86 | tmCmd.PersistentFlags().BoolVar(&client.Wait, "wait", false, "Wait for the operation to complete") 87 | tmCmd.PersistentFlags().BoolVar(&client.Dry, "dry", false, "Do not create k8s objects, just print its structure") 88 | 89 | tmCmd.AddCommand(versionCmd) 90 | tmCmd.AddCommand(newDeployCmd(&clientset)) 91 | tmCmd.AddCommand(newDeleteCmd(&clientset)) 92 | tmCmd.AddCommand(newGenerateCmd(&clientset)) 93 | tmCmd.AddCommand(newPushCmd(&clientset)) 94 | tmCmd.AddCommand(newSetCmd(&clientset)) 95 | tmCmd.AddCommand(newGetCmd(&clientset)) 96 | } 97 | 98 | var versionCmd = &cobra.Command{ 99 | Use: "version", 100 | Short: "Print the version number of tm CLI", 101 | Run: func(cmd *cobra.Command, args []string) { 102 | fmt.Printf("%s, version %s\n", tmCmd.Short, version) 103 | }, 104 | } 105 | 106 | func initConfig() { 107 | confPath := client.ConfigPath(kubeConf) 108 | if clientset, err = client.NewClient(confPath, tmCmd.OutOrStdout()); err != nil { 109 | log.Fatalln(err) 110 | } 111 | clientset.Printer.Format = client.Output 112 | if debug { 113 | clientset.Log.SetDebugLevel() 114 | } 115 | clientset.Registry.Host = registryHost 116 | clientset.Registry.Secret = registrySecret 117 | clientset.Registry.SkipTLS = registrySkipTLS 118 | } 119 | -------------------------------------------------------------------------------- /pkg/printer/printer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh, Inc 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package printer 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "io" 21 | "reflect" 22 | "strings" 23 | 24 | "github.com/ghodss/yaml" 25 | "github.com/olekukonko/tablewriter" 26 | ) 27 | 28 | type headers []string 29 | type rows [][]string 30 | 31 | // Table is a structure with list headers and data rows that can be printed in PrintTable method 32 | type Table struct { 33 | Headers headers 34 | Rows rows 35 | } 36 | 37 | // Object is a structure that contain k8s object and field descriptions that should be printed 38 | type Object struct { 39 | Fields map[string]interface{} 40 | K8sObject interface{} 41 | } 42 | 43 | // Printer structure contains information needed to print objects in "tm get" command 44 | type Printer struct { 45 | Format string 46 | Output io.Writer 47 | Table *tablewriter.Table 48 | } 49 | 50 | // NewPrinter returns new Printer instance 51 | func NewPrinter(out io.Writer) *Printer { 52 | table := tablewriter.NewWriter(out) 53 | table.SetAlignment(tablewriter.ALIGN_LEFT) 54 | table.SetBorder(false) 55 | table.SetRowSeparator("") 56 | table.SetCenterSeparator("") 57 | table.SetColumnSeparator("") 58 | table.SetNoWhiteSpace(true) 59 | table.SetTablePadding("\t") 60 | return &Printer{ 61 | Output: out, 62 | Table: table, 63 | } 64 | } 65 | 66 | func (p *Printer) setTableHeaders(heads headers) { 67 | p.Table.SetHeader(heads) 68 | p.Table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 69 | p.Table.SetHeaderLine(false) 70 | } 71 | 72 | // PrintTable accepts Table instance and prints it using olekukonko/tablewriter package 73 | func (p *Printer) PrintTable(table Table) { 74 | p.setTableHeaders(table.Headers) 75 | p.Table.AppendBulk(table.Rows) 76 | p.Table.Render() 77 | } 78 | 79 | // PrintObject accepts Object instance and depending on output format encodes object and writes to Object output 80 | func (p *Printer) PrintObject(object Object) error { 81 | switch p.Format { 82 | case "yaml": 83 | data, err := yaml.Marshal(object.K8sObject) 84 | if err != nil { 85 | return err 86 | } 87 | fmt.Fprintf(p.Output, "%s", data) 88 | case "json": 89 | data, err := json.MarshalIndent(object.K8sObject, "", " ") 90 | if err != nil { 91 | return err 92 | } 93 | fmt.Fprintf(p.Output, "%s", data) 94 | default: 95 | p.printShort(object) 96 | } 97 | return nil 98 | } 99 | 100 | func (p *Printer) printShort(object Object) { 101 | val := reflect.ValueOf(object.K8sObject) 102 | val = reflect.Indirect(val) 103 | if val.Kind().String() != "struct" { 104 | return 105 | } 106 | for i := 0; i < val.NumField(); i++ { 107 | fieldType := val.Field(i).Type() 108 | fieldName := val.Type().Field(i) 109 | 110 | for key, value := range object.Fields { 111 | printType := reflect.ValueOf(value).Type() 112 | // fmt.Printf("Comparing %q(%s) with %q(%s)\n", key, printType.String(), fieldName.Name, fieldType.String()) 113 | if strings.EqualFold(fieldType.String(), printType.String()) { 114 | if fieldName.Name == key { 115 | output, err := yaml.Marshal(val.Field(i).Interface()) 116 | if err != nil { 117 | continue 118 | } 119 | fmt.Fprintf(p.Output, "%s:\n%s\n", key, output) 120 | // Empty TypeMeta fields due to https://github.com/kubernetes/client-go/issues/308 121 | // Print only first occurrance of a requested object 122 | delete(object.Fields, key) 123 | break 124 | } 125 | } 126 | } 127 | 128 | switch fieldType.Kind().String() { 129 | case "struct": 130 | p.printShort(Object{ 131 | K8sObject: val.Field(i).Interface(), 132 | Fields: object.Fields, 133 | }) 134 | case "slice": 135 | for j := 0; j < val.Field(i).Len(); j++ { 136 | p.printShort(Object{ 137 | K8sObject: val.Field(i).Index(j).Interface(), 138 | Fields: object.Fields, 139 | }) 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | aws-eks: circleci/aws-eks@1 5 | gcp-cli: circleci/gcp-cli@1 6 | go: circleci/go@1 7 | 8 | jobs: 9 | checkout: 10 | executor: 11 | name: go/default 12 | tag: '1.18' 13 | steps: 14 | - checkout 15 | - go/mod-download-cached 16 | - persist_to_workspace: 17 | root: ~/ 18 | paths: 19 | - go 20 | - project 21 | 22 | build: 23 | executor: 24 | name: go/default 25 | tag: '1.18' 26 | steps: 27 | - attach_workspace: 28 | at: ~/ 29 | - run: 30 | name: Building package 31 | command: make build 32 | no_output_timeout: 20m 33 | 34 | test: 35 | executor: 36 | name: go/default 37 | tag: '1.18' 38 | steps: 39 | - attach_workspace: 40 | at: ~/ 41 | - run: 42 | name: Running fmt-test 43 | command: make fmt-test 44 | - run: 45 | name: Running vet 46 | command: make vet 47 | - aws-eks/update-kubeconfig-with-authenticator: 48 | cluster-name: $AWS_CLUSTER_NAME 49 | aws-region: $AWS_REGION 50 | - run: 51 | name: Creating tm config file 52 | command: mkdir -p ~/.tm/ && cp ~/.kube/config ~/.tm/config.json 53 | - run: 54 | name: Running unit tests 55 | command: mkdir -p ${OUTPUT_DIR} && make test 56 | no_output_timeout: 20m 57 | environment: 58 | OUTPUT_DIR: /tmp/test-results/ 59 | - store_test_results: 60 | path: /tmp/test-results/ 61 | - run: 62 | name: Generating coverage report 63 | command: mkdir -p ${OUTPUT_DIR} && make coverage 64 | environment: 65 | OUTPUT_DIR: /tmp/artifacts/ 66 | - store_artifacts: 67 | path: /tmp/artifacts/ 68 | 69 | publish: 70 | executor: 71 | name: gcp-cli/google 72 | steps: 73 | - attach_workspace: 74 | at: ~/ 75 | - gcp-cli/initialize 76 | - run: 77 | name: Publishing docker image 78 | no_output_timeout: 20m 79 | command: gcloud builds submit --config cloudbuild.yaml --substitutions COMMIT_SHA=${CIRCLE_SHA1},TAG_NAME=${CIRCLE_TAG:-$(git describe --tags --always)},_KANIKO_IMAGE_TAG=${CIRCLE_TAG:-latest} 80 | 81 | release: 82 | executor: 83 | name: go/default 84 | tag: '1.18' 85 | steps: 86 | - attach_workspace: 87 | at: ~/ 88 | - run: 89 | name: Building release packages 90 | command: make release 91 | environment: 92 | DIST_DIR: /tmp/dist/ 93 | GIT_TAG: ${CIRCLE_TAG} 94 | - run: 95 | name: Installing github-release 96 | command: go install github.com/github-release/github-release@latest 97 | - run: 98 | name: Creating github release 99 | command: | 100 | PRE_RELEASE=${CIRCLE_TAG/${CIRCLE_TAG%-rc[0-9]*}/} 101 | github-release delete -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -t ${CIRCLE_TAG} 2>/dev/null ||: 102 | ./scripts/release-notes.sh ${CIRCLE_TAG} | github-release release ${PRE_RELEASE:+-p} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -t ${CIRCLE_TAG} -d - 103 | 104 | max_tries=3 105 | for _ in $(seq 1 ${max_tries}); do 106 | github-release info -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -t ${CIRCLE_TAG} >/dev/null && break 107 | sleep 1 108 | done 109 | 110 | for f in $(find /tmp/dist -type f); do github-release upload -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -t ${CIRCLE_TAG} -n $(basename ${f}) -f ${f} ; done 111 | 112 | workflows: 113 | build-test-and-release: 114 | jobs: 115 | - checkout: 116 | filters: 117 | tags: 118 | only: /^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/ 119 | - build: 120 | context: production 121 | requires: 122 | - checkout 123 | filters: 124 | tags: 125 | only: /^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/ 126 | - test: 127 | context: staging 128 | requires: 129 | - checkout 130 | filters: 131 | tags: 132 | only: /^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/ 133 | - publish: 134 | context: production 135 | requires: 136 | - build 137 | - test 138 | filters: 139 | tags: 140 | only: /^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/ 141 | branches: 142 | only: main 143 | - release: 144 | context: production 145 | requires: 146 | - publish 147 | filters: 148 | tags: 149 | only: /^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/ 150 | branches: 151 | ignore: /.*/ 152 | -------------------------------------------------------------------------------- /pkg/file/ops.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package file 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "math/rand" 21 | "net/http" 22 | "net/url" 23 | "os" 24 | "strings" 25 | "time" 26 | 27 | git "gopkg.in/src-d/go-git.v4" 28 | ) 29 | 30 | const ( 31 | tmpPath = "/tmp/tm/" 32 | ) 33 | 34 | const letterBytesDNS = "abcdefghijklmnopqrstuvwxyz" 35 | const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 36 | 37 | // RandString accepts integer value N and returns random string with length of N 38 | func RandString(n int) string { 39 | random := rand.New(rand.NewSource(time.Now().UnixNano())) 40 | b := make([]byte, n) 41 | for i := range b { 42 | b[i] = letterBytes[random.Intn(len(letterBytes))] 43 | } 44 | return string(b) 45 | } 46 | 47 | // RandStringDNS accepts integer value N and returns "DNS-friendly" random string with length of N 48 | func RandStringDNS(n int) string { 49 | random := rand.New(rand.NewSource(time.Now().UnixNano())) 50 | b := make([]byte, n) 51 | for i := range b { 52 | b[i] = letterBytes[random.Intn(len(letterBytesDNS))] 53 | } 54 | return string(b) 55 | } 56 | 57 | // IsLocal return true if path is local filesystem 58 | func IsLocal(path string) bool { 59 | if _, err := os.Stat(path); err != nil { 60 | return false 61 | } 62 | return true 63 | } 64 | 65 | // IsDir return true if path is directory 66 | func IsDir(path string) bool { 67 | info, err := os.Stat(path) 68 | if err != nil { 69 | return false 70 | } 71 | return info.IsDir() 72 | } 73 | 74 | // IsRemote return true if path is URL 75 | func IsRemote(path string) bool { 76 | if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "git@") { 77 | return true 78 | } 79 | if _, err := http.Head("https://" + path); err != nil { 80 | return false 81 | } 82 | return true 83 | } 84 | 85 | // IsGit most likely return true if path is URL to git repository 86 | func IsGit(path string) bool { 87 | if strings.HasSuffix(path, ".git") || strings.HasPrefix(path, "git@") { 88 | return true 89 | } 90 | url, err := url.Parse(path) 91 | if err != nil { 92 | return false 93 | } 94 | if url.Scheme == "" { 95 | url.Scheme = "https" 96 | } 97 | resp, err := http.Head(url.String() + ".git") 98 | if err != nil { 99 | return false 100 | } 101 | for _, status := range []int{200, 301, 302, 401} { 102 | if resp.StatusCode == status { 103 | return true 104 | } 105 | } 106 | return false 107 | } 108 | 109 | // IsGitFile accepts string and, if this string is git repsotiry, 110 | // returns URL to clone and relative file path inside cloned repository directory 111 | // Otherwise it returns empty strings 112 | func IsGitFile(path string) (string, string) { 113 | uri := strings.Split(path, "/") 114 | if len(uri) < 8 { 115 | return "", "" 116 | } 117 | repository := fmt.Sprintf("https://%s/%s/%s", uri[0], uri[1], uri[2]) 118 | file := strings.Join(uri[5:], "/") 119 | if strings.HasPrefix(uri[0], "http") { 120 | repository = fmt.Sprintf("%s//%s/%s/%s", uri[0], uri[2], uri[3], uri[4]) 121 | file = strings.Join(uri[7:], "/") 122 | } 123 | if !IsGit(repository) { 124 | return "", "" 125 | } 126 | return repository, file 127 | } 128 | 129 | // IsRegistry return true if path "behaves" like URL to docker registry 130 | func IsRegistry(path string) bool { 131 | url, err := url.Parse(path) 132 | if err != nil { 133 | return false 134 | } 135 | // Need to find the way how to detect docker registry 136 | if strings.Contains(url.String(), "docker.") { 137 | return true 138 | } 139 | if url.Scheme == "" { 140 | url.Scheme = "https" 141 | } 142 | resp, err := http.Head(url.String()) 143 | if err != nil { 144 | return false 145 | } 146 | if resp.StatusCode == 405 { 147 | return true 148 | } 149 | return false 150 | } 151 | 152 | // Download receives URL and return path to saved file 153 | func Download(url string) (string, error) { 154 | path := fmt.Sprintf("%s/download", tmpPath) 155 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 156 | return "", err 157 | } 158 | path = fmt.Sprintf("%s/%s", path, RandString(10)) 159 | out, err := os.Create(path) 160 | if err != nil { 161 | return "", err 162 | } 163 | defer out.Close() 164 | 165 | resp, err := http.Get(url) 166 | if err != nil { 167 | return "", err 168 | } 169 | defer resp.Body.Close() 170 | 171 | _, err = io.Copy(out, resp.Body) 172 | if err != nil { 173 | return "", err 174 | } 175 | 176 | return path, nil 177 | } 178 | 179 | // Clone runs `git clone` operation for specified URL and returns local path to repository root directory 180 | func Clone(url string) (string, error) { 181 | path := fmt.Sprintf("%s/git/%s", tmpPath, RandString(10)) 182 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 183 | return "", err 184 | } 185 | _, err := git.PlainClone(path, false, &git.CloneOptions{ 186 | URL: url, 187 | }) 188 | return path, err 189 | } 190 | 191 | // Write creates file named as passed filename and writes data into this file 192 | func Write(filename, data string) error { 193 | f, err := os.Create(filename) 194 | if err != nil { 195 | return err 196 | } 197 | defer f.Close() 198 | _, err = f.WriteString(data) 199 | return err 200 | } 201 | 202 | // MakeDir created directory and all missing parents 203 | func MakeDir(path string) error { 204 | return os.MkdirAll(path, os.FileMode(0700)) 205 | } 206 | -------------------------------------------------------------------------------- /cmd/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "log" 19 | 20 | "github.com/spf13/cobra" 21 | "github.com/triggermesh/tm/pkg/client" 22 | ) 23 | 24 | // NewDeleteCmd returns cobra Command with set of resource deletion subcommands 25 | func newDeleteCmd(clientset *client.ConfigSet) *cobra.Command { 26 | var file string 27 | deleteCmd := &cobra.Command{ 28 | Use: "delete", 29 | Short: "Delete knative resource", 30 | Run: func(cmd *cobra.Command, args []string) { 31 | s.Namespace = client.Namespace 32 | if err := s.DeleteYAML(file, args, concurrency, clientset); err != nil { 33 | log.Fatal(err) 34 | } 35 | }, 36 | } 37 | 38 | deleteCmd.Flags().StringVarP(&file, "file", "f", "serverless.yaml", "Delete functions defined in yaml") 39 | deleteCmd.Flags().IntVarP(&concurrency, "concurrency", "c", 3, "Number of concurrent deletion threads") 40 | deleteCmd.AddCommand(cmdDeleteConfiguration(clientset)) 41 | deleteCmd.AddCommand(cmdDeleteRevision(clientset)) 42 | deleteCmd.AddCommand(cmdDeleteService(clientset)) 43 | deleteCmd.AddCommand(cmdDeleteRoute(clientset)) 44 | deleteCmd.AddCommand(cmdDeleteChannel(clientset)) 45 | deleteCmd.AddCommand(cmdDeleteTask(clientset)) 46 | deleteCmd.AddCommand(cmdDeleteTaskRun(clientset)) 47 | deleteCmd.AddCommand(cmdDeletePipelineResource(clientset)) 48 | 49 | return deleteCmd 50 | } 51 | 52 | func cmdDeleteChannel(clientset *client.ConfigSet) *cobra.Command { 53 | return &cobra.Command{ 54 | Use: "channel", 55 | Aliases: []string{"channels"}, 56 | Short: "Delete knative channel resource", 57 | Args: cobra.ExactArgs(1), 58 | Run: func(cmd *cobra.Command, args []string) { 59 | c.Name = args[0] 60 | c.Namespace = client.Namespace 61 | if err := c.Delete(clientset); err != nil { 62 | log.Fatalln(err) 63 | } 64 | clientset.Log.Infoln("Channel is being deleted") 65 | }, 66 | } 67 | } 68 | 69 | func cmdDeleteService(clientset *client.ConfigSet) *cobra.Command { 70 | return &cobra.Command{ 71 | Use: "service", 72 | Short: "Delete knative service resource", 73 | Aliases: []string{"services"}, 74 | Args: cobra.ExactArgs(1), 75 | Run: func(cmd *cobra.Command, args []string) { 76 | s.Name = args[0] 77 | s.Namespace = client.Namespace 78 | if err := s.Delete(clientset); err != nil { 79 | log.Fatalln(err) 80 | } 81 | clientset.Log.Infoln("Service is being deleted") 82 | }, 83 | } 84 | } 85 | 86 | func cmdDeleteConfiguration(clientset *client.ConfigSet) *cobra.Command { 87 | return &cobra.Command{ 88 | Use: "configuration", 89 | Short: "Delete knative configuration resource", 90 | Aliases: []string{"configurations"}, 91 | Args: cobra.ExactArgs(1), 92 | Run: func(cmd *cobra.Command, args []string) { 93 | cf.Name = args[0] 94 | cf.Namespace = client.Namespace 95 | if err := cf.Delete(clientset); err != nil { 96 | log.Fatalln(err) 97 | } 98 | clientset.Log.Infoln("Configuration is being deleted") 99 | }, 100 | } 101 | } 102 | 103 | func cmdDeleteRevision(clientset *client.ConfigSet) *cobra.Command { 104 | return &cobra.Command{ 105 | Use: "revision", 106 | Short: "Delete knative revision resource", 107 | Aliases: []string{"revisions"}, 108 | Args: cobra.ExactArgs(1), 109 | Run: func(cmd *cobra.Command, args []string) { 110 | r.Name = args[0] 111 | r.Namespace = client.Namespace 112 | if err := r.Delete(clientset); err != nil { 113 | log.Fatalln(err) 114 | } 115 | clientset.Log.Infoln("Revision is being deleted") 116 | }, 117 | } 118 | } 119 | 120 | func cmdDeleteRoute(clientset *client.ConfigSet) *cobra.Command { 121 | return &cobra.Command{ 122 | Use: "route", 123 | Short: "Delete knative route resource", 124 | Aliases: []string{"routes"}, 125 | Args: cobra.ExactArgs(1), 126 | Run: func(cmd *cobra.Command, args []string) { 127 | rt.Name = args[0] 128 | rt.Namespace = client.Namespace 129 | if err := rt.Delete(clientset); err != nil { 130 | log.Fatalln(err) 131 | } 132 | clientset.Log.Infoln("Route is being deleted") 133 | }, 134 | } 135 | } 136 | 137 | func cmdDeleteTask(clientset *client.ConfigSet) *cobra.Command { 138 | return &cobra.Command{ 139 | Use: "task", 140 | Aliases: []string{"tasks"}, 141 | Short: "Delete tekton task resource", 142 | Args: cobra.ExactArgs(1), 143 | Run: func(cmd *cobra.Command, args []string) { 144 | t.Name = args[0] 145 | t.Namespace = client.Namespace 146 | if err := t.Delete(clientset); err != nil { 147 | log.Fatalln(err) 148 | } 149 | clientset.Log.Infoln("Task is being deleted") 150 | }, 151 | } 152 | } 153 | 154 | func cmdDeleteTaskRun(clientset *client.ConfigSet) *cobra.Command { 155 | return &cobra.Command{ 156 | Use: "taskrun", 157 | Aliases: []string{"taskruns"}, 158 | Short: "Delete tekton taskrun resource", 159 | Args: cobra.ExactArgs(1), 160 | Run: func(cmd *cobra.Command, args []string) { 161 | tr.Name = args[0] 162 | tr.Namespace = client.Namespace 163 | if err := tr.Delete(clientset); err != nil { 164 | log.Fatalln(err) 165 | } 166 | clientset.Log.Infoln("TaskRun is being deleted") 167 | }, 168 | } 169 | } 170 | 171 | func cmdDeletePipelineResource(clientset *client.ConfigSet) *cobra.Command { 172 | return &cobra.Command{ 173 | Use: "pipelineresource", 174 | Aliases: []string{"pipelineresources"}, 175 | Short: "Delete tekton pipelineresource resource", 176 | Args: cobra.ExactArgs(1), 177 | Run: func(cmd *cobra.Command, args []string) { 178 | plr.Name = args[0] 179 | plr.Namespace = client.Namespace 180 | if err := plr.Delete(clientset); err != nil { 181 | log.Fatalln(err) 182 | } 183 | clientset.Log.Infoln("PipelineResource is being deleted") 184 | }, 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 TriggerMesh Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package client 18 | 19 | import ( 20 | "io" 21 | "io/ioutil" 22 | "log" 23 | "os" 24 | "path/filepath" 25 | 26 | "github.com/ghodss/yaml" 27 | tektonTask "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" 28 | tektonResource "github.com/tektoncd/pipeline/pkg/client/resource/clientset/versioned" 29 | triggersApi "github.com/tektoncd/triggers/pkg/client/clientset/versioned" 30 | logwrapper "github.com/triggermesh/tm/pkg/log" 31 | printerwrapper "github.com/triggermesh/tm/pkg/printer" 32 | githubSource "knative.dev/eventing-github/pkg/client/clientset/versioned" 33 | eventingApi "knative.dev/eventing/pkg/client/clientset/versioned" 34 | servingApi "knative.dev/serving/pkg/client/clientset/versioned" 35 | 36 | "k8s.io/client-go/kubernetes" 37 | "k8s.io/client-go/rest" 38 | "k8s.io/client-go/tools/clientcmd" 39 | 40 | // gcp package is required for kubectl configs with GCP auth providers 41 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 42 | ) 43 | 44 | const ( 45 | confPath = "/.tm/config.json" 46 | defaultRegistry = "knative.registry.svc.cluster.local" 47 | ) 48 | 49 | // CLI global flags 50 | var ( 51 | // Namespace to work in passed with "-n" argument or defined in kube configs 52 | Namespace string 53 | // Output format for k8s objects in "tm get" result. Can be either "yaml" (default) or "json" 54 | Output string 55 | // Debug enables verbose output for CLI commands 56 | Debug bool 57 | // Dry run of some commands 58 | Dry bool 59 | // Wait till deployment operation finishes 60 | Wait bool 61 | ) 62 | 63 | // Registry to store container images for user services 64 | type Registry struct { 65 | Host string 66 | Secret string 67 | SkipTLS bool 68 | } 69 | 70 | // ConfigSet contains different information that may be needed by underlying functions 71 | type ConfigSet struct { 72 | Core *kubernetes.Clientset 73 | Serving *servingApi.Clientset 74 | Eventing *eventingApi.Clientset 75 | GithubSource *githubSource.Clientset 76 | TektonPipelines *tektonResource.Clientset 77 | TektonTasks *tektonTask.Clientset 78 | TektonTriggers *triggersApi.Clientset 79 | Registry *Registry 80 | Log *logwrapper.StandardLogger 81 | Printer *printerwrapper.Printer 82 | Config *rest.Config 83 | } 84 | 85 | type config struct { 86 | Contexts []struct { 87 | Name string 88 | Context struct { 89 | Namespace string 90 | } 91 | } 92 | CurrentContext string `json:"current-context"` 93 | } 94 | 95 | func getNamespace(kubeCfgFile string) string { 96 | namespace := "default" 97 | data, err := ioutil.ReadFile(kubeCfgFile) 98 | if err != nil { 99 | log.Printf("Can't read config file: %s\n", err) 100 | return namespace 101 | } 102 | var c config 103 | if err := yaml.Unmarshal(data, &c); err != nil { 104 | log.Printf("Can't parse config body: %s\n", err) 105 | return namespace 106 | } 107 | for _, context := range c.Contexts { 108 | if context.Name == c.CurrentContext { 109 | if context.Context.Namespace != "" { 110 | namespace = context.Context.Namespace 111 | } 112 | break 113 | } 114 | } 115 | return namespace 116 | } 117 | 118 | func getInClusterNamespace() string { 119 | data, err := ioutil.ReadFile("/run/secrets/kubernetes.io/serviceaccount/namespace") 120 | if err != nil { 121 | return "default" 122 | } 123 | return string(data) 124 | } 125 | 126 | // ConfigPath calculates local path to get tm config from 127 | func ConfigPath(cfgFile string) string { 128 | homeDir := "." 129 | if dir := os.Getenv("HOME"); dir != "" { 130 | homeDir = dir 131 | } 132 | tmHome := filepath.Dir(homeDir + confPath) 133 | if _, err := os.Stat(tmHome); os.IsNotExist(err) { 134 | if err := os.MkdirAll(tmHome, 0755); err != nil { 135 | log.Fatalln(err) 136 | } 137 | } 138 | 139 | kubeconfig := os.Getenv("KUBECONFIG") 140 | if len(cfgFile) != 0 { 141 | // using config file passed with --config argument 142 | } else if _, err := os.Stat(homeDir + confPath); err == nil { 143 | cfgFile = homeDir + confPath 144 | } else if _, err := os.Stat(kubeconfig); err == nil { 145 | cfgFile = kubeconfig 146 | } else { 147 | cfgFile = homeDir + "/.kube/config" 148 | } 149 | return cfgFile 150 | } 151 | 152 | // NewClient returns ConfigSet created from available configuration file or from in-cluster environment 153 | func NewClient(cfgFile string, output ...io.Writer) (ConfigSet, error) { 154 | var c ConfigSet 155 | 156 | config, err := clientcmd.BuildConfigFromFlags("", cfgFile) 157 | if err != nil { 158 | log.Printf("%s, falling back to in-cluster configuration\n", err) 159 | if config, err = rest.InClusterConfig(); err != nil { 160 | return c, err 161 | } 162 | if len(Namespace) == 0 { 163 | Namespace = getInClusterNamespace() 164 | } 165 | } else if len(Namespace) == 0 { 166 | Namespace = getNamespace(cfgFile) 167 | } 168 | c.Config = config 169 | c.Log = logwrapper.NewLogger() 170 | if len(output) == 1 { 171 | c.Printer = printerwrapper.NewPrinter(output[0]) 172 | } 173 | c.Registry = &Registry{ 174 | Host: defaultRegistry, 175 | } 176 | 177 | if c.Eventing, err = eventingApi.NewForConfig(config); err != nil { 178 | return c, err 179 | } 180 | if c.Serving, err = servingApi.NewForConfig(config); err != nil { 181 | return c, err 182 | } 183 | if c.Core, err = kubernetes.NewForConfig(config); err != nil { 184 | return c, err 185 | } 186 | if c.TektonPipelines, err = tektonResource.NewForConfig(config); err != nil { 187 | return c, err 188 | } 189 | if c.TektonTasks, err = tektonTask.NewForConfig(config); err != nil { 190 | return c, err 191 | } 192 | if c.TektonTriggers, err = triggersApi.NewForConfig(config); err != nil { 193 | return c, err 194 | } 195 | if c.GithubSource, err = githubSource.NewForConfig(config); err != nil { 196 | return c, err 197 | } 198 | return c, nil 199 | } 200 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/triggermesh/tm 2 | 3 | go 1.18 4 | 5 | require ( 6 | knative.dev/eventing v0.31.1-0.20220523181303-c3e13967001f 7 | knative.dev/pkg v0.0.0-20220525153005-18f69958870f 8 | knative.dev/serving v0.31.0 9 | ) 10 | 11 | // Top-level module control over the exact version used for important direct dependencies. 12 | // https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive 13 | replace k8s.io/client-go => k8s.io/client-go v0.23.5 14 | 15 | require ( 16 | cloud.google.com/go/compute v1.5.0 // indirect 17 | contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect 18 | contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect 19 | github.com/Microsoft/go-winio v0.5.2 // indirect 20 | github.com/PuerkitoBio/purell v1.1.1 // indirect 21 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 22 | github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220209173558-ad29539cd2e9 // indirect 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/blendle/zapdriver v1.3.1 // indirect 25 | github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect 26 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 27 | github.com/cloudevents/sdk-go/sql/v2 v2.8.0 // indirect 28 | github.com/cloudevents/sdk-go/v2 v2.10.1 // indirect 29 | github.com/davecgh/go-spew v1.1.1 // indirect 30 | github.com/dsnet/compress v0.0.1 // indirect 31 | github.com/emicklei/go-restful v2.15.0+incompatible // indirect 32 | github.com/emirpasic/gods v1.12.0 // indirect 33 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 34 | github.com/ghodss/yaml v1.0.0 35 | github.com/go-kit/log v0.1.0 // indirect 36 | github.com/go-logfmt/logfmt v0.5.0 // indirect 37 | github.com/go-logr/logr v1.2.2 // indirect 38 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 39 | github.com/go-openapi/jsonreference v0.19.5 // indirect 40 | github.com/go-openapi/swag v0.19.15 // indirect 41 | github.com/gogo/protobuf v1.3.2 // indirect 42 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 43 | github.com/golang/protobuf v1.5.2 // indirect 44 | github.com/golang/snappy v0.0.4 // indirect 45 | github.com/google/cel-go v0.11.2 // indirect 46 | github.com/google/go-cmp v0.5.7 // indirect 47 | github.com/google/go-containerregistry v0.8.1-0.20220414143355-892d7a808387 // indirect 48 | github.com/google/go-github/v31 v31.0.0 // indirect 49 | github.com/google/go-querystring v1.0.0 // indirect 50 | github.com/google/gofuzz v1.2.0 // indirect 51 | github.com/google/uuid v1.3.0 // indirect 52 | github.com/googleapis/gnostic v0.5.5 // indirect 53 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect 54 | github.com/hashicorp/errwrap v1.0.0 // indirect 55 | github.com/hashicorp/go-multierror v1.1.1 // indirect 56 | github.com/hashicorp/golang-lru v0.5.4 // indirect 57 | github.com/imdario/mergo v0.3.12 // indirect 58 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 59 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 60 | github.com/josharian/intern v1.0.0 // indirect 61 | github.com/json-iterator/go v1.1.12 // indirect 62 | github.com/kelseyhightower/envconfig v1.4.0 // indirect 63 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect 64 | github.com/mailru/easyjson v0.7.7 // indirect 65 | github.com/mattn/go-runewidth v0.0.9 // indirect 66 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 67 | github.com/mholt/archiver v3.1.1+incompatible 68 | github.com/mitchellh/go-homedir v1.1.0 // indirect 69 | github.com/moby/spdystream v0.2.0 // indirect 70 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 71 | github.com/modern-go/reflect2 v1.0.2 // indirect 72 | github.com/nwaples/rardecode v1.1.0 // indirect 73 | github.com/olekukonko/tablewriter v0.0.4 74 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 75 | github.com/pkg/errors v0.9.1 // indirect 76 | github.com/pmezard/go-difflib v1.0.0 // indirect 77 | github.com/prometheus/client_golang v1.11.1 // indirect 78 | github.com/prometheus/client_model v0.2.0 // indirect 79 | github.com/prometheus/common v0.32.1 // indirect 80 | github.com/prometheus/procfs v0.7.3 // indirect 81 | github.com/prometheus/statsd_exporter v0.21.0 // indirect 82 | github.com/rickb777/date v1.14.3 // indirect 83 | github.com/rickb777/plural v1.2.2 // indirect 84 | github.com/robfig/cron/v3 v3.0.1 // indirect 85 | github.com/sergi/go-diff v1.1.0 // indirect 86 | github.com/sirupsen/logrus v1.8.1 87 | github.com/spf13/afero v1.6.0 88 | github.com/spf13/cobra v1.3.0 89 | github.com/spf13/pflag v1.0.5 // indirect 90 | github.com/src-d/gcfg v1.4.0 // indirect 91 | github.com/stoewer/go-strcase v1.2.0 // indirect 92 | github.com/stretchr/testify v1.7.0 93 | github.com/tektoncd/pipeline v0.37.2 94 | github.com/tektoncd/triggers v0.11.2 95 | github.com/ulikunitz/xz v0.5.8 // indirect 96 | github.com/xanzy/ssh-agent v0.3.0 // indirect 97 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 98 | go.opencensus.io v0.23.0 // indirect 99 | go.uber.org/atomic v1.9.0 // indirect 100 | go.uber.org/multierr v1.7.0 // indirect 101 | go.uber.org/zap v1.19.1 // indirect 102 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292 103 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect 104 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect 105 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 106 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect 107 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 108 | golang.org/x/text v0.3.7 // indirect 109 | golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect 110 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 111 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 112 | google.golang.org/api v0.70.0 // indirect 113 | google.golang.org/appengine v1.6.7 // indirect 114 | google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect 115 | google.golang.org/grpc v1.46.0 // indirect 116 | google.golang.org/protobuf v1.28.0 // indirect 117 | gopkg.in/inf.v0 v0.9.1 // indirect 118 | gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect 119 | gopkg.in/src-d/go-git.v4 v4.13.1 120 | gopkg.in/warnings.v0 v0.1.2 // indirect 121 | gopkg.in/yaml.v2 v2.4.0 122 | gopkg.in/yaml.v3 v3.0.1 // indirect 123 | k8s.io/api v0.23.5 124 | k8s.io/apimachinery v0.23.5 125 | k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible 126 | k8s.io/klog/v2 v2.60.1-0.20220317184644-43cc75f9ae89 // indirect 127 | k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect 128 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect 129 | knative.dev/eventing-github v0.19.0 130 | knative.dev/networking v0.0.0-20220412163509-1145ec58c8be // indirect 131 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect 132 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect 133 | sigs.k8s.io/yaml v1.3.0 // indirect 134 | ) 135 | -------------------------------------------------------------------------------- /cmd/deploy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "github.com/spf13/cobra" 19 | "github.com/triggermesh/tm/pkg/client" 20 | ) 21 | 22 | func newDeployCmd(clientset *client.ConfigSet) *cobra.Command { 23 | deployCmd := &cobra.Command{ 24 | Use: "deploy", 25 | Aliases: []string{"create"}, 26 | Short: "Deploy knative resource", 27 | Run: func(cmd *cobra.Command, args []string) { 28 | s.Namespace = client.Namespace 29 | if clientset.Log.IsDebug() && concurrency > 1 { 30 | clientset.Log.Warnf(`You are about to run %d deployments in parallel with verbose logging - the output may be unreadable.`, concurrency) 31 | } 32 | if err := s.DeployYAML(yaml, args, concurrency, clientset); err != nil { 33 | clientset.Log.Fatal(err) 34 | } 35 | }, 36 | } 37 | 38 | deployCmd.Flags().StringVarP(&yaml, "from", "f", "serverless.yaml", "Deploy functions defined in yaml") 39 | deployCmd.Flags().IntVarP(&concurrency, "concurrency", "c", 3, "Number on concurrent deployment threads") 40 | 41 | deployCmd.AddCommand(cmdDeployService(clientset)) 42 | deployCmd.AddCommand(cmdDeployChannel(clientset)) 43 | deployCmd.AddCommand(cmdDeployTask(clientset)) 44 | deployCmd.AddCommand(cmdDeployTaskRun(clientset)) 45 | deployCmd.AddCommand(cmdDeployPipelineResource(clientset)) 46 | return deployCmd 47 | } 48 | 49 | func cmdDeployService(clientset *client.ConfigSet) *cobra.Command { 50 | deployServiceCmd := &cobra.Command{ 51 | Use: "service", 52 | Aliases: []string{"services", "svc"}, 53 | Short: "Deploy knative service", 54 | Args: cobra.ExactArgs(1), 55 | Example: "tm deploy service foo -f gcr.io/google-samples/hello-app:1.0", 56 | Run: func(cmd *cobra.Command, args []string) { 57 | s.Name = args[0] 58 | s.Namespace = client.Namespace 59 | output, err := s.Deploy(clientset) 60 | if err != nil { 61 | clientset.Log.Fatal(err) 62 | } 63 | clientset.Log.Infoln(output) 64 | }, 65 | } 66 | // kept for back compatibility 67 | deployServiceCmd.Flags().StringVar(&s.Source, "from-path", "", "Deprecated, use `-f` flag instead") 68 | deployServiceCmd.Flags().StringVar(&s.Source, "from-image", "", "Deprecated, use `-f` flag instead") 69 | deployServiceCmd.Flags().StringVar(&s.Source, "from-source", "", "Deprecated, use `-f` flag instead") 70 | deployServiceCmd.Flags().StringVar(&s.Runtime, "build-template", "", "Deprecated, use `--runtime` flag instead") 71 | 72 | deployServiceCmd.Flags().StringVarP(&s.Source, "from", "f", "", "Service source to deploy: local folder with sources, git repository or docker image") 73 | deployServiceCmd.Flags().StringVar(&s.Revision, "revision", "master", "Git revision (branch, tag, commit SHA or ref)") 74 | deployServiceCmd.Flags().StringVar(&s.Runtime, "runtime", "", "Existing task name, local path or URL to task yaml file") 75 | deployServiceCmd.Flags().StringVar(&s.BuildTimeout, "build-timeout", "10m", "Service image build timeout") 76 | deployServiceCmd.Flags().IntVar(&s.Concurrency, "concurrency", 0, "Number of concurrent events per container: 0 - multiple events, 1 - single event, N - particular number of events") 77 | deployServiceCmd.Flags().StringSliceVar(&s.BuildArgs, "build-argument", []string{}, "Build arguments") 78 | deployServiceCmd.Flags().StringSliceVar(&s.EnvSecrets, "env-secret", []string{}, "Name of k8s secrets to populate pod environment variables") 79 | deployServiceCmd.Flags().BoolVar(&s.BuildOnly, "build-only", false, "Build image and exit") 80 | deployServiceCmd.Flags().StringSliceVarP(&s.Labels, "label", "l", []string{}, "Service labels") 81 | deployServiceCmd.Flags().StringToStringVarP(&s.Annotations, "annotation", "a", map[string]string{}, "Revision template annotations") 82 | deployServiceCmd.Flags().StringSliceVarP(&s.Env, "env", "e", []string{}, "Environment variables of the service, eg. `--env foo=bar`") 83 | return deployServiceCmd 84 | } 85 | 86 | func cmdDeployChannel(clientset *client.ConfigSet) *cobra.Command { 87 | deployChannelCmd := &cobra.Command{ 88 | Use: "channel", 89 | Aliases: []string{"channels"}, 90 | Args: cobra.ExactArgs(1), 91 | Short: "Deploy knative eventing in-memory CRD channel", 92 | Run: func(cmd *cobra.Command, args []string) { 93 | c.Name = args[0] 94 | c.Namespace = client.Namespace 95 | if err := c.Deploy(clientset); err != nil { 96 | clientset.Log.Fatal(err) 97 | } 98 | }, 99 | } 100 | 101 | // only InMemoryChannel kind of channels available now 102 | // deployChannelCmd.Flags().StringVarP(&c.Kind, "kind", "k", "InMemoryChannel", "Channel kind") 103 | return deployChannelCmd 104 | } 105 | 106 | func cmdDeployTask(clientset *client.ConfigSet) *cobra.Command { 107 | deployTaskCmd := &cobra.Command{ 108 | Use: "task", 109 | Aliases: []string{"tasks"}, 110 | Short: "Deploy tekton Task object", 111 | Run: func(cmd *cobra.Command, args []string) { 112 | if len(args) == 1 { 113 | t.Name = args[0] 114 | } 115 | t.Namespace = client.Namespace 116 | if _, err := t.Deploy(clientset); err != nil { 117 | clientset.Log.Fatal(err) 118 | } 119 | clientset.Log.Infoln("Task installed") 120 | }, 121 | } 122 | deployTaskCmd.Flags().StringVarP(&t.File, "file", "f", "", "Task yaml manifest path") 123 | return deployTaskCmd 124 | } 125 | 126 | func cmdDeployTaskRun(clientset *client.ConfigSet) *cobra.Command { 127 | deployTaskRunCmd := &cobra.Command{ 128 | Use: "taskrun", 129 | Aliases: []string{"taskruns"}, 130 | Short: "Deploy tekton TaskRun object", 131 | Run: func(cmd *cobra.Command, args []string) { 132 | tr.Namespace = client.Namespace 133 | tr.Wait = client.Wait 134 | tr.Name = args[0] 135 | _, err := tr.Deploy(clientset) 136 | if err != nil { 137 | clientset.Log.Fatal(err) 138 | } 139 | }, 140 | } 141 | deployTaskRunCmd.Flags().StringVarP(&tr.Task.Name, "task", "t", "", "Name of task to run") 142 | deployTaskRunCmd.Flags().StringVarP(&tr.Function.Path, "file", "f", "", "Function source") 143 | deployTaskRunCmd.Flags().StringVarP(&tr.PipelineResource.Name, "resources", "r", "", "Name of pipelineresource to pass into task") 144 | // deployTaskRunCmd.Flags().StringVarP(&tr.RegistrySecret, "secret", "s", "", "Secret name with registry credentials") 145 | deployTaskRunCmd.Flags().StringArrayVar(&tr.Params, "args", []string{}, "Image build arguments") 146 | return deployTaskRunCmd 147 | } 148 | 149 | func cmdDeployPipelineResource(clientset *client.ConfigSet) *cobra.Command { 150 | deployPipelineResourceCmd := &cobra.Command{ 151 | Use: "pipelineresource", 152 | Aliases: []string{"pipelineresources"}, 153 | Args: cobra.ExactArgs(1), 154 | Short: "Deploy tekton PipelineResource object", 155 | Run: func(cmd *cobra.Command, args []string) { 156 | plr.Name = args[0] 157 | plr.Namespace = client.Namespace 158 | if _, err := plr.Deploy(clientset); err != nil { 159 | clientset.Log.Fatal(err) 160 | } 161 | clientset.Log.Infoln("PipelineResource created") 162 | }, 163 | } 164 | deployPipelineResourceCmd.Flags().StringVar(&plr.Source.URL, "url", "", "Git URL to get sources from") 165 | deployPipelineResourceCmd.Flags().StringVar(&plr.Source.Revision, "rev", "", "Git revision") 166 | return deployPipelineResourceCmd 167 | } 168 | -------------------------------------------------------------------------------- /cmd/get.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 TriggerMesh Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cmd 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | "github.com/triggermesh/tm/pkg/client" 24 | ) 25 | 26 | var ( 27 | data string 28 | ) 29 | 30 | // getCmd represents the get command 31 | var getCmd = &cobra.Command{ 32 | Use: "get", 33 | Aliases: []string{"list", "describe"}, 34 | Short: "Retrieve resources from k8s cluster", 35 | } 36 | 37 | // NewGetCmd returns "Get" cobra CLI command with its subcommands 38 | func newGetCmd(clientset *client.ConfigSet) *cobra.Command { 39 | getCmd.AddCommand(cmdListConfigurations(clientset)) 40 | getCmd.AddCommand(cmdListRevision(clientset)) 41 | getCmd.AddCommand(cmdListRoute(clientset)) 42 | getCmd.AddCommand(cmdListService(clientset)) 43 | getCmd.AddCommand(cmdListChannels(clientset)) 44 | getCmd.AddCommand(cmdListTasks(clientset)) 45 | getCmd.AddCommand(cmdListTaskRuns(clientset)) 46 | getCmd.AddCommand(cmdListPipelineResources(clientset)) 47 | 48 | return getCmd 49 | } 50 | 51 | func cmdListChannels(clientset *client.ConfigSet) *cobra.Command { 52 | return &cobra.Command{ 53 | Use: "channel", 54 | Aliases: []string{"channels"}, 55 | Short: "List of knative channel resources", 56 | Run: func(cmd *cobra.Command, args []string) { 57 | c.Namespace = client.Namespace 58 | if len(args) == 0 { 59 | list, err := c.List(clientset) 60 | if err != nil { 61 | clientset.Log.Fatalln(err) 62 | } 63 | if len(list.Items) == 0 { 64 | fmt.Fprintf(cmd.OutOrStdout(), "No channels found\n") 65 | return 66 | } 67 | clientset.Printer.PrintTable(c.GetTable(list)) 68 | return 69 | } 70 | c.Name = args[0] 71 | channel, err := c.Get(clientset) 72 | if err != nil { 73 | clientset.Log.Fatalln(err) 74 | } 75 | clientset.Printer.PrintObject(c.GetObject(channel)) 76 | }, 77 | } 78 | } 79 | 80 | func cmdListService(clientset *client.ConfigSet) *cobra.Command { 81 | return &cobra.Command{ 82 | Use: "service", 83 | Aliases: []string{"services"}, 84 | Short: "List of knative service resources", 85 | Args: cobra.MaximumNArgs(1), 86 | Run: func(cmd *cobra.Command, args []string) { 87 | s.Namespace = client.Namespace 88 | if len(args) == 0 { 89 | list, err := s.List(clientset) 90 | if err != nil { 91 | clientset.Log.Fatalln(err) 92 | } 93 | if len(list.Items) == 0 { 94 | fmt.Fprintf(cmd.OutOrStdout(), "No services found\n") 95 | return 96 | } 97 | clientset.Printer.PrintTable(s.GetTable(list)) 98 | return 99 | } 100 | s.Name = args[0] 101 | service, err := s.Get(clientset) 102 | if err != nil { 103 | clientset.Log.Fatalln(err) 104 | } 105 | clientset.Printer.PrintObject(s.GetObject(service)) 106 | }, 107 | } 108 | } 109 | 110 | func cmdListConfigurations(clientset *client.ConfigSet) *cobra.Command { 111 | return &cobra.Command{ 112 | Use: "configuration", 113 | Aliases: []string{"configurations"}, 114 | Short: "List of configurations", 115 | Run: func(cmd *cobra.Command, args []string) { 116 | cf.Namespace = client.Namespace 117 | if len(args) == 0 { 118 | list, err := cf.List(clientset) 119 | if err != nil { 120 | clientset.Log.Fatalln(err) 121 | } 122 | if len(list.Items) == 0 { 123 | fmt.Fprintf(cmd.OutOrStdout(), "No configurations found\n") 124 | return 125 | } 126 | clientset.Printer.PrintTable(cf.GetTable(list)) 127 | return 128 | } 129 | cf.Name = args[0] 130 | configuration, err := cf.Get(clientset) 131 | if err != nil { 132 | clientset.Log.Fatalln(err) 133 | } 134 | clientset.Printer.PrintObject(cf.GetObject(configuration)) 135 | }, 136 | } 137 | } 138 | 139 | func cmdListRevision(clientset *client.ConfigSet) *cobra.Command { 140 | return &cobra.Command{ 141 | Use: "revision", 142 | Aliases: []string{"revisions"}, 143 | Short: "List of knative revision resources", 144 | Run: func(cmd *cobra.Command, args []string) { 145 | r.Namespace = client.Namespace 146 | if len(args) == 0 { 147 | list, err := r.List(clientset) 148 | if err != nil { 149 | clientset.Log.Fatalln(err) 150 | } 151 | if len(list.Items) == 0 { 152 | fmt.Fprintf(cmd.OutOrStdout(), "No revisions found\n") 153 | return 154 | } 155 | clientset.Printer.PrintTable(r.GetTable(list)) 156 | return 157 | } 158 | r.Name = args[0] 159 | revision, err := r.Get(clientset) 160 | if err != nil { 161 | clientset.Log.Fatalln(err) 162 | } 163 | clientset.Printer.PrintObject(r.GetObject(revision)) 164 | }, 165 | } 166 | } 167 | 168 | func cmdListRoute(clientset *client.ConfigSet) *cobra.Command { 169 | return &cobra.Command{ 170 | Use: "route", 171 | Aliases: []string{"routes"}, 172 | Short: "List of knative routes resources", 173 | Run: func(cmd *cobra.Command, args []string) { 174 | rt.Namespace = client.Namespace 175 | if len(args) == 0 { 176 | list, err := rt.List(clientset) 177 | if err != nil { 178 | clientset.Log.Fatalln(err) 179 | } 180 | clientset.Printer.PrintTable(rt.GetTable(list)) 181 | return 182 | } 183 | rt.Name = args[0] 184 | route, err := rt.Get(clientset) 185 | if err != nil { 186 | clientset.Log.Fatalln(err) 187 | } 188 | clientset.Printer.PrintObject(rt.GetObject(route)) 189 | }, 190 | } 191 | } 192 | 193 | func cmdListTasks(clientset *client.ConfigSet) *cobra.Command { 194 | return &cobra.Command{ 195 | Use: "task", 196 | Aliases: []string{"tasks"}, 197 | Short: "List of tekton task resources", 198 | Run: func(cmd *cobra.Command, args []string) { 199 | t.Namespace = client.Namespace 200 | if len(args) == 0 { 201 | list, err := t.List(clientset) 202 | if err != nil { 203 | clientset.Log.Fatalln(err) 204 | } 205 | clientset.Printer.PrintTable(t.GetTable(list)) 206 | return 207 | } 208 | t.Name = args[0] 209 | task, err := t.Get(clientset) 210 | if err != nil { 211 | clientset.Log.Fatalln(err) 212 | } 213 | clientset.Printer.PrintObject(t.GetObject(task)) 214 | }, 215 | } 216 | } 217 | 218 | func cmdListTaskRuns(clientset *client.ConfigSet) *cobra.Command { 219 | return &cobra.Command{ 220 | Use: "taskrun", 221 | Aliases: []string{"taskruns"}, 222 | Short: "List of tekton TaskRun resources", 223 | Run: func(cmd *cobra.Command, args []string) { 224 | tr.Namespace = client.Namespace 225 | if len(args) == 0 { 226 | list, err := tr.List(clientset) 227 | if err != nil { 228 | clientset.Log.Fatalln(err) 229 | } 230 | clientset.Printer.PrintTable(tr.GetTable(list)) 231 | return 232 | } 233 | tr.Name = args[0] 234 | taskrun, err := tr.Get(clientset) 235 | if err != nil { 236 | clientset.Log.Fatalln(err) 237 | } 238 | clientset.Printer.PrintObject(tr.GetObject(taskrun)) 239 | }, 240 | } 241 | } 242 | 243 | func cmdListPipelineResources(clientset *client.ConfigSet) *cobra.Command { 244 | return &cobra.Command{ 245 | Use: "pipelineresource", 246 | Aliases: []string{"pipelineresources"}, 247 | Short: "List of tekton PipelineResources resources", 248 | Run: func(cmd *cobra.Command, args []string) { 249 | plr.Namespace = client.Namespace 250 | if len(args) == 0 { 251 | list, err := plr.List(clientset) 252 | if err != nil { 253 | clientset.Log.Fatalln(err) 254 | } 255 | clientset.Printer.PrintTable(plr.GetTable(list)) 256 | return 257 | } 258 | plr.Name = args[0] 259 | pipelineResource, err := plr.Get(clientset) 260 | if err != nil { 261 | clientset.Log.Fatalln(err) 262 | } 263 | clientset.Printer.PrintObject(plr.GetObject(pipelineResource)) 264 | }, 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /pkg/resources/task/create.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package task 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "io/ioutil" 21 | "strings" 22 | 23 | "github.com/ghodss/yaml" 24 | tekton "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 25 | "github.com/triggermesh/tm/pkg/client" 26 | "github.com/triggermesh/tm/pkg/file" 27 | corev1 "k8s.io/api/core/v1" 28 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | ) 31 | 32 | const ( 33 | kind = "Task" 34 | api = "tekton.dev/v1beta1" 35 | uploadDoneTrigger = ".uploadIsDone" 36 | ) 37 | 38 | // Deploy accepts path (local or URL) to tekton Task manifest and installs it 39 | func (t *Task) Deploy(clientset *client.ConfigSet) (*tekton.Task, error) { 40 | if !file.IsLocal(t.File) { 41 | clientset.Log.Debugf("cannot find %q locally, downloading", t.File) 42 | path, err := file.Download(t.File) 43 | if err != nil { 44 | return nil, fmt.Errorf("task not found: %s", err) 45 | } 46 | t.File = path 47 | } 48 | 49 | task, err := t.readYAML() 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | // sometimes, if input param type is not set, string value causing the error 55 | // so we're explicitly setting "string" param type 56 | // if inputs := task.TaskSpec().Inputs; inputs != nil { 57 | for k, v := range task.Spec.Params { 58 | if v.Type == "" { 59 | task.Spec.Params[k].Type = tekton.ParamTypeString 60 | } 61 | } 62 | // } 63 | 64 | task.SetNamespace(t.Namespace) 65 | if t.GenerateName != "" { 66 | task.SetName("") 67 | task.SetGenerateName(t.GenerateName) 68 | } else if t.Name != "" { 69 | task.SetName(t.Name) 70 | } 71 | 72 | if clientset.Registry.Secret != "" { 73 | clientset.Log.Debugf("setting registry secret %q for task \"%s/%s\"", clientset.Registry.Secret, task.GetNamespace(), task.GetName()) 74 | setupEnv(clientset, task) 75 | setupVolume(clientset, task) 76 | } 77 | 78 | if clientset.Registry.SkipTLS { 79 | clientset.Log.Debugf("setting '--skip-tls-verify' flag for task \"%s/%s\"", task.GetNamespace(), task.GetName()) 80 | setupArgs(clientset, task) 81 | } 82 | 83 | if t.FromLocalSource { 84 | clientset.Log.Debugf("adding source uploading step to task \"%s/%s\"", task.GetNamespace(), task.GetGenerateName()) 85 | task.Spec.Steps = append([]tekton.Step{t.customStep()}, task.Spec.Steps...) 86 | // if task.Spec.Inputs != nil { 87 | task.Spec.Resources = &tekton.TaskResources{} 88 | // } 89 | } 90 | if client.Dry { 91 | return task, nil 92 | } 93 | return t.CreateOrUpdate(task, clientset) 94 | } 95 | 96 | // Clone installs a copy of provided tekton task object with generated name suffix 97 | func (t *Task) Clone(clientset *client.ConfigSet, task *tekton.Task) (*tekton.Task, error) { 98 | task.Kind = kind 99 | task.APIVersion = api 100 | task.SetGenerateName(task.GetName() + "-") 101 | task.SetName("") 102 | task.SetResourceVersion("") 103 | if clientset.Registry.Secret != "" { 104 | clientset.Log.Debugf("setting registry secret %q for task \"%s/%s\"", clientset.Registry.Secret, task.GetNamespace(), task.GetName()) 105 | setupEnv(clientset, task) 106 | setupVolume(clientset, task) 107 | } 108 | if clientset.Registry.SkipTLS { 109 | clientset.Log.Debugf("setting '--skip-tls-verify' flag for task \"%s/%s\"", task.GetNamespace(), task.GetName()) 110 | setupArgs(clientset, task) 111 | } 112 | if t.FromLocalSource { 113 | clientset.Log.Debugf("adding source uploading step to task \"%s/%s\" clone", task.GetNamespace(), task.GetGenerateName()) 114 | task.Spec.Steps = append([]tekton.Step{t.customStep()}, task.Spec.Steps...) 115 | // if task.Spec.Inputs != nil { 116 | task.Spec.Resources = &tekton.TaskResources{} 117 | // } 118 | } 119 | if client.Dry { 120 | return task, nil 121 | } 122 | return t.CreateOrUpdate(task, clientset) 123 | } 124 | 125 | func (t *Task) customStep() tekton.Step { 126 | return tekton.Step{ 127 | Name: "sources-receiver", 128 | Image: "busybox", 129 | Command: []string{"sh"}, 130 | Args: []string{"-c", fmt.Sprintf(` 131 | while [ ! -f %s ]; do 132 | sleep 1; 133 | done; 134 | sync; 135 | mkdir -p /workspace/workspace; 136 | mv /home/*/* /workspace/workspace/; 137 | if [[ $? != 0 ]]; then 138 | mv /home/* /workspace/workspace/; 139 | fi 140 | ls -lah /workspace/workspace; 141 | sync;`, 142 | uploadDoneTrigger)}, 143 | } 144 | } 145 | 146 | func (t *Task) readYAML() (*tekton.Task, error) { 147 | var res tekton.Task 148 | yamlFile, err := ioutil.ReadFile(t.File) 149 | if err != nil { 150 | return &res, err 151 | } 152 | return &res, yaml.Unmarshal(yamlFile, &res) 153 | } 154 | 155 | // CreateOrUpdate creates new tekton Task object or updates existing one 156 | func (t *Task) CreateOrUpdate(task *tekton.Task, clientset *client.ConfigSet) (*tekton.Task, error) { 157 | ctx := context.Background() 158 | if task.GetGenerateName() != "" { 159 | return clientset.TektonTasks.TektonV1beta1().Tasks(t.Namespace).Create(ctx, task, metav1.CreateOptions{}) 160 | } 161 | 162 | taskObj, err := clientset.TektonTasks.TektonV1beta1().Tasks(t.Namespace).Create(ctx, task, metav1.CreateOptions{}) 163 | if k8serrors.IsAlreadyExists(err) { 164 | clientset.Log.Debugf("task %q is already exist, updating", task.GetName()) 165 | if taskObj, err = clientset.TektonTasks.TektonV1beta1().Tasks(t.Namespace).Get(ctx, task.ObjectMeta.Name, metav1.GetOptions{}); err != nil { 166 | return nil, err 167 | } 168 | task.ObjectMeta.ResourceVersion = taskObj.GetResourceVersion() 169 | taskObj, err = clientset.TektonTasks.TektonV1beta1().Tasks(t.Namespace).Update(ctx, task, metav1.UpdateOptions{}) 170 | } 171 | return taskObj, err 172 | } 173 | 174 | // SetOwner updates tekton Task object with provided owner reference 175 | func (t *Task) SetOwner(clientset *client.ConfigSet, owner metav1.OwnerReference) error { 176 | ctx := context.Background() 177 | task, err := clientset.TektonTasks.TektonV1beta1().Tasks(t.Namespace).Get(ctx, t.Name, metav1.GetOptions{}) 178 | if err != nil { 179 | return err 180 | } 181 | clientset.Log.Debugf("setting task \"%s/%s\" owner to %s/%s", task.GetNamespace(), task.GetName(), owner.Kind, owner.Name) 182 | task.SetOwnerReferences([]metav1.OwnerReference{owner}) 183 | _, err = clientset.TektonTasks.TektonV1beta1().Tasks(t.Namespace).Update(ctx, task, metav1.UpdateOptions{}) 184 | return err 185 | } 186 | 187 | func setupEnv(clientset *client.ConfigSet, task *tekton.Task) { 188 | for i, container := range task.Spec.Steps { 189 | appendConfig := true 190 | for j, env := range container.Env { 191 | if env.Name == "DOCKER_CONFIG" { 192 | task.Spec.Steps[i].Env[j].Value = "/" + clientset.Registry.Secret 193 | appendConfig = false 194 | break 195 | } 196 | } 197 | if appendConfig { 198 | task.Spec.Steps[i].Env = append(container.Env, corev1.EnvVar{ 199 | Name: "DOCKER_CONFIG", 200 | Value: "/" + clientset.Registry.Secret, 201 | }) 202 | } 203 | } 204 | } 205 | 206 | func setupVolume(clientset *client.ConfigSet, task *tekton.Task) { 207 | task.Spec.Volumes = []corev1.Volume{ 208 | { 209 | Name: clientset.Registry.Secret, 210 | VolumeSource: corev1.VolumeSource{ 211 | Secret: &corev1.SecretVolumeSource{ 212 | SecretName: clientset.Registry.Secret, 213 | }, 214 | }, 215 | }, 216 | } 217 | for i, step := range task.Spec.Steps { 218 | mounts := append(step.VolumeMounts, corev1.VolumeMount{ 219 | Name: clientset.Registry.Secret, 220 | MountPath: "/" + clientset.Registry.Secret, 221 | ReadOnly: true, 222 | }) 223 | task.Spec.Steps[i].VolumeMounts = mounts 224 | } 225 | } 226 | 227 | func setupArgs(clientset *client.ConfigSet, task *tekton.Task) { 228 | // not the best way to add kaniko build arguments 229 | for i, step := range task.Spec.Steps { 230 | exportStep := false 231 | if !strings.HasPrefix(step.Image, "gcr.io/kaniko-project/executor") { 232 | continue 233 | } 234 | for _, arg := range step.Args { 235 | if strings.HasPrefix(arg, "--destination=") { 236 | exportStep = true 237 | break 238 | } 239 | } 240 | // TODO check if "--skip-tls-verify" is already set 241 | if exportStep { 242 | task.Spec.Steps[i].Args = append(step.Args, "--skip-tls-verify") 243 | break 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /pkg/push/push.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 TriggerMesh Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package push 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strings" 21 | 22 | "github.com/ghodss/yaml" 23 | tekton "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 24 | "github.com/triggermesh/tm/pkg/client" 25 | "github.com/triggermesh/tm/pkg/file" 26 | "github.com/triggermesh/tm/pkg/resources/pipelineresource" 27 | "github.com/triggermesh/tm/pkg/resources/service" 28 | "github.com/triggermesh/tm/pkg/resources/task" 29 | "gopkg.in/src-d/go-git.v4" 30 | corev1 "k8s.io/api/core/v1" 31 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 32 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 | sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" 34 | duckv1 "knative.dev/pkg/apis/duck/v1" 35 | ) 36 | 37 | // Push tries to read git configuration in current directory and if it succeeds 38 | // tekton pipeline resource and task are being prepared to run "tm deploy" command. 39 | // Corresponding TaskRun object which binds these pipelineresources and tasks 40 | // is printed to stdout. 41 | func Push(clientset *client.ConfigSet, token string) error { 42 | repo, err := git.PlainOpen(".") 43 | if err != nil { 44 | return err 45 | } 46 | remote, err := repo.Remote("origin") 47 | if err != nil { 48 | return err 49 | } 50 | if remote == nil { 51 | return fmt.Errorf("nil remote") 52 | } 53 | if len(remote.Config().URLs) == 0 { 54 | return fmt.Errorf("no remote URLs") 55 | } 56 | 57 | url := remote.Config().URLs[0] 58 | if prefix := strings.Index(url, "@"); prefix != 0 { 59 | url = strings.ReplaceAll(url[prefix+1:], ":", "/") 60 | url = strings.TrimRight(url, ".git") 61 | } 62 | 63 | url = fmt.Sprintf("https://%s", url) 64 | parts := strings.Split(url, "/") 65 | project := parts[len(parts)-1] 66 | owner := parts[len(parts)-2] 67 | 68 | pipelineResourceObj := pipelineresource.PipelineResource{ 69 | Name: project, 70 | Namespace: client.Namespace, 71 | Source: pipelineresource.Git{ 72 | URL: url, 73 | }, 74 | } 75 | if _, err := pipelineResourceObj.Deploy(clientset); err != nil { 76 | return err 77 | } 78 | 79 | taskObj := task.Task{ 80 | Name: project, 81 | Namespace: client.Namespace, 82 | } 83 | 84 | if _, err := taskObj.CreateOrUpdate(getTask(project, client.Namespace), clientset); err != nil { 85 | return err 86 | } 87 | 88 | taskrunObj := getTaskRun(project, client.Namespace) 89 | taskrunYAML, err := yaml.Marshal(taskrunObj) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | configmapObj := &corev1.ConfigMap{ 95 | TypeMeta: metav1.TypeMeta{ 96 | Kind: "ConfigMap", 97 | APIVersion: "v1", 98 | }, 99 | ObjectMeta: metav1.ObjectMeta{ 100 | Name: project, 101 | Namespace: client.Namespace, 102 | }, 103 | Data: map[string]string{ 104 | "taskrun": string(taskrunYAML), 105 | }, 106 | } 107 | 108 | if err := createOrUpdateConfigmap(clientset, configmapObj); err != nil { 109 | return err 110 | } 111 | 112 | kservice := service.Service{ 113 | Name: project + "-transceiver", 114 | Namespace: client.Namespace, 115 | Source: "docker.io/triggermesh/transceiver", 116 | Env: []string{ 117 | "TASKRUN_CONFIGMAP=" + project, 118 | "NAMESPACE=" + client.Namespace, 119 | }, 120 | } 121 | 122 | if _, err := kservice.Deploy(clientset); err != nil { 123 | return err 124 | } 125 | 126 | containersource := getContainerSource(project, owner, token) 127 | if err := createOrUpdateContainersource(clientset, containersource); err != nil { 128 | return err 129 | } 130 | 131 | taskrunObj.SetGenerateName("") 132 | taskrunObj.SetName(fmt.Sprintf("%s-%s", project, file.RandStringDNS(6))) 133 | taskrunYAML, err = yaml.Marshal(taskrunObj) 134 | if err != nil { 135 | return err 136 | } 137 | fmt.Printf("%s\n", taskrunYAML) 138 | return nil 139 | } 140 | 141 | func getContainerSource(project, owner, token string) *sourcesv1.ContainerSource { 142 | return &sourcesv1.ContainerSource{ 143 | TypeMeta: metav1.TypeMeta{ 144 | Kind: "ContainerSource", 145 | APIVersion: "sources.knative.dev/v1", 146 | }, 147 | ObjectMeta: metav1.ObjectMeta{ 148 | Name: project, 149 | Namespace: client.Namespace, 150 | }, 151 | Spec: sourcesv1.ContainerSourceSpec{ 152 | SourceSpec: duckv1.SourceSpec{ 153 | Sink: duckv1.Destination{ 154 | Ref: &duckv1.KReference{ 155 | Kind: "Service", 156 | APIVersion: "serving.knative.dev/v1", 157 | Name: project + "-transceiver", 158 | }, 159 | }, 160 | }, 161 | Template: corev1.PodTemplateSpec{ 162 | Spec: corev1.PodSpec{ 163 | Containers: []corev1.Container{ 164 | { 165 | Name: "user-container", 166 | Image: "gcr.io/triggermesh/github-third-party-source", 167 | Env: []corev1.EnvVar{ 168 | { 169 | Name: "OWNER", 170 | Value: owner, 171 | }, { 172 | Name: "REPOSITORY", 173 | Value: project, 174 | }, { 175 | Name: "TOKEN", 176 | Value: token, 177 | }, { 178 | Name: "EVENT_TYPE", 179 | Value: "commit", 180 | }, 181 | }, 182 | }, 183 | }, 184 | }, 185 | }, 186 | }, 187 | } 188 | } 189 | 190 | func getTaskRun(taskName, namespace string) *tekton.TaskRun { 191 | return &tekton.TaskRun{ 192 | TypeMeta: metav1.TypeMeta{ 193 | APIVersion: "tekton.dev/v1beta1", 194 | Kind: "TaskRun", 195 | }, 196 | ObjectMeta: metav1.ObjectMeta{ 197 | GenerateName: taskName + "-", 198 | Namespace: namespace, 199 | }, 200 | Spec: tekton.TaskRunSpec{ 201 | Resources: &tekton.TaskRunResources{ 202 | Inputs: []tekton.TaskResourceBinding{ 203 | { 204 | PipelineResourceBinding: tekton.PipelineResourceBinding{ 205 | Name: "url", 206 | ResourceRef: &tekton.PipelineResourceRef{ 207 | Name: taskName, 208 | APIVersion: "tekton.dev/v1beta1", 209 | }, 210 | }, 211 | }, 212 | }, 213 | }, 214 | TaskRef: &tekton.TaskRef{ 215 | Name: taskName, 216 | Kind: "Task", 217 | APIVersion: "tekton.dev/v1beta1", 218 | }, 219 | }, 220 | } 221 | } 222 | 223 | func getTask(name, namespace string) *tekton.Task { 224 | return &tekton.Task{ 225 | TypeMeta: metav1.TypeMeta{ 226 | APIVersion: "tekton.dev/v1beta1", 227 | Kind: "Task", 228 | }, 229 | ObjectMeta: metav1.ObjectMeta{ 230 | Name: name, 231 | Namespace: namespace, 232 | }, 233 | Spec: tekton.TaskSpec{ 234 | Resources: &tekton.TaskResources{ 235 | Inputs: []tekton.TaskResource{ 236 | { 237 | ResourceDeclaration: tekton.ResourceDeclaration{ 238 | Name: "url", 239 | Type: tekton.PipelineResourceTypeGit, 240 | }, 241 | }, 242 | }, 243 | }, 244 | Steps: []tekton.Step{ 245 | { 246 | Name: "deploy", 247 | Image: "gcr.io/triggermesh/tm", 248 | Command: []string{"tm"}, 249 | // TODO: move sources from this illogical path; update pipelineresource/create.go 250 | Args: []string{"deploy", "-f", "/workspace/url/", "--wait"}, 251 | }, 252 | }, 253 | }, 254 | } 255 | } 256 | 257 | func createOrUpdateConfigmap(clientset *client.ConfigSet, cm *corev1.ConfigMap) error { 258 | ctx := context.Background() 259 | _, err := clientset.Core.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{}) 260 | if k8serrors.IsAlreadyExists(err) { 261 | cmObj, err := clientset.Core.CoreV1().ConfigMaps(cm.Namespace).Get(ctx, cm.Name, metav1.GetOptions{}) 262 | if err != nil { 263 | return err 264 | } 265 | cm.ObjectMeta.ResourceVersion = cmObj.GetResourceVersion() 266 | if _, err := clientset.Core.CoreV1().ConfigMaps(cm.Namespace).Update(ctx, cm, metav1.UpdateOptions{}); err != nil { 267 | return err 268 | } 269 | } else if err != nil { 270 | return err 271 | } 272 | return nil 273 | } 274 | 275 | func createOrUpdateContainersource(clientset *client.ConfigSet, cs *sourcesv1.ContainerSource) error { 276 | ctx := context.Background() 277 | _, err := clientset.Eventing.SourcesV1().ContainerSources(cs.Namespace).Create(ctx, cs, metav1.CreateOptions{}) 278 | if k8serrors.IsAlreadyExists(err) { 279 | csObj, err := clientset.Eventing.SourcesV1().ContainerSources(cs.Namespace).Get(ctx, cs.Name, metav1.GetOptions{}) 280 | if err != nil { 281 | return err 282 | } 283 | cs.ObjectMeta.ResourceVersion = csObj.GetResourceVersion() 284 | _, err = clientset.Eventing.SourcesV1().ContainerSources(cs.Namespace).Update(ctx, cs, metav1.UpdateOptions{}) 285 | if err != nil { 286 | return err 287 | } 288 | } else if err != nil { 289 | return err 290 | } 291 | return nil 292 | } 293 | -------------------------------------------------------------------------------- /HOWTO.md: -------------------------------------------------------------------------------- 1 | # TriggerMesh CLI How-To Guide 2 | 3 | - [TriggerMesh CLI How-To Guide](#triggermesh-cli-how-to-guide) 4 | - [Overview](#overview) 5 | - [Generating a Serverless Function](#generating-a-serverless-function) 6 | - [Deploying `tm` to Deploy a Service](#deploying-tm-to-deploy-a-service) 7 | - [Deploying a Function](#deploying-a-function) 8 | - [Deleteing a Function](#deleteing-a-function) 9 | - [Obtaining Details About a Function](#obtaining-details-about-a-function) 10 | - [Serverless.yaml Configuration](#serverlessyaml-configuration) 11 | - [Top-Level Definition](#top-level-definition) 12 | - [Provider](#provider) 13 | - [Function](#function) 14 | 15 | ## Overview 16 | 17 | The [TriggerMesh CLI][tm-cli] provides a quick and easy way to create and manage 18 | serverless functions. 19 | 20 | ## Generating a Serverless Function 21 | 22 | `tm generate --(python|go|ruby|node) ` will create a directory with 23 | `` or the name of the runtime environment passed as a flag, and 24 | containing two files: 25 | * `handler.(js|rb|py|go)` The source file containing the serverless implementation 26 | * `serverless.yaml` The [serverless][serverless] manifest file 27 | 28 | The [serverless.yaml](#serverlessyaml-configuration) contains the details required 29 | to build the serverless functions, and the handler contains the specific code. 30 | 31 | The generated handlers are designed for [AWS Lambda][aws-lambda], but make 32 | use of the [Triggermesh Knative Lambda Runtime][tm-klr] which will allow the 33 | handler to run on Knative, however there is no restriction to the types of 34 | runtimes that can be used when combined with the [serverless.yaml](#serverlessyaml-configuration) file. 35 | 36 | ## Deploying `tm` to Deploy a Service 37 | 38 | When given a `serverless.yaml` file, `tm` can be used to deploy and/or build a 39 | serverless function. In addition, `tm` can be used to delete the function once 40 | deployed, and retrieve details from a running function. 41 | 42 | The remaining commands will require a configuration file for the 43 | [TriggerMesh Cloud](https://cloud.triggermesh.io) or a Kubernetes configuration 44 | file. 45 | 46 | In the target Kubernetes cluster, there may be some additional requirements: 47 | * [Knative Local Registry][knative-local-registry] to provide a Docker Registry for deploying built services 48 | * [Tekton Pipeline][tekton] for building the containers that will run the service 49 | 50 | --- 51 | 52 | _NOTE_: An alternate Docker Registry can be used by passing `--registry-host` with 53 | `tm`, or specifying the registry in the 54 | [serverless.yaml](#serverlessyaml-configuration) file. If credentials are 55 | required to read or write to the registry, then run `tm set registry-auth ` to add the credentials to the cluster and use `--registry-secret` for the `tm deploy` command. 56 | 57 | --- 58 | 59 | _NOTE_: If the [serverless.yaml](#serverlessyaml-configuration) file will point 60 | to pre-built images, then Tekton will not be required. 61 | 62 | --- 63 | 64 | The global parameters that can be used: 65 | * `--namespace` or `-n` The kubernetes namespace to perform the action in 66 | * `--registry-host` The alternate Docker registry host to use (defaulting to [Knative Local Registry][knative-local-registry]) 67 | * `--registry-secret` The kubernetes secret in the namespace to use for authenticating against the Docker registry 68 | 69 | Other flags to help with debugging or usability: 70 | * `-d` Debug mode to enable verbose output 71 | * `--dry` Print the Kubernetes manifests instead of applying them directly to the cluster 72 | * `--wait` Run the command and wait for the service to become available 73 | 74 | ### Deploying a Function 75 | 76 | Deploying a new function or service can be done in one of two ways: 77 | * `serverless.yaml` file 78 | * Git repo, docker image, or source directory to upload 79 | 80 | To deploy using the `serverless.yaml` file, then the command to run would be: 81 | 82 | tm deploy [-f path/to/serverless.yaml] 83 | 84 | or (assuming the serverless.yaml file is in the same directory as the invocation): 85 | 86 | tm deploy 87 | 88 | To deploy using the repo, docker, or source variant: 89 | 90 | tm depoly service -f [repo|image|source] 91 | 92 | Note that `repo` points to a Git based repository such as 93 | `https://github.com/triggermesh/tm.git,` `image` is a docker image such as 94 | `docker.io/hello-world:latest`, and source is a path to the handler. 95 | 96 | When using `repo` or `source`, the `--runtime` flag is required to reference a 97 | Tekton task or a runtime recipe such as the 98 | [Triggermesh Knative Lambda Runtime][tm-klr] or [Triggermesh OpenFaaS Runtime][tm-openfaas] to build the function. 99 | 100 | Lastly, if using `repo`, then the `--revision ` flag can be used to select 101 | the branch, tag, or changeset from the repo. 102 | 103 | ### Deleteing a Function 104 | 105 | To delete the functions defined with a serverless.yaml file: 106 | 107 | tm delete [-f path/to/serverless.yaml] 108 | 109 | To delete the service: 110 | 111 | tm delete svc_name 112 | 113 | 114 | ### Obtaining Details About a Function 115 | 116 | Details on the function can be obtained using: 117 | 118 | tm get service 119 | 120 | # Serverless.yaml Configuration 121 | 122 | The `serverless.yaml` file syntax follows a structure similar to the 123 | [Serverless.com][serverless]. 124 | 125 | A sample `serverless.yaml` file would look like: 126 | ```yaml 127 | service: go-demo-service 128 | description: Sample knative service 129 | provider: 130 | name: triggermesh 131 | functions: 132 | go-function: 133 | source: main.go 134 | runtime: https://raw.githubusercontent.com/triggermesh/knative-lambda-runtime/master/go-1.x/runtime.yaml 135 | ``` 136 | 137 | ## Top-Level Definition 138 | 139 | | Name | Type | Description | 140 | |---|---|---| 141 | |service|string|Name of this collection of services| 142 | |description|string|_optional_ Human readable description of the services| 143 | |provider|[TriggermeshProvider](#provider)| specific attributes| 144 | |repository|string|_optional_ Git or local base of the serverless function repository| 145 | |functions|map[string][function](#function)| pairs describing serverless functions| 146 | |include|[]string|List of additional files containing function definitions| 147 | 148 | Describes the attributes at the 'top' level of the `serverless.yaml` file. 149 | 150 | ### Provider 151 | 152 | The provider will always be `triggermesh` when using the `tm` CLI. The other 153 | attributes reflect where the service will be deployed, and the global parameters 154 | to apply towards the defined functions. 155 | 156 | | Name | Type | Description | 157 | |---|---|---| 158 | |name|string|Name of the serverless provider being targeted, and must be `triggermesh`| 159 | |pull-policy|string||_optional_ The container pull policy to apply for after the functions are built| 160 | |namespace|string|_optional_ target namespace to deploy the services| 161 | |runtime|string|_optional default runtime environment to use to build the services| 162 | |buildtimeout|string|_optional_ Max runtime for building the functions before timing out the build process| 163 | |environment|map[string]string|_optional_ Global dictionary of environment variables| 164 | |env-secrets|[]string|_optional_ Global list of secrets that will be exposed as environment variables| 165 | |annotations|map[string]string|_optional_ Dictionary of metadata annotations to apply to all services defined in this file| 166 | |registry|string|_optional_ **deprecated** Docker registry server to push the services to when built| 167 | |registry-secret|string|_optional_ **deprecated** secret name for authenticated access to the registry| 168 | 169 | ### Function 170 | 171 | Define the attributes required for building and running a function. 172 | 173 | | Name | Type | Description | 174 | |---|---|---| 175 | |handler|string|_optional_ **deprecated** Analogous to _source_| 176 | |source|string|_optional_ Source file that provides the function implementation| 177 | |runtime|string|file or URL path to a yaml runtime definition on how to build the function as a container| 178 | |buildargs|[]string|_optional_ Arguments to pass to the runtime definition during the function build process| 179 | |description|string|_optional_ Human readable description of the function| 180 | |labels|[]string|_optional_ Kubernetes labels to apply to the function at runtime| 181 | |environment|map[string]string|_optional_ Environment name/value pairs to pass to the serverless function at runtime| 182 | |env-secrets|[]string|_optional_ List of secrets which get expanded as environment variables during the function runtime| 183 | |annotations|map[string]string|_optional_ Dictionary of metadata annotations to apply to the serverless function| 184 | 185 | At a minimum, one of `source` or `handler` is required. If `source` points to a 186 | file, then `runtime` will be required as well. 187 | 188 | 189 | [tm-cli]: https://github.com/triggermesh/tm 190 | [tm-klr]: https://github.com/triggermesh/knative-lambda-runtime 191 | [tm-openfaas]: https://github.com/triggermesh/openfaas-runtime 192 | 193 | [knative-local-registry]: https://github.com/triggermesh/knative-local-registry 194 | [serverless]: https://github.com/serverless/serverless/tree/master/docs 195 | [aws-lambda]: https://docs.aws.amazon.com/lambda 196 | [tekton]: https://tekton.dev --------------------------------------------------------------------------------