diff --git a/go.mod b/go.mod index 5e1f85cf..32732f55 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 // indirect github.com/dustin/go-humanize v1.0.0 github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a // indirect + github.com/emicklei/go-restful-openapi v1.2.0 // indirect github.com/fsnotify/fsnotify v1.4.7 github.com/ghodss/yaml v1.0.0 github.com/go-sql-driver/mysql v1.4.1 // indirect @@ -64,7 +65,7 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.2.0 github.com/spf13/pflag v1.0.1 // indirect - github.com/stretchr/testify v1.2.2 + github.com/stretchr/testify v1.3.0 github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670 // indirect github.com/urfave/cli v1.20.0 github.com/wcharczuk/go-chart v2.0.1+incompatible @@ -86,4 +87,5 @@ require ( k8s.io/apiextensions-apiserver v0.0.0-20190116211702-f0729a5940c5 k8s.io/apimachinery v0.0.0-20190116203031-d49e237a2683 k8s.io/client-go v7.0.0+incompatible + k8s.io/klog v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index 83f3ab2a..616a1216 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,10 @@ github.com/Azure/go-autorest v9.9.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWA github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14= github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.21.0 h1:0GKs+e8mn1RRUzfg9oUXv3v7ZieQLmOZF/bfnmmGhM8= github.com/Shopify/sarama v1.21.0/go.mod h1:yuqtN/pe8cXRWG5zPaO7hCfNJp5MwmkoJEoLjkm5tCQ= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= @@ -27,6 +31,7 @@ github.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU= @@ -51,10 +56,22 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a h1:A4wNiqeKqU56ZhtnzJCTyPZ1+cyu8jKtIchQ3TtxHgw= github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w= +github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful-openapi v1.2.0 h1:ohRZ1yEZERGzqaozBgxa3A0lt6c6KF14xhs3IL9ECwg= +github.com/emicklei/go-restful-openapi v1.2.0/go.mod h1:cy7o3Ge8ZWZ5E90mpEY81sJZZFs2pkuYcLvfngYy1l0= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.0.0-20180322222829-3a0015ad55fa h1:hr8WVDjg4JKtQptZpzyb196TmruCs7PIsdJz8KAOZp8= +github.com/go-openapi/jsonpointer v0.0.0-20180322222829-3a0015ad55fa/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20180322222742-3fb327e6747d h1:k3UQ7Z8yFYq0BNkYykKIheY0HlZBl1Hku+pO9HE9FNU= +github.com/go-openapi/jsonreference v0.0.0-20180322222742-3fb327e6747d/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20180415031709-bcff419492ee h1:eo0HQoNFtbiEc7+1gRF9pgW6azx8a1cO2fXcqq1MuD0= +github.com/go-openapi/spec v0.0.0-20180415031709-bcff419492ee/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20180405201759-811b1089cde9 h1:+vsw187FKvA2QUGAcE+vQSfyxqLbUXixPYRRMAzwu04= +github.com/go-openapi/swag v0.0.0-20180405201759-811b1089cde9/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= @@ -124,6 +141,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mailru/easyjson v0.0.0-20180323154445-8b799c424f57 h1:qhv1ir3dIyOFmFU+5KqG4dF3zSQTA4nn1DFhu2NQC44= +github.com/mailru/easyjson v0.0.0-20180323154445-8b799c424f57/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c h1:N7uWGS2fTwH/4BwxbHiJZNAFTSJ5yPU0emHsQWvkxEY= github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= @@ -181,10 +200,12 @@ github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670 h1:HQWT4ta3wW5GZ790GaqLCS+w1dvuA3rMfEQxLi+UOYU= github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= @@ -207,6 +228,7 @@ golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTz golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -265,3 +287,5 @@ k8s.io/apimachinery v0.0.0-20190116203031-d49e237a2683 h1:BmOSGJ1vwLhZKgDhTQlLF6 k8s.io/apimachinery v0.0.0-20190116203031-d49e237a2683/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/client-go v7.0.0+incompatible h1:kiH+Y6hn+pc78QS/mtBfMJAMIIaWevHi++JvOGEEQp4= k8s.io/client-go v7.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c= +k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/hack/release-build.sh b/hack/release-build.sh index 4d4c5614..17cca779 100755 --- a/hack/release-build.sh +++ b/hack/release-build.sh @@ -266,12 +266,27 @@ build_all() { fi mkdir -p $BUILDDIR - + + # generate swagger (OpenApi 2.0) doc before building bundle image + generate_swagger_doc + build_fission_bundle_image $version $date $gitcommit build_fetcher_image $version $date $gitcommit build_builder_image $version $date $gitcommit build_all_cli $version $date $gitcommit build_pre_upgrade_checks_image $version $date $gitcommit + + remove_generated_swagger_doc +} + +generate_swagger_doc() { + pushd $DIR/pkg/apis/fission.io/v1/tool + ./update-generated-swagger-docs.sh + popd +} + +remove_generated_swagger_doc() { + rm $DIR/pkg/apis/fission.io/v1/types_swagger_doc_generated.go } version=${VERSION} diff --git a/pkg/apis/fission.io/v1/tool/README.md b/pkg/apis/fission.io/v1/tool/README.md new file mode 100644 index 00000000..f3f4092d --- /dev/null +++ b/pkg/apis/fission.io/v1/tool/README.md @@ -0,0 +1,7 @@ +# How to update swagger (OpenAPI) struct description + +Run `update-generated-swagger-docs.sh` and it will parse all comments in `types.go`. + +```bash +./update-generated-swagger-docs.sh +``` diff --git a/pkg/apis/fission.io/v1/tool/boilerplate.generatego.txt b/pkg/apis/fission.io/v1/tool/boilerplate.generatego.txt new file mode 100644 index 00000000..ebeae43a --- /dev/null +++ b/pkg/apis/fission.io/v1/tool/boilerplate.generatego.txt @@ -0,0 +1,15 @@ +/* +Copyright 2019 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ diff --git a/pkg/apis/fission.io/v1/tool/swagger.sh b/pkg/apis/fission.io/v1/tool/swagger.sh new file mode 100755 index 00000000..d8e83387 --- /dev/null +++ b/pkg/apis/fission.io/v1/tool/swagger.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Please refer https://github.com/kubernetes/kubernetes/tree/master/hack for original file +# + +# Contains swagger related util functions. +# +set -o errexit +set -o nounset +set -o pipefail + +# Generates types_swagger_doc_generated file for the given group version. +# $1: Name of the group version +# $2: Path to the directory where types.go for that group version exists. This +# is the directory where the file will be generated. +kube::swagger::gen_types_swagger_doc() { + local group_version=$1 + local gv_dir=$2 + local TMPFILE + TMPFILE="${TMPDIR:-/tmp}/types_swagger_doc_generated.$(date +%s).go" + + echo "Generating swagger type docs for ${group_version} at ${gv_dir}" + + { + echo -e "$(cat boilerplate.generatego.txt)\n" + echo "package ${group_version##*/}" + cat < "${TMPFILE}" + + go run ./swagger_type_docs.go -s \ + "${gv_dir}/types.go" \ + -f - \ + >> "${TMPFILE}" + + echo "// AUTO-GENERATED FUNCTIONS END HERE" >> "${TMPFILE}" + + gofmt -w -s "${TMPFILE}" + mv "${TMPFILE}" "${gv_dir}/types_swagger_doc_generated.go" +} diff --git a/pkg/apis/fission.io/v1/tool/swagger_type_docs.go b/pkg/apis/fission.io/v1/tool/swagger_type_docs.go new file mode 100644 index 00000000..184c3d18 --- /dev/null +++ b/pkg/apis/fission.io/v1/tool/swagger_type_docs.go @@ -0,0 +1,75 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +// Please refer https://github.com/kubernetes/kubernetes/blob/master/cmd/genswaggertypedocs/swagger_type_docs.go for original file +// + +package main + +import ( + "fmt" + "io" + "os" + + kruntime "k8s.io/apimachinery/pkg/runtime" + + flag "github.com/spf13/pflag" + "k8s.io/klog" +) + +var ( + functionDest = flag.StringP("func-dest", "f", "-", "Output for swagger functions; '-' means stdout (default)") + typeSrc = flag.StringP("type-src", "s", "", "From where we are going to read the types") + verify = flag.BoolP("verify", "v", false, "Verifies if the given type-src file has documentation for every type") +) + +func main() { + flag.Parse() + + if *typeSrc == "" { + klog.Fatalf("Please define -s flag as it is the source file") + } + + var funcOut io.Writer + if *functionDest == "-" { + funcOut = os.Stdout + } else { + file, err := os.Create(*functionDest) + if err != nil { + klog.Fatalf("Couldn't open %v: %v", *functionDest, err) + } + defer file.Close() + funcOut = file + } + + docsForTypes := kruntime.ParseDocumentationFrom(*typeSrc) + + if *verify { + rc, err := kruntime.VerifySwaggerDocsExist(docsForTypes, funcOut) + if err != nil { + fmt.Fprintf(os.Stderr, "Error in verification process: %s\n", err) + } + os.Exit(rc) + } + + if len(docsForTypes) > 0 { + if err := kruntime.WriteSwaggerDocFunc(docsForTypes, funcOut); err != nil { + fmt.Fprintf(os.Stderr, "Error when writing swagger documentation functions: %s\n", err) + os.Exit(-1) + } + } +} diff --git a/pkg/apis/fission.io/v1/tool/update-generated-swagger-docs.sh b/pkg/apis/fission.io/v1/tool/update-generated-swagger-docs.sh new file mode 100755 index 00000000..783ef719 --- /dev/null +++ b/pkg/apis/fission.io/v1/tool/update-generated-swagger-docs.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Copyright 2015 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generates `types_swagger_doc_generated.go` files for API group +# versions. That file contains functions on API structs that return +# the comments that should be surfaced for the corresponding API type +# in our API docs. + + +# +# Please refer https://github.com/kubernetes/kubernetes/tree/master/hack for original file +# + +set -o errexit +set -o nounset +set -o pipefail + +FISSION_CRD_VERSION=v1 + +source "swagger.sh" + +# To avoid compile errors, remove the currently existing files. + +for group_version in "${FISSION_CRD_VERSION}"; do + kube::swagger::gen_types_swagger_doc "${group_version}" ../../${FISSION_CRD_VERSION} +done diff --git a/pkg/apis/fission.io/v1/typeDeepcopyFunc.go b/pkg/apis/fission.io/v1/typeDeepcopyFunc.go new file mode 100644 index 00000000..6b33a245 --- /dev/null +++ b/pkg/apis/fission.io/v1/typeDeepcopyFunc.go @@ -0,0 +1,291 @@ +/* +Copyright 2019 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/hashicorp/go-multierror" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Each CRD type needs: +// GetObjectKind (to satisfy the Object interface) +// +// In addition, each singular CRD type needs: +// GetObjectMeta (to satisfy the ObjectMetaAccessor interface) +// +// And each list CRD type needs: +// GetListMeta (to satisfy the ListMetaAccessor interface) + +func (f *Function) GetObjectKind() schema.ObjectKind { + return &f.TypeMeta +} +func (e *Environment) GetObjectKind() schema.ObjectKind { + return &e.TypeMeta +} +func (ht *HTTPTrigger) GetObjectKind() schema.ObjectKind { + return &ht.TypeMeta +} +func (w *KubernetesWatchTrigger) GetObjectKind() schema.ObjectKind { + return &w.TypeMeta +} +func (t *TimeTrigger) GetObjectKind() schema.ObjectKind { + return &t.TypeMeta +} +func (m *MessageQueueTrigger) GetObjectKind() schema.ObjectKind { + return &m.TypeMeta +} +func (p *Package) GetObjectKind() schema.ObjectKind { + return &p.TypeMeta +} +func (c *CanaryConfig) GetObjectKind() schema.ObjectKind { + return &c.TypeMeta +} + +func (r *Recorder) GetObjectKind() schema.ObjectKind { + return &r.TypeMeta +} + +func (f *Function) GetObjectMeta() metav1.Object { + return &f.Metadata +} +func (e *Environment) GetObjectMeta() metav1.Object { + return &e.Metadata +} +func (ht *HTTPTrigger) GetObjectMeta() metav1.Object { + return &ht.Metadata +} +func (w *KubernetesWatchTrigger) GetObjectMeta() metav1.Object { + return &w.Metadata +} +func (t *TimeTrigger) GetObjectMeta() metav1.Object { + return &t.Metadata +} +func (m *MessageQueueTrigger) GetObjectMeta() metav1.Object { + return &m.Metadata +} +func (p *Package) GetObjectMeta() metav1.Object { + return &p.Metadata +} +func (c *CanaryConfig) GetObjectMeta() metav1.Object { + return &c.Metadata +} + +func (r *Recorder) GetObjectMeta() metav1.Object { + return &r.Metadata +} + +func (fl *FunctionList) GetObjectKind() schema.ObjectKind { + return &fl.TypeMeta +} +func (el *EnvironmentList) GetObjectKind() schema.ObjectKind { + return &el.TypeMeta +} +func (hl *HTTPTriggerList) GetObjectKind() schema.ObjectKind { + return &hl.TypeMeta +} +func (wl *KubernetesWatchTriggerList) GetObjectKind() schema.ObjectKind { + return &wl.TypeMeta +} +func (wl *TimeTriggerList) GetObjectKind() schema.ObjectKind { + return &wl.TypeMeta +} +func (ml *MessageQueueTriggerList) GetObjectKind() schema.ObjectKind { + return &ml.TypeMeta +} +func (pl *PackageList) GetObjectKind() schema.ObjectKind { + return &pl.TypeMeta +} +func (rl *RecorderList) GetObjectKind() schema.ObjectKind { + return &rl.TypeMeta +} + +func (cl *CanaryConfigList) GetObjectKind() schema.ObjectKind { + return &cl.TypeMeta +} + +func (fl *FunctionList) GetListMeta() metav1.ListInterface { + return &fl.Metadata +} +func (el *EnvironmentList) GetListMeta() metav1.ListInterface { + return &el.Metadata +} +func (hl *HTTPTriggerList) GetListMeta() metav1.ListInterface { + return &hl.Metadata +} +func (wl *KubernetesWatchTriggerList) GetListMeta() metav1.ListInterface { + return &wl.Metadata +} +func (wl *TimeTriggerList) GetListMeta() metav1.ListInterface { + return &wl.Metadata +} +func (ml *MessageQueueTriggerList) GetListMeta() metav1.ListInterface { + return &ml.Metadata +} +func (pl *PackageList) GetListMeta() metav1.ListInterface { + return &pl.Metadata +} + +func (rl *RecorderList) GetListMeta() metav1.ListInterface { + return &rl.Metadata +} + +func (cl *CanaryConfigList) GetListMeta() metav1.ListInterface { + return &cl.Metadata +} + +func validateMetadata(field string, m metav1.ObjectMeta) error { + return ValidateKubeReference(field, m.Name, m.Namespace) +} + +func (p *Package) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("Package", p.Metadata), + p.Spec.Validate(), + p.Status.Validate()) + + return result.ErrorOrNil() +} + +func (pl *PackageList) Validate() error { + var result *multierror.Error + // not validate ListMeta + for _, p := range pl.Items { + result = multierror.Append(result, p.Validate()) + } + return result.ErrorOrNil() +} + +func (f *Function) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("Function", f.Metadata), + f.Spec.Validate()) + + return result.ErrorOrNil() +} + +func (fl *FunctionList) Validate() error { + var result *multierror.Error + for _, f := range fl.Items { + result = multierror.Append(result, f.Validate()) + } + return result.ErrorOrNil() +} + +func (e *Environment) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("Environment", e.Metadata), + e.Spec.Validate()) + + return result.ErrorOrNil() +} + +func (el *EnvironmentList) Validate() error { + var result *multierror.Error + for _, e := range el.Items { + result = multierror.Append(result, e.Validate()) + } + return result.ErrorOrNil() +} + +func (h *HTTPTrigger) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("HTTPTrigger", h.Metadata), + h.Spec.Validate()) + + return result.ErrorOrNil() +} + +func (hl *HTTPTriggerList) Validate() error { + var result *multierror.Error + for _, h := range hl.Items { + result = multierror.Append(result, h.Validate()) + } + return result.ErrorOrNil() +} + +func (k *KubernetesWatchTrigger) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("KubernetesWatchTrigger", k.Metadata), + k.Spec.Validate()) + + return result.ErrorOrNil() +} + +func (kl *KubernetesWatchTriggerList) Validate() error { + var result *multierror.Error + for _, k := range kl.Items { + result = multierror.Append(result, k.Validate()) + } + return result +} + +func (t *TimeTrigger) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("TimeTrigger", t.Metadata), + t.Spec.Validate()) + + return result.ErrorOrNil() +} + +func (tl *TimeTriggerList) Validate() error { + var result *multierror.Error + for _, t := range tl.Items { + result = multierror.Append(result, t.Validate()) + } + return result.ErrorOrNil() +} + +func (m *MessageQueueTrigger) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("MessageQueueTrigger", m.Metadata), + m.Spec.Validate()) + + return result.ErrorOrNil() +} + +func (ml *MessageQueueTriggerList) Validate() error { + var result *multierror.Error + for _, m := range ml.Items { + result = multierror.Append(result, m.Validate()) + } + return result.ErrorOrNil() +} + +func (r *Recorder) Validate() error { + var result *multierror.Error + + result = multierror.Append(result, + validateMetadata("Recorder", r.Metadata), + r.Spec.Validate()) + + return result.ErrorOrNil() +} diff --git a/pkg/apis/fission.io/v1/typefields.go b/pkg/apis/fission.io/v1/typefields.go deleted file mode 100644 index ec723a12..00000000 --- a/pkg/apis/fission.io/v1/typefields.go +++ /dev/null @@ -1,364 +0,0 @@ -/* -Copyright 2018 The Fission Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1 - -import ( - apiv1 "k8s.io/api/core/v1" -) - -type ( - // - // Functions and packages - // - - // ChecksumType specifies the checksum algorithm, such as - // sha256, used for a checksum. - ChecksumType string - - // Checksum of package contents when the contents are stored - // outside the Package struct. Type is the checksum algorithm; - // "sha256" is the only currently supported one. Sum is hex - // encoded. - Checksum struct { - Type ChecksumType `json:"type,omitempty"` - Sum string `json:"sum,omitempty"` - } - - // ArchiveType is either literal or URL, indicating whether - // the package is specified in the Archive struct or - // externally. - ArchiveType string - - // Package contains or references a collection of source or - // binary files. - Archive struct { - // Type defines how the package is specified: literal or URL. - Type ArchiveType `json:"type,omitempty"` - - // Literal contents of the package. Can be used for - // encoding packages below TODO (256KB?) size. - Literal []byte `json:"literal,omitempty"` - - // URL references a package. - URL string `json:"url,omitempty"` - - // Checksum ensures the integrity of packages - // refereced by URL. Ignored for literals. - Checksum Checksum `json:"checksum,omitempty"` - } - - EnvironmentReference struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - } - - SecretReference struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - } - - ConfigMapReference struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - } - - BuildStatus string - - PackageSpec struct { - Environment EnvironmentReference `json:"environment"` - Source Archive `json:"source,omitempty"` - Deployment Archive `json:"deployment,omitempty"` - BuildCommand string `json:"buildcmd,omitempty"` - // In the future, we can have a debug build here too - } - - PackageStatus struct { - BuildStatus BuildStatus `json:"buildstatus,omitempty"` - BuildLog string `json:"buildlog,omitempty"` // output of the build (errors etc) - } - - PackageRef struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - - // Including resource version in the reference forces the function to be updated on - // package update, making it possible to cache the function based on its metadata. - ResourceVersion string `json:"resourceversion,omitempty"` - } - - FunctionPackageRef struct { - PackageRef PackageRef `json:"packageref"` - - // FunctionName specifies a specific function within the package. This allows - // functions to share packages, by having different functions within the same - // package. - // - // Fission itself does not interpret this path. It is passed verbatim to - // build and runtime environments. - // - // This is optional: if unspecified, the environment has a default name. - FunctionName string `json:"functionName,omitempty"` - } - - //ExecutorType is the primary executor for an environment - ExecutorType string - - //StrategyType is the strategy to be used for function execution - StrategyType string - - // FunctionSpec describes the contents of the function. - FunctionSpec struct { - // Environment is the build and runtime environment that this function is - // associated with. An Environment with this name should exist, otherwise the - // function cannot be invoked. - Environment EnvironmentReference `json:"environment"` - - // Reference to a package containing deployment and optionally the source - Package FunctionPackageRef `json:"package"` - - Secrets []SecretReference `json:"secrets"` - ConfigMaps []ConfigMapReference `json:"configmaps"` - - // cpu and memory resources as per K8S standards - Resources apiv1.ResourceRequirements `json:"resources"` - - // InvokeStrategy is a set of controls which affect how function executes - InvokeStrategy InvokeStrategy - } - - /*InvokeStrategy is a set of controls over how the function executes. - It affects the performance and resource usage of the function. - - An InvokeStategy is of one of two types: ExecutionStrategy, which controls low-level - parameters such as which ExecutorType to use, when to autoscale, minimum and maximum - number of running instances, etc. A higher-level AbstractInvokeStrategy will also be - supported; this strategy would specify the target request rate of the function, - the target latency statistics, and the target cost (in terms of compute resources). - */ - InvokeStrategy struct { - ExecutionStrategy ExecutionStrategy - StrategyType StrategyType - } - - /*ExecutionStrategy specifies low-level parameters for function execution, - such as the number of instances. - - MinScale affects the cold start behaviour for a function. If MinScale is 0 then the - deployment is created on first invocation of function and is good for requests of - asynchronous nature. If MinScale is greater than 0 then MinScale number of pods are - created at the time of creation of function. This ensures faster response during first - invocation at the cost of consuming resources. - - MaxScale is the maximum number of pods that function will scale to based on TargetCPUPercent - and resources allocated to the function pod. - */ - ExecutionStrategy struct { - ExecutorType ExecutorType - MinScale int - MaxScale int - TargetCPUPercent int - SpecializationTimeout int - } - - FunctionReferenceType string - - FunctionReference struct { - // Type indicates whether this function reference is by name or selector. For now, - // the only supported reference type is by name. Future reference types: - // * Function by label or annotation - // * Branch or tag of a versioned function - // * A "rolling upgrade" from one version of a function to another - Type FunctionReferenceType `json:"type"` - - // Name of the function. - Name string `json:"name"` - - // Function Reference by weight. this map contains function name as key and its weight - // as the value. - FunctionWeights map[string]int `json:"functionweights"` - } - - // - // Environments - // - - Runtime struct { - // Image for containing the language runtime. - Image string `json:"image"` - - // LoadEndpointPort defines the port on which the - // server listens for function load - // requests. Optional; default 8888. - LoadEndpointPort int32 `json:"loadendpointport"` - - // LoadEndpointPath defines the relative URL on which - // the server listens for function load - // requests. Optional; default "/specialize". - LoadEndpointPath string `json:"loadendpointpath"` - - // FunctionEndpointPort defines the port on which the - // server listens for function requests. Optional; - // default 8888. - FunctionEndpointPort int32 `json:"functionendpointport"` - - // Container allows the modification of the deployed runtime - // container using the Kubernetes Container spec. Fission overrides - // the following fields: - // - Name - // - Image; set to the Runtime.Image - // - TerminationMessagePath - // - ImagePullPolicy - // (optional) - Container *apiv1.Container `json:"container,omitempty"` - - // Podspec allows modification of deployed runtime pod with Kubernetes PodSpec - // The merging logic is briefly described below and detailed MergePodSpec function - // - Volumes mounts and env variables for function and fetcher container are appended - // - All additional containers and init containers are appended - // - Volume definitions are appended - // - Lists such as tolerations, ImagePullSecrets, HostAliases are appended - // - Structs are merged and variables from pod spec take precedence - // (optional) - PodSpec *apiv1.PodSpec `json:"podspec,omitempty"` - } - - Builder struct { - // Image for containing the language runtime. - Image string `json:"image,omitempty"` - - // (Optional) Default build command to run for this build environment. - Command string `json:"command,omitempty"` - - // Container allows the modification of the deployed builder - // container using the Kubernetes Container spec. Fission overrides - // the following fields: - // - Name - // - Image; set to the Builder.Image - // - Command; set to the Builder.Command - // - TerminationMessagePath - // - ImagePullPolicy - // - ReadinessProbe - // (optional) - Container *apiv1.Container `json:"container,omitempty"` - } - - EnvironmentSpec struct { - // Environment API version - Version int `json:"version"` - - // Runtime container image etc.; required - Runtime Runtime `json:"runtime"` - - // Optional - Builder Builder `json:"builder"` - - // Optional, but strongly encouraged. Used to populate - // links from UI, CLI, etc. - DocumentationURL string `json:"documentationurl,omitempty"` - - // Optional, defaults to 'AllowedFunctionsPerContainerSingle' - AllowedFunctionsPerContainer AllowedFunctionsPerContainer `json:"allowedFunctionsPerContainer,omitempty"` - - // Optional, defaults to 'false' - AllowAccessToExternalNetwork bool `json:"allowAccessToExternalNetwork,omitempty"` - - // Request and limit resources for the environment - Resources apiv1.ResourceRequirements `json:"resources"` - - // The initial pool size for environment - Poolsize int `json:"poolsize,omitempty"` - - // The grace time for pod to perform connection draining before termination. The unit is in seconds. - // Optional, defaults to 360 seconds - TerminationGracePeriod int64 - - // KeepArchive is used by fetcher to determine if the extracted archive - // or unarchived file should be placed, which is then used by specialize handler - KeepArchive bool `json:"keeparchive"` - } - - AllowedFunctionsPerContainer string - - // - // Triggers - // - - HTTPTriggerSpec struct { - Host string `json:"host"` - RelativeURL string `json:"relativeurl"` - CreateIngress bool `json:"createingress"` - Method string `json:"method"` - FunctionReference FunctionReference `json:"functionref"` - } - - KubernetesWatchTriggerSpec struct { - Namespace string `json:"namespace"` - Type string `json:"type"` - LabelSelector map[string]string `json:"labelselector"` - FunctionReference FunctionReference `json:"functionref"` - } - - MessageQueueType string - - // MessageQueueTriggerSpec defines a binding from a topic in a - // message queue to a function. - MessageQueueTriggerSpec struct { - FunctionReference FunctionReference `json:"functionref"` - MessageQueueType MessageQueueType `json:"messageQueueType"` - Topic string `json:"topic"` - ResponseTopic string `json:"respTopic,omitempty"` - ErrorTopic string `json:"errorTopic"` - MaxRetries int `json:"maxRetries"` - ContentType string `json:"contentType"` - } - - // RecorderSpec defines a policy for recording requests and responses - // to a function, that can be later inspected or replayed. - RecorderSpec struct { - Name string `json:"name"` - Function string `json:"function"` - Triggers []string `json:"triggers"` - RetentionPolicy string `json:"retentionPolicy"` - EvictionPolicy string `json:"evictionPolicy"` - Enabled bool `json:"enabled"` - } - - // TimeTrigger invokes the specific function at a time or - // times specified by a cron string. - TimeTriggerSpec struct { - Cron string `json:"cron"` - FunctionReference `json:"functionref"` - } - - FailureType string - - // Canary Config Spec - CanaryConfigSpec struct { - Trigger string `json:"trigger"` - NewFunction string `json:"newfunction"` - OldFunction string `json:"oldfunction"` - WeightIncrement int `json:"weightincrement"` - WeightIncrementDuration string `json:"duration"` - FailureThreshold int `json:"failurethreshold"` - FailureType FailureType `json:"failureType"` - } - - // CanaryConfig Status - CanaryConfigStatus struct { - Status string `json:"status"` - } -) diff --git a/pkg/apis/fission.io/v1/types.go b/pkg/apis/fission.io/v1/types.go index 7c523753..bf2d03a8 100644 --- a/pkg/apis/fission.io/v1/types.go +++ b/pkg/apis/fission.io/v1/types.go @@ -17,9 +17,8 @@ limitations under the License. package v1 import ( - "github.com/hashicorp/go-multierror" + apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -47,18 +46,19 @@ type ( Metadata metav1.ObjectMeta `json:"metadata"` Spec PackageSpec `json:"spec"` + // Status indicates the build status of package. Status PackageStatus `json:"status"` } + // PackageList is a list of Packages. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object PackageList struct { metav1.TypeMeta `json:",inline"` Metadata metav1.ListMeta `json:"metadata"` - - Items []Package `json:"items"` + Items []Package `json:"items"` } - // Functions. + // Function is function runs within environment runtime with given package and secrets/configmaps. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object Function struct { metav1.TypeMeta `json:",inline"` @@ -66,15 +66,15 @@ type ( Spec FunctionSpec `json:"spec"` } + // FunctionList is a list of Functions. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object FunctionList struct { metav1.TypeMeta `json:",inline"` Metadata metav1.ListMeta `json:"metadata"` - - Items []Function `json:"items"` + Items []Function `json:"items"` } - // Environments. + // Environment is environment for building and running user functions. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object Environment struct { metav1.TypeMeta `json:",inline"` @@ -82,14 +82,15 @@ type ( Spec EnvironmentSpec `json:"spec"` } + // EnvironmentList is a list of Environments. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object EnvironmentList struct { metav1.TypeMeta `json:",inline"` Metadata metav1.ListMeta `json:"metadata"` - - Items []Environment `json:"items"` + Items []Environment `json:"items"` } + // HTTPTrigger is the trigger invokes user functions when receiving HTTP requests. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object HTTPTrigger struct { metav1.TypeMeta `json:",inline"` @@ -97,15 +98,15 @@ type ( Spec HTTPTriggerSpec `json:"spec"` } + // HTTPTriggerList is a list of HTTPTriggers // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object HTTPTriggerList struct { metav1.TypeMeta `json:",inline"` Metadata metav1.ListMeta `json:"metadata"` - - Items []HTTPTrigger `json:"items"` + Items []HTTPTrigger `json:"items"` } - // Kubernetes Watches as triggers + // KubernetesWatchTrigger watches kubernetes resource events and invokes functions. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object KubernetesWatchTrigger struct { metav1.TypeMeta `json:",inline"` @@ -113,15 +114,15 @@ type ( Spec KubernetesWatchTriggerSpec `json:"spec"` } + // KubernetesWatchTriggerList is a list of KubernetesWatchTriggers // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object KubernetesWatchTriggerList struct { metav1.TypeMeta `json:",inline"` - Metadata metav1.ListMeta `json:"metadata"` - - Items []KubernetesWatchTrigger `json:"items"` + Metadata metav1.ListMeta `json:"metadata"` + Items []KubernetesWatchTrigger `json:"items"` } - // Time triggers + // TimeTrigger invokes functions based on given cron schedule. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object TimeTrigger struct { metav1.TypeMeta `json:",inline"` @@ -129,6 +130,7 @@ type ( Spec TimeTriggerSpec `json:"spec"` } + // TimeTriggerList is a list of TimeTriggers. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object TimeTriggerList struct { metav1.TypeMeta `json:",inline"` @@ -137,7 +139,7 @@ type ( Items []TimeTrigger `json:"items"` } - // Message Queue triggers + // MessageQueueTrigger invokes functions when messages arrive to certain topic that trigger subscribes to. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object MessageQueueTrigger struct { metav1.TypeMeta `json:",inline"` @@ -145,14 +147,15 @@ type ( Spec MessageQueueTriggerSpec `json:"spec"` } + // MessageQueueTriggerList is a list of MessageQueueTriggers. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object MessageQueueTriggerList struct { metav1.TypeMeta `json:",inline"` - Metadata metav1.ListMeta `json:"metadata"` - - Items []MessageQueueTrigger `json:"items"` + Metadata metav1.ListMeta `json:"metadata"` + Items []MessageQueueTrigger `json:"items"` } + // Recorder allows user to record all traffic payload to a certain function. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object Recorder struct { metav1.TypeMeta `json:",inline"` @@ -160,6 +163,7 @@ type ( Spec RecorderSpec `json:"spec"` } + // RecorderList is a list of Recorders. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object RecorderList struct { metav1.TypeMeta `json:",inline"` @@ -168,6 +172,7 @@ type ( Items []Recorder `json:"items"` } + // CanaryConfig is for canary deployment of two functions. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object CanaryConfig struct { metav1.TypeMeta `json:",inline"` @@ -176,6 +181,7 @@ type ( Status CanaryConfigStatus `json:"status"` } + // CanaryConfigList is a list of CanaryConfigs. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object CanaryConfigList struct { metav1.TypeMeta `json:",inline"` @@ -183,272 +189,472 @@ type ( Items []CanaryConfig `json:"items"` } + + // + // Functions and packages + // + + // ChecksumType specifies the checksum algorithm, such as + // sha256, used for a checksum. + ChecksumType string + + // Checksum of package contents when the contents are stored + // outside the Package struct. Type is the checksum algorithm; + // "sha256" is the only currently supported one. Sum is hex + // encoded. + Checksum struct { + Type ChecksumType `json:"type,omitempty"` + Sum string `json:"sum,omitempty"` + } + + // ArchiveType is either literal or URL, indicating whether + // the package is specified in the Archive struct or + // externally. + ArchiveType string + + // Package contains or references a collection of source or + // binary files. + Archive struct { + // Type defines how the package is specified: literal or URL. + // Available value: + // - literal + // - url + Type ArchiveType `json:"type,omitempty"` + + // Literal contents of the package. Can be used for + // encoding packages below TODO (256KB?) size. + Literal []byte `json:"literal,omitempty"` + + // URL references a package. + URL string `json:"url,omitempty"` + + // Checksum ensures the integrity of packages + // refereced by URL. Ignored for literals. + Checksum Checksum `json:"checksum,omitempty"` + } + + // EnvironmentReference is a reference to a environment. + EnvironmentReference struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + } + + // SecretReference is a reference to a kubernetes secret. + SecretReference struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + } + + // ConfigMapReference is a reference to a kubernetes configmap. + ConfigMapReference struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + } + + // BuildStatus indicates the current build status of a package. + BuildStatus string + + // PackageSpec includes source/deploy archives and the reference of environment to build the package. + PackageSpec struct { + // Environment is a reference to the environment for building source archive. + Environment EnvironmentReference `json:"environment"` + + // Source is the archive contains source code and dependencies file. + // If the package status is in PENDING state, builder manager will then + // notify builder to compile source and save the result as deployable archive. + Source Archive `json:"source,omitempty"` + + // Deployment is the deployable archive that environment runtime used to run user function. + Deployment Archive `json:"deployment,omitempty"` + + // BuildCommand is a custom build command that builder used to build the source archive. + BuildCommand string `json:"buildcmd,omitempty"` + + // In the future, we can have a debug build here too + } + + // PackageStatus contains the build status of a package also the build log for examination. + PackageStatus struct { + // BuildStatus is the package build status. + BuildStatus BuildStatus `json:"buildstatus,omitempty"` + + // BuildLog stores build log during the compilation. + BuildLog string `json:"buildlog,omitempty"` // output of the build (errors etc) + } + + // PackageRef is a reference to the package. + PackageRef struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + + // Including resource version in the reference forces the function to be updated on + // package update, making it possible to cache the function based on its metadata. + ResourceVersion string `json:"resourceversion,omitempty"` + } + + // FunctionPackageRef includes the reference to the package also the entrypoint of package. + FunctionPackageRef struct { + // Package reference + PackageRef PackageRef `json:"packageref"` + + // FunctionName specifies a specific function within the package. This allows + // functions to share packages, by having different functions within the same + // package. + // + // Fission itself does not interpret this path. It is passed verbatim to + // build and runtime environments. + // + // This is optional: if unspecified, the environment has a default name. + FunctionName string `json:"functionName,omitempty"` + } + + // ExecutorType is the primary executor for an environment + ExecutorType string + + // StrategyType is the strategy to be used for function execution + StrategyType string + + // FunctionSpec describes the contents of the function. + FunctionSpec struct { + // Environment is the build and runtime environment that this function is + // associated with. An Environment with this name should exist, otherwise the + // function cannot be invoked. + Environment EnvironmentReference `json:"environment"` + + // Reference to a package containing deployment and optionally the source. + Package FunctionPackageRef `json:"package"` + + // Reference to a list of secrets. + Secrets []SecretReference `json:"secrets"` + + // Reference to a list of configmaps. + ConfigMaps []ConfigMapReference `json:"configmaps"` + + // cpu and memory resources as per K8S standards + // This is only for newdeploy to set up resource limitation + // when creating deployment for a function. + Resources apiv1.ResourceRequirements `json:"resources"` + + // InvokeStrategy is a set of controls which affect how function executes + InvokeStrategy InvokeStrategy + } + + // InvokeStrategy is a set of controls over how the function executes. + // It affects the performance and resource usage of the function. + // + // An InvokeStrategy is of one of two types: ExecutionStrategy, which controls low-level + // parameters such as which ExecutorType to use, when to autoscale, minimum and maximum + // number of running instances, etc. A higher-level AbstractInvokeStrategy will also be + // supported; this strategy would specify the target request rate of the function, + // the target latency statistics, and the target cost (in terms of compute resources). + InvokeStrategy struct { + + // ExecutionStrategy specifies low-level parameters for function execution, + // such as the number of instances. + ExecutionStrategy ExecutionStrategy + + // StrategyType is the strategy type of a function. + // Now it only supports 'execution'. + StrategyType StrategyType + } + + // ExecutionStrategy specifies low-level parameters for function execution, + // such as the number of instances. + // + // MinScale affects the cold start behaviour for a function. If MinScale is 0 then the + // deployment is created on first invocation of function and is good for requests of + // asynchronous nature. If MinScale is greater than 0 then MinScale number of pods are + // created at the time of creation of function. This ensures faster response during first + // invocation at the cost of consuming resources. + // + // MaxScale is the maximum number of pods that function will scale to based on TargetCPUPercent + // and resources allocated to the function pod. + ExecutionStrategy struct { + + // ExecutorType is the executor type of a function used. Defaults to "poolmgr". + // + // Available value: + // - poolmgr + // - newdeploy + ExecutorType ExecutorType + + // This is only for newdeploy to set up minimum replicas of deployment. + MinScale int + + // This is only for newdeploy to set up maximum replicas of deployment. + MaxScale int + + // This is only for newdeploy to set up target CPU utilization of HPA. + TargetCPUPercent int + + // This is the timeout setting for executor to wait for pod specialization. + // Currently, only newdeploy utilizes this value. + SpecializationTimeout int + } + + FunctionReferenceType string + + FunctionReference struct { + // Type indicates whether this function reference is by name or selector. For now, + // the only supported reference type is by "name". Future reference types: + // * Function by label or annotation + // * Branch or tag of a versioned function + // * A "rolling upgrade" from one version of a function to another + // Available value: + // - name + // - function-weights + Type FunctionReferenceType `json:"type"` + + // Name of the function. + Name string `json:"name"` + + // Function Reference by weight. this map contains function name as key and its weight + // as the value. This is for canary upgrade purpose. + FunctionWeights map[string]int `json:"functionweights"` + } + + // + // Environments + // + + // Runtime is the setting for environment runtime. + Runtime struct { + // Image for containing the language runtime. + Image string `json:"image"` + + // NOT USED NOW + // LoadEndpointPort defines the port on which the + // server listens for function load + // requests. Optional; default 8888. + LoadEndpointPort int32 `json:"-"` // `json:"loadendpointport"` + + // NOT USED NOW + // LoadEndpointPath defines the relative URL on which + // the server listens for function load + // requests. Optional; default "/specialize". + LoadEndpointPath string `json:"-"` // `json:"loadendpointpath"` + + // NOT USED NOW + // FunctionEndpointPort defines the port on which the + // server listens for function requests. Optional; + // default 8888. + FunctionEndpointPort int32 `json:"-"` // `json:"functionendpointport"` + + // (Optional) Container allows the modification of the deployed runtime + // container using the Kubernetes Container spec. Fission overrides + // the following fields: + // - Name + // - Image; set to the Runtime.Image + // - TerminationMessagePath + // - ImagePullPolicy + // + // You can set either PodSpec or Container, but not both. + Container *apiv1.Container `json:"container,omitempty"` + + // (Optional) Podspec allows modification of deployed runtime pod with Kubernetes PodSpec + // The merging logic is briefly described below and detailed MergePodSpec function + // - Volumes mounts and env variables for function and fetcher container are appended + // - All additional containers and init containers are appended + // - Volume definitions are appended + // - Lists such as tolerations, ImagePullSecrets, HostAliases are appended + // - Structs are merged and variables from pod spec take precedence + // + // You can set either PodSpec or Container, but not both. + PodSpec *apiv1.PodSpec `json:"podspec,omitempty"` + } + + // Builder is the setting for environment builder. + Builder struct { + // Image for containing the language compilation environment. + Image string `json:"image,omitempty"` + + // (Optional) Default build command to run for this build environment. + Command string `json:"command,omitempty"` + + // (Optional) Container allows the modification of the deployed builder + // container using the Kubernetes Container spec. Fission overrides + // the following fields: + // - Name + // - Image; set to the Builder.Image + // - Command; set to the Builder.Command + // - TerminationMessagePath + // - ImagePullPolicy + // - ReadinessProbe + Container *apiv1.Container `json:"container,omitempty"` + } + + // EnvironmentSpec contains with builder, runtime and some other related environment settings. + EnvironmentSpec struct { + // Version is the Environment API version + // + // Version "1" allows user to run code snippet in a file and + // it's supported by most of environments except tensorflow-serving. + // + // Version "2" supports downloading and compiling user function if source archive is not empty. + // + // Version "3" is almost the same with v2, but you're able to control the size of pre-warm pool of the environment. + Version int `json:"version"` + + // Runtime is configuration for running function, like container image etc. + Runtime Runtime `json:"runtime"` + + // (Optional) Builder is configuration for builder manager to launch environment builder to build source code into + // deployable binary. + Builder Builder `json:"builder"` + + // NOT USED NOW. + // (Optional) Strongly encouraged. Used to populate links from UI, CLI, etc. + DocumentationURL string `json:"-"` // `json:"documentationurl,omitempty"` + + // (Optional) defaults to 'single'. Fission workflow uses + // 'infinite' to load multiple functions in one function pod. + // Available value: + // - single + // - infinite + AllowedFunctionsPerContainer AllowedFunctionsPerContainer `json:"allowedFunctionsPerContainer,omitempty"` + + // Istio default blocks all egress traffic for safety. + // To enable accessibility of external network for builder/function pod, set to 'true'. + // (Optional) defaults to 'false' + AllowAccessToExternalNetwork bool `json:"allowAccessToExternalNetwork,omitempty"` + + // The request and limit CPU/MEM resource setting for poolmanager to set up pods in the pre-warm pool. + // (Optional) defaults to no limitation. + Resources apiv1.ResourceRequirements `json:"resources"` + + // The initial pool size for environment + Poolsize int `json:"poolsize,omitempty"` + + // The grace time for pod to perform connection draining before termination. The unit is in seconds. + // (Optional) defaults to 360 seconds + TerminationGracePeriod int64 `json:"terminationGracePeriod,omitempty"` + + // KeepArchive is used by fetcher to determine if the extracted archive + // or unarchived file should be placed, which is then used by specialize handler. + // (This is mainly for the JVM environment because .jar is one kind of zip archive.) + KeepArchive bool `json:"keeparchive"` + } + + AllowedFunctionsPerContainer string + + // + // Triggers + // + + // HTTPTriggerSpec is for router to expose user functions at the given URL path. + HTTPTriggerSpec struct { + // NOT USED NOW + Host string `json:"-"` //`json:"host"` + + // RelativeURL is the exposed URL for external client to access a function with. + RelativeURL string `json:"relativeurl"` + + // If CreateIngress is true, router will create a ingress definition. + CreateIngress bool `json:"createingress"` + + // HTTP method to access a function. + Method string `json:"method"` + + // FunctionReference is a reference to the target function. + FunctionReference FunctionReference `json:"functionref"` + } + + // KubernetesWatchTriggerSpec + KubernetesWatchTriggerSpec struct { + Namespace string `json:"namespace"` + + // Type of resource to watch (Pod, Service, etc.) + Type string `json:"type"` + + // Resource labels + LabelSelector map[string]string `json:"labelselector"` + + // The reference to a function for kubewatcher to invoke with + // when receiving events. + FunctionReference FunctionReference `json:"functionref"` + } + + // Type of message queue + MessageQueueType string + + // MessageQueueTriggerSpec defines a binding from a topic in a + // message queue to a function. + MessageQueueTriggerSpec struct { + // The reference to a function for message queue trigger to invoke with + // when receiving messages from subscribed topic. + FunctionReference FunctionReference `json:"functionref"` + + // Type of message queue (NATS, Kafka, AzureQueue) + MessageQueueType MessageQueueType `json:"messageQueueType"` + + // Subscribed topic + Topic string `json:"topic"` + + // Topic for message queue trigger to sent response from function. + ResponseTopic string `json:"respTopic,omitempty"` + + // Topic to collect error response sent from function + ErrorTopic string `json:"errorTopic"` + + // Maximum times for message queue trigger to retry + MaxRetries int `json:"maxRetries"` + + // Content type of payload + ContentType string `json:"contentType"` + } + + // RecorderSpec defines a policy for recording requests and responses + // to a function, that can be later inspected or replayed. + RecorderSpec struct { + // Name of recorder resource + Name string `json:"name"` + + // Function to collect requests/responses + Function string `json:"function"` + + // HTTP trigger to record the requests and responses. + Triggers []string `json:"triggers"` + RetentionPolicy string `json:"-"` // `json:"retentionPolicy"` + EvictionPolicy string `json:"-"` // `json:"evictionPolicy"` + Enabled bool `json:"enabled"` + } + + // TimeTrigger invokes the specific function at a time or + // times specified by a cron string. + TimeTriggerSpec struct { + // Cron schedule + Cron string `json:"cron"` + + // The reference to function + FunctionReference `json:"functionref"` + } + + FailureType string + + // Canary Config Spec + CanaryConfigSpec struct { + // HTTP trigger that this config references + Trigger string `json:"trigger"` + + // New version of the function + NewFunction string `json:"newfunction"` + + // Old stable version of the function + OldFunction string `json:"oldfunction"` + + // Weight increment step for function + WeightIncrement int `json:"weightincrement"` + + // Weight increment interval, string representation of time.Duration, ex : 1m, 2h, 2d (default: "2m") + WeightIncrementDuration string `json:"duration"` + + // Threshold in percentage beyond which the new version of the function is considered unstable + FailureThreshold int `json:"failurethreshold"` + FailureType FailureType `json:"failureType"` + } + + // CanaryConfig Status + CanaryConfigStatus struct { + Status string `json:"status"` + } ) - -// Each CRD type needs: -// GetObjectKind (to satisfy the Object interface) -// -// In addition, each singular CRD type needs: -// GetObjectMeta (to satisfy the ObjectMetaAccessor interface) -// -// And each list CRD type needs: -// GetListMeta (to satisfy the ListMetaAccessor interface) - -func (f *Function) GetObjectKind() schema.ObjectKind { - return &f.TypeMeta -} -func (e *Environment) GetObjectKind() schema.ObjectKind { - return &e.TypeMeta -} -func (ht *HTTPTrigger) GetObjectKind() schema.ObjectKind { - return &ht.TypeMeta -} -func (w *KubernetesWatchTrigger) GetObjectKind() schema.ObjectKind { - return &w.TypeMeta -} -func (t *TimeTrigger) GetObjectKind() schema.ObjectKind { - return &t.TypeMeta -} -func (m *MessageQueueTrigger) GetObjectKind() schema.ObjectKind { - return &m.TypeMeta -} -func (p *Package) GetObjectKind() schema.ObjectKind { - return &p.TypeMeta -} -func (c *CanaryConfig) GetObjectKind() schema.ObjectKind { - return &c.TypeMeta -} - -func (r *Recorder) GetObjectKind() schema.ObjectKind { - return &r.TypeMeta -} - -func (f *Function) GetObjectMeta() metav1.Object { - return &f.Metadata -} -func (e *Environment) GetObjectMeta() metav1.Object { - return &e.Metadata -} -func (ht *HTTPTrigger) GetObjectMeta() metav1.Object { - return &ht.Metadata -} -func (w *KubernetesWatchTrigger) GetObjectMeta() metav1.Object { - return &w.Metadata -} -func (t *TimeTrigger) GetObjectMeta() metav1.Object { - return &t.Metadata -} -func (m *MessageQueueTrigger) GetObjectMeta() metav1.Object { - return &m.Metadata -} -func (p *Package) GetObjectMeta() metav1.Object { - return &p.Metadata -} -func (c *CanaryConfig) GetObjectMeta() metav1.Object { - return &c.Metadata -} - -func (r *Recorder) GetObjectMeta() metav1.Object { - return &r.Metadata -} - -func (fl *FunctionList) GetObjectKind() schema.ObjectKind { - return &fl.TypeMeta -} -func (el *EnvironmentList) GetObjectKind() schema.ObjectKind { - return &el.TypeMeta -} -func (hl *HTTPTriggerList) GetObjectKind() schema.ObjectKind { - return &hl.TypeMeta -} -func (wl *KubernetesWatchTriggerList) GetObjectKind() schema.ObjectKind { - return &wl.TypeMeta -} -func (wl *TimeTriggerList) GetObjectKind() schema.ObjectKind { - return &wl.TypeMeta -} -func (ml *MessageQueueTriggerList) GetObjectKind() schema.ObjectKind { - return &ml.TypeMeta -} -func (pl *PackageList) GetObjectKind() schema.ObjectKind { - return &pl.TypeMeta -} -func (rl *RecorderList) GetObjectKind() schema.ObjectKind { - return &rl.TypeMeta -} - -func (cl *CanaryConfigList) GetObjectKind() schema.ObjectKind { - return &cl.TypeMeta -} - -func (fl *FunctionList) GetListMeta() metav1.ListInterface { - return &fl.Metadata -} -func (el *EnvironmentList) GetListMeta() metav1.ListInterface { - return &el.Metadata -} -func (hl *HTTPTriggerList) GetListMeta() metav1.ListInterface { - return &hl.Metadata -} -func (wl *KubernetesWatchTriggerList) GetListMeta() metav1.ListInterface { - return &wl.Metadata -} -func (wl *TimeTriggerList) GetListMeta() metav1.ListInterface { - return &wl.Metadata -} -func (ml *MessageQueueTriggerList) GetListMeta() metav1.ListInterface { - return &ml.Metadata -} -func (pl *PackageList) GetListMeta() metav1.ListInterface { - return &pl.Metadata -} - -func (rl *RecorderList) GetListMeta() metav1.ListInterface { - return &rl.Metadata -} - -func (cl *CanaryConfigList) GetListMeta() metav1.ListInterface { - return &cl.Metadata -} - -func validateMetadata(field string, m metav1.ObjectMeta) error { - return ValidateKubeReference(field, m.Name, m.Namespace) -} - -func (p *Package) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("Package", p.Metadata), - p.Spec.Validate(), - p.Status.Validate()) - - return result.ErrorOrNil() -} - -func (pl *PackageList) Validate() error { - var result *multierror.Error - // not validate ListMeta - for _, p := range pl.Items { - result = multierror.Append(result, p.Validate()) - } - return result.ErrorOrNil() -} - -func (f *Function) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("Function", f.Metadata), - f.Spec.Validate()) - - return result.ErrorOrNil() -} - -func (fl *FunctionList) Validate() error { - var result *multierror.Error - for _, f := range fl.Items { - result = multierror.Append(result, f.Validate()) - } - return result.ErrorOrNil() -} - -func (e *Environment) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("Environment", e.Metadata), - e.Spec.Validate()) - - return result.ErrorOrNil() -} - -func (el *EnvironmentList) Validate() error { - var result *multierror.Error - for _, e := range el.Items { - result = multierror.Append(result, e.Validate()) - } - return result.ErrorOrNil() -} - -func (h *HTTPTrigger) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("HTTPTrigger", h.Metadata), - h.Spec.Validate()) - - return result.ErrorOrNil() -} - -func (hl *HTTPTriggerList) Validate() error { - var result *multierror.Error - for _, h := range hl.Items { - result = multierror.Append(result, h.Validate()) - } - return result.ErrorOrNil() -} - -func (k *KubernetesWatchTrigger) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("KubernetesWatchTrigger", k.Metadata), - k.Spec.Validate()) - - return result.ErrorOrNil() -} - -func (kl *KubernetesWatchTriggerList) Validate() error { - var result *multierror.Error - for _, k := range kl.Items { - result = multierror.Append(result, k.Validate()) - } - return result -} - -func (t *TimeTrigger) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("TimeTrigger", t.Metadata), - t.Spec.Validate()) - - return result.ErrorOrNil() -} - -func (tl *TimeTriggerList) Validate() error { - var result *multierror.Error - for _, t := range tl.Items { - result = multierror.Append(result, t.Validate()) - } - return result.ErrorOrNil() -} - -func (m *MessageQueueTrigger) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("MessageQueueTrigger", m.Metadata), - m.Spec.Validate()) - - return result.ErrorOrNil() -} - -func (ml *MessageQueueTriggerList) Validate() error { - var result *multierror.Error - for _, m := range ml.Items { - result = multierror.Append(result, m.Validate()) - } - return result.ErrorOrNil() -} - -func (r *Recorder) Validate() error { - var result *multierror.Error - - result = multierror.Append(result, - validateMetadata("Recorder", r.Metadata), - r.Spec.Validate()) - - return result.ErrorOrNil() -} diff --git a/pkg/controller/api.go b/pkg/controller/api.go index 37db06a3..97cefab1 100644 --- a/pkg/controller/api.go +++ b/pkg/controller/api.go @@ -270,6 +270,8 @@ func (api *API) Serve(port int) { r.HandleFunc("/proxy/workflows-apiserver/{path:.*}", api.WorkflowApiserverProxy) r.HandleFunc("/proxy/svcname", api.GetSvcName).Queries("application", "").Methods("GET") + r.Handle("/v2/apidocs.json", openAPI()).Methods("GET") + address := fmt.Sprintf(":%v", port) api.logger.Info("server started", zap.Int("port", port)) diff --git a/pkg/controller/canaryConfigApi.go b/pkg/controller/canaryConfigApi.go index 7aa44a67..ffe4e6e2 100644 --- a/pkg/controller/canaryConfigApi.go +++ b/pkg/controller/canaryConfigApi.go @@ -22,6 +22,9 @@ import ( "io/ioutil" "net/http" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "github.com/gorilla/mux" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,6 +34,73 @@ import ( config "github.com/fission/fission/pkg/featureconfig" ) +func RegisterCanaryConfigRoute(ws *restful.WebService) { + tags := []string{"CanaryConfig"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "CanaryConfig", Description: "CanaryConfig Operation"}}) + + ws.Route( + ws.GET("/v2/canaryconfigs"). + Doc("List all canary configs"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of canaryConfig").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.CanaryConfig{}). + Returns(http.StatusOK, "List of canaryConfigs", []fv1.CanaryConfig{})) + + ws.Route( + ws.POST("/v2/canaryconfigs"). + Doc("Create canary config"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.CanaryConfig{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusCreated, "Metadata of created canaryConfig", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/canaryconfigs/{canaryConfig}"). + Doc("Get detail of canary config"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("canaryConfig", "CanaryConfig name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of canaryConfig").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.CanaryConfig{}). // on the response + Returns(http.StatusOK, "A canaryConfig", fv1.CanaryConfig{})) + + ws.Route( + ws.PUT("/v2/canaryconfigs/{canaryConfig}"). + Doc("Update canary config"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("canaryConfig", "CanaryConfig name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.CanaryConfig{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated canaryConfig", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/canaryconfigs/{canaryConfig}"). + Doc("Delete canary config"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("canaryConfig", "CanaryConfig name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of canaryConfig").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) CanaryConfigApiCreate(w http.ResponseWriter, r *http.Request) { featureErr := a.featureStatus[config.CanaryFeature] if len(featureErr) > 0 { diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 93ece256..c3c8fb19 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -49,6 +49,9 @@ func Start(logger *zap.Logger, port int, unitTestFlag bool) { } defer cancel() + // openapi service + //go openAPI(cLogger) + api, err := MakeAPI(cLogger, featureStatus) if err != nil { cLogger.Fatal("failed to start controller", zap.Error(err)) diff --git a/pkg/controller/environmentApi.go b/pkg/controller/environmentApi.go index 656cf867..2396c1db 100644 --- a/pkg/controller/environmentApi.go +++ b/pkg/controller/environmentApi.go @@ -21,6 +21,9 @@ import ( "io/ioutil" "net/http" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "github.com/gorilla/mux" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,6 +32,73 @@ import ( ferror "github.com/fission/fission/pkg/error" ) +func RegisterEnvironmentRoute(ws *restful.WebService) { + tags := []string{"Environment"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "Environment", Description: "Environment Operation"}}) + + ws.Route( + ws.GET("/v2/environments"). + Doc("List all environments"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of environment").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.Environment{}). + Returns(http.StatusOK, "List of environments", []fv1.Environment{})) + + ws.Route( + ws.POST("/v2/environments"). + Doc("Create environment"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.Environment{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusCreated, "Metadata of created environment", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/environments/{environment}"). + Doc("Get detail of environment"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("environment", "Environment name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of environment").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.Environment{}). // on the response + Returns(http.StatusOK, "A environment", fv1.Environment{})) + + ws.Route( + ws.PUT("/v2/environments/{environment}"). + Doc("Update environment"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("environment", "Environment name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.Environment{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated environment", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/environments/{environment}"). + Doc("Delete environment"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("environment", "Environment name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of environment").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) EnvironmentApiList(w http.ResponseWriter, r *http.Request) { ns := a.extractQueryParamFromRequest(r, "namespace") if len(ns) == 0 { diff --git a/pkg/controller/functionApi.go b/pkg/controller/functionApi.go index b25a8e2e..a886b882 100644 --- a/pkg/controller/functionApi.go +++ b/pkg/controller/functionApi.go @@ -26,6 +26,9 @@ import ( "net/url" "sort" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "github.com/gorilla/mux" "go.uber.org/zap" apiv1 "k8s.io/api/core/v1" @@ -36,6 +39,73 @@ import ( ferror "github.com/fission/fission/pkg/error" ) +func RegisterFunctionRoute(ws *restful.WebService) { + tags := []string{"Function"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "Function", Description: "Function Operation"}}) + + ws.Route( + ws.GET("/v2/functions"). + Doc("List all functions"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of function").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.Function{}). + Returns(http.StatusOK, "List of functions", []fv1.Function{})) + + ws.Route( + ws.POST("/v2/functions"). + Doc("Create function"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.Function{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusOK, "Metadata of created function", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/functions/{function}"). + Doc("Get detail of function"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("function", "Function name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of function").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.Function{}). // on the response + Returns(http.StatusOK, "A function", fv1.Function{})) + + ws.Route( + ws.PUT("/v2/functions/{function}"). + Doc("Update function"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("function", "Function name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.Function{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated function", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/functions/{function}"). + Doc("Delete function"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("function", "Function name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of function").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) getIstioServiceLabels(fnName string) map[string]string { return map[string]string{ "functionName": fnName, diff --git a/pkg/controller/httpTriggerApi.go b/pkg/controller/httpTriggerApi.go index f23856fd..75cbdd18 100644 --- a/pkg/controller/httpTriggerApi.go +++ b/pkg/controller/httpTriggerApi.go @@ -22,6 +22,9 @@ import ( "io/ioutil" "net/http" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "github.com/gorilla/mux" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,6 +32,73 @@ import ( ferror "github.com/fission/fission/pkg/error" ) +func RegisterHTTPTriggerRoute(ws *restful.WebService) { + tags := []string{"HTTPTrigger"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "HTTPTrigger", Description: "HTTPTrigger Operation"}}) + + ws.Route( + ws.GET("/v2/triggers/http"). + Doc("List all HTTP triggers"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of httpTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.HTTPTrigger{}). + Returns(http.StatusOK, "List of httpTriggers", []fv1.HTTPTrigger{})) + + ws.Route( + ws.POST("/v2/triggers/http"). + Doc("Create HTTP trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.HTTPTrigger{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusCreated, "Metadata of created httpTrigger", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/triggers/http/{httpTrigger}"). + Doc("Get detail of HTTP trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("httpTrigger", "HTTPTrigger name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of httpTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.HTTPTrigger{}). // on the response + Returns(http.StatusOK, "A httpTrigger", fv1.HTTPTrigger{})) + + ws.Route( + ws.PUT("/v2/triggers/http/{httpTrigger}"). + Doc("Update HTTP trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("httpTrigger", "HTTPTrigger name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.HTTPTrigger{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated httpTrigger", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/triggers/http/{httpTrigger}"). + Doc("Delete HTTP trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("httpTrigger", "HTTPTrigger name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of httpTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) HTTPTriggerApiList(w http.ResponseWriter, r *http.Request) { ns := a.extractQueryParamFromRequest(r, "namespace") if len(ns) == 0 { diff --git a/pkg/controller/mqTriggerApi.go b/pkg/controller/mqTriggerApi.go index b5941d80..9f43ea35 100644 --- a/pkg/controller/mqTriggerApi.go +++ b/pkg/controller/mqTriggerApi.go @@ -21,6 +21,9 @@ import ( "io/ioutil" "net/http" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "github.com/gorilla/mux" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,6 +31,73 @@ import ( ferror "github.com/fission/fission/pkg/error" ) +func RegisterMessageQueueTriggerRoute(ws *restful.WebService) { + tags := []string{"MessageQueueTrigger"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "MessageQueueTrigger", Description: "MessageQueueTrigger Operation"}}) + + ws.Route( + ws.GET("/v2/triggers/messagequeue"). + Doc("List all message queue triggers"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of messageQueueTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.MessageQueueTrigger{}). + Returns(http.StatusOK, "List of messageQueueTriggers", []fv1.MessageQueueTrigger{})) + + ws.Route( + ws.POST("/v2/triggers/messagequeue"). + Doc("Create message queue trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.MessageQueueTrigger{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusCreated, "Metadata of created messageQueueTrigger", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/triggers/messagequeue/{mqTrigger}"). + Doc("Get detail of message queue trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("mqTrigger", "MessageQueueTriggers name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of messageQueueTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.MessageQueueTrigger{}). // on the response + Returns(http.StatusOK, "A messageQueueTrigger", fv1.MessageQueueTrigger{})) + + ws.Route( + ws.PUT("/v2/triggers/messagequeue/{mqTrigger}"). + Doc("Update message queue trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("mqTrigger", "MessageQueueTrigger name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.MessageQueueTrigger{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated messageQueueTrigger", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/triggers/messagequeue/{mqTrigger}"). + Doc("Delete message queue trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("mqTrigger", "MessageQueueTrigger name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of messageQueueTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) MessageQueueTriggerApiList(w http.ResponseWriter, r *http.Request) { //mqType := r.FormValue("mqtype") // ignored for now ns := a.extractQueryParamFromRequest(r, "namespace") diff --git a/pkg/controller/openapi.go b/pkg/controller/openapi.go new file mode 100644 index 00000000..b6e1d4d7 --- /dev/null +++ b/pkg/controller/openapi.go @@ -0,0 +1,78 @@ +/* +Copyright 2019 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "net/http" + + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" +) + +var specTag []spec.Tag + +func openAPI() http.Handler { + restful.DefaultContainer.Add(openAPIWebService()) + + config := restfulspec.Config{ + WebServices: restful.RegisteredWebServices(), + APIPath: "/v2/apidocs.json", + PostBuildSwaggerObjectHandler: enrichSwaggerObject} + + restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config)) + + return restful.DefaultContainer +} + +func openAPIWebService() *restful.WebService { + ws := new(restful.WebService) + + // CRD resource + RegisterEnvironmentRoute(ws) + RegisterFunctionRoute(ws) + RegisterHTTPTriggerRoute(ws) + RegisterMessageQueueTriggerRoute(ws) + RegisterPackageRoute(ws) + RegisterWatchRoute(ws) + RegisterTimeTriggerRoute(ws) + RegisterCanaryConfigRoute(ws) + + // proxy + RegisterStorageServiceProxyRoute(ws) + + return ws +} + +func enrichSwaggerObject(swo *spec.Swagger) { + swo.Info = &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Fission OpenAPI 2.0", + Description: openapiDescription, + Version: "v1", + }, + } + swo.Tags = specTag +} + +var openapiDescription = ` +OpenAPI 2.0 document for fission controller +* Metadata (v1.ObjectMeta) should be empty when creating a CRD resource. Kubernetes will assign it automatically. +* Following semantic errors are known issues and won't affect the API accessibility. + - Operations must have unique operationIds. + - All scale semantic errors. (Due to go-restful exposes inner fields of k8s struct). +` diff --git a/pkg/controller/packageApi.go b/pkg/controller/packageApi.go index 00420ff3..cea3c84e 100644 --- a/pkg/controller/packageApi.go +++ b/pkg/controller/packageApi.go @@ -23,7 +23,10 @@ import ( "net/http" "github.com/dustin/go-humanize" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" "github.com/fission/fission/pkg/types" + "github.com/go-openapi/spec" "github.com/gorilla/mux" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,6 +34,73 @@ import ( ferror "github.com/fission/fission/pkg/error" ) +func RegisterPackageRoute(ws *restful.WebService) { + tags := []string{"Package"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "Package", Description: "Package Operation"}}) + + ws.Route( + ws.GET("/v2/packages"). + Doc("List all packages"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of package").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.Package{}). + Returns(http.StatusOK, "List of packages", []fv1.Package{})) + + ws.Route( + ws.POST("/v2/packages"). + Doc("Create package"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.Package{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusCreated, "Metadata of created package", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/packages/{package}"). + Doc("Get detail of package"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("package", "Package name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of package").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.Package{}). // on the response + Returns(http.StatusOK, "A package", fv1.Package{})) + + ws.Route( + ws.PUT("/v2/packages/{package}"). + Doc("Update package"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("package", "Package name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.Package{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated package", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/packages/{package}"). + Doc("Delete package"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("package", "Package name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of package").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) PackageApiList(w http.ResponseWriter, r *http.Request) { ns := a.extractQueryParamFromRequest(r, "namespace") if len(ns) == 0 { diff --git a/pkg/controller/storagesvc.go b/pkg/controller/storagesvc.go index 647c8b06..a587fd25 100644 --- a/pkg/controller/storagesvc.go +++ b/pkg/controller/storagesvc.go @@ -22,9 +22,40 @@ import ( "net/http/httputil" "net/url" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "go.uber.org/zap" ) +func RegisterStorageServiceProxyRoute(ws *restful.WebService) { + tags := []string{"StorageServiceProxy"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "StorageServiceProxy", Description: "StorageServiceProxy Operation"}}) + + // workaround as go-restful has to set HTTP method explicitly. + ws.Route( + ws.POST("/proxy/storage/v1/archive"). + Doc("Create archive"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + })) + ws.Route( + ws.GET("/proxy/storage/v1/archive"). + Doc("Get archive"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + })) + ws.Route( + ws.DELETE("/proxy/storage/v1/archive"). + Doc("Delete archive"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + })) +} + func (api *API) StorageServiceProxy(w http.ResponseWriter, r *http.Request) { u := api.storageServiceUrl ssUrl, err := url.Parse(u) diff --git a/pkg/controller/timeTriggerApi.go b/pkg/controller/timeTriggerApi.go index b7e157b8..0df43881 100644 --- a/pkg/controller/timeTriggerApi.go +++ b/pkg/controller/timeTriggerApi.go @@ -21,6 +21,9 @@ import ( "io/ioutil" "net/http" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "github.com/gorilla/mux" "github.com/robfig/cron" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,6 +32,73 @@ import ( ferror "github.com/fission/fission/pkg/error" ) +func RegisterTimeTriggerRoute(ws *restful.WebService) { + tags := []string{"TimeTrigger"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "TimeTrigger", Description: "TimeTrigger Operation"}}) + + ws.Route( + ws.GET("/v2/triggers/time"). + Doc("List all time trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of timeTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.TimeTrigger{}). + Returns(http.StatusOK, "List of timeTriggers", []fv1.TimeTrigger{})) + + ws.Route( + ws.POST("/v2/triggers/time"). + Doc("Create time trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.TimeTrigger{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusCreated, "Metadata of created timeTrigger", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/triggers/time/{timeTrigger}"). + Doc("Get detail of time trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("timeTrigger", "TimeTrigger name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of timeTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.TimeTrigger{}). // on the response + Returns(http.StatusOK, "A timeTrigger", fv1.TimeTrigger{})) + + ws.Route( + ws.PUT("/v2/triggers/time/{timeTrigger}"). + Doc("Update time trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("timeTrigger", "TimeTrigger name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.TimeTrigger{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated timeTrigger", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/triggers/time/{timeTrigger}"). + Doc("Delete time trigger"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("timeTrigger", "TimeTrigger name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of timeTrigger").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) TimeTriggerApiList(w http.ResponseWriter, r *http.Request) { ns := a.extractQueryParamFromRequest(r, "namespace") if len(ns) == 0 { diff --git a/pkg/controller/watchApi.go b/pkg/controller/watchApi.go index 205d5475..94a66cd6 100644 --- a/pkg/controller/watchApi.go +++ b/pkg/controller/watchApi.go @@ -21,6 +21,9 @@ import ( "io/ioutil" "net/http" + "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "github.com/go-openapi/spec" "github.com/gorilla/mux" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,6 +31,73 @@ import ( ferror "github.com/fission/fission/pkg/error" ) +func RegisterWatchRoute(ws *restful.WebService) { + tags := []string{"KubernetesWatch"} + specTag = append(specTag, spec.Tag{TagProps: spec.TagProps{Name: "KubernetesWatch", Description: "KubernetesWatch Operation"}}) + + ws.Route( + ws.GET("/v2/watches"). + Doc("List all kubernetes watch"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.QueryParameter("namespace", "Namespace of kubernetesWatch").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes([]fv1.KubernetesWatchTrigger{}). + Returns(http.StatusOK, "List of kubernetesWatchs", []fv1.KubernetesWatchTrigger{})) + + ws.Route( + ws.POST("/v2/watches"). + Doc("Create kubernetes watch"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Produces(restful.MIME_JSON). + Reads(fv1.KubernetesWatchTrigger{}). + Writes(metav1.ObjectMeta{}). + Returns(http.StatusCreated, "Metadata of created kubernetesWatch", metav1.ObjectMeta{})) + + ws.Route( + ws.GET("/v2/watches/{watch}"). + Doc("Get detail of kubernetes watch"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("watch", "KubernetesWatch name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of kubernetesWatch").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Writes(fv1.KubernetesWatchTrigger{}). // on the response + Returns(http.StatusOK, "A kubernetesWatch", fv1.KubernetesWatchTrigger{})) + + ws.Route( + ws.PUT("/v2/watches/{watch}"). + Doc("Update kubernetes watch"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("watch", "KubernetesWatch name").DataType("string").DefaultValue("").Required(true)). + Produces(restful.MIME_JSON). + Reads(fv1.KubernetesWatchTrigger{}). + Writes(metav1.ObjectMeta{}). // on the response + Returns(http.StatusOK, "Metadata of updated kubernetesWatch", metav1.ObjectMeta{})) + + ws.Route( + ws.DELETE("/v2/watches/{watch}"). + Doc("Delete kubernetes watch"). + Metadata(restfulspec.KeyOpenAPITags, tags). + To(func(req *restful.Request, resp *restful.Response) { + resp.ResponseWriter.WriteHeader(http.StatusOK) + }). + Param(ws.PathParameter("watch", "KubernetesWatch name").DataType("string").DefaultValue("").Required(true)). + Param(ws.QueryParameter("namespace", "Namespace of kubernetesWatch").DataType("string").DefaultValue(metav1.NamespaceAll).Required(false)). + Produces(restful.MIME_JSON). + Returns(http.StatusOK, "Only HTTP status returned", nil)) +} + func (a *API) WatchApiList(w http.ResponseWriter, r *http.Request) { ns := a.extractQueryParamFromRequest(r, "namespace") if len(ns) == 0 { diff --git a/pkg/fission-cli/mqtrigger.go b/pkg/fission-cli/mqtrigger.go index a481a318..8d949d8e 100644 --- a/pkg/fission-cli/mqtrigger.go +++ b/pkg/fission-cli/mqtrigger.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - tttp://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/pkg/fission-cli/recorder.go b/pkg/fission-cli/recorder.go index 3c7ad968..da3200a9 100644 --- a/pkg/fission-cli/recorder.go +++ b/pkg/fission-cli/recorder.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - tttp://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/pkg/fission-cli/records.go b/pkg/fission-cli/records.go index 2fa7bda1..879a31dc 100644 --- a/pkg/fission-cli/records.go +++ b/pkg/fission-cli/records.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - tttp://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/pkg/fission-cli/timetrigger.go b/pkg/fission-cli/timetrigger.go index 3881a527..f58822e5 100644 --- a/pkg/fission-cli/timetrigger.go +++ b/pkg/fission-cli/timetrigger.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - tttp://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,