From 6b76f2c89ab5d4eb9a7840bd165a13427fd6a6a2 Mon Sep 17 00:00:00 2001 From: Edward Wilde Date: Sat, 29 Feb 2020 16:07:18 +0000 Subject: [PATCH] Move to official OpenFaaS proxy & client Signed-off-by: Edward Wilde --- README.md | 10 +- examples/authenticated/README.md | 2 +- examples/authenticated/database.tf | 3 +- examples/authenticated/function.tf | 17 +- examples/authenticated/main.tf | 12 +- examples/authenticated/variables.tf | 12 +- examples/authenticated/versions.tf | 4 + examples/unauthenticated/main.tf | 1 - go.mod | 7 +- go.sum | 14 +- openfaas/config.go | 8 +- openfaas/data_source_openfaas_function.go | 4 +- openfaas/provider.go | 107 ++++- openfaas/resource_openfaas_function.go | 26 +- openfaas/resource_openfaas_function_test.go | 14 +- openfaas/structure.go | 11 +- vendor/github.com/drone/envsubst/.drone.yml | 8 + vendor/github.com/drone/envsubst/.gitignore | 1 + .../faas-cli => drone/envsubst}/LICENSE | 11 +- vendor/github.com/drone/envsubst/README | 33 ++ vendor/github.com/drone/envsubst/eval.go | 19 + vendor/github.com/drone/envsubst/funcs.go | 228 +++++++++++ vendor/github.com/drone/envsubst/go.mod | 3 + vendor/github.com/drone/envsubst/go.sum | 2 + .../github.com/drone/envsubst/parse/node.go | 86 ++++ .../github.com/drone/envsubst/parse/parse.go | 374 ++++++++++++++++++ .../github.com/drone/envsubst/parse/scan.go | 280 +++++++++++++ .../github.com/drone/envsubst/path/match.go | 207 ++++++++++ vendor/github.com/drone/envsubst/template.go | 157 ++++++++ .../openfaas/faas-cli/config/config_file.go | 53 +-- .../openfaas/faas-cli/proxy/auth.go | 48 +++ .../openfaas/faas-cli/proxy/client.go | 114 ++++++ .../faas-cli/proxy/delete.go | 24 +- .../faas-cli/proxy/deploy.go | 70 ++-- .../faas-cli/proxy/describe.go | 37 +- .../openfaas/faas-cli/proxy/function_store.go | 52 +++ .../faas-cli/proxy/invoke.go | 28 +- .../faas-cli/proxy/list.go | 40 +- .../openfaas/faas-cli/proxy/logs.go | 97 +++++ .../openfaas/faas-cli/proxy/namespaces.go | 54 +++ .../openfaas/faas-cli/proxy/proxy.go | 48 +++ .../openfaas/faas-cli/proxy/secret.go | 190 +++++++++ .../openfaas/faas-cli/proxy/utils.go | 51 +++ .../openfaas/faas-cli/proxy/version.go | 53 +++ .../openfaas/faas-cli/schema/describe.go | 19 + .../openfaas/faas-cli/schema/image.go | 84 ++++ .../openfaas/faas-cli/schema/metadata.go | 10 + .../openfaas/faas-cli/schema/secret.go | 13 + .../openfaas/faas-cli/schema/store_item.go | 17 + .../openfaas/faas-cli/stack/schema.go | 46 ++- .../openfaas/faas-cli/stack/stack.go | 71 +++- .../github.com/openfaas/faas-provider/LICENSE | 21 + .../faas-provider/httputil/writers.go | 12 + .../openfaas/faas-provider/logs/handler.go | 144 +++++++ .../openfaas/faas-provider/logs/logs.go | 62 +++ .../openfaas/faas-provider/types/config.go | 87 ++++ .../openfaas/faas-provider/types/model.go | 99 +++++ .../faas-provider/types/read_config.go | 112 ++++++ .../openfaas/faas-provider/types/requests.go | 22 ++ .../viveksyngh/faas-cli/proxy/auth.go | 21 - .../viveksyngh/faas-cli/proxy/proxy.go | 35 -- vendor/modules.txt | 14 +- 62 files changed, 3240 insertions(+), 269 deletions(-) create mode 100644 examples/authenticated/versions.tf create mode 100644 vendor/github.com/drone/envsubst/.drone.yml create mode 100644 vendor/github.com/drone/envsubst/.gitignore rename vendor/github.com/{viveksyngh/faas-cli => drone/envsubst}/LICENSE (88%) create mode 100644 vendor/github.com/drone/envsubst/README create mode 100644 vendor/github.com/drone/envsubst/eval.go create mode 100644 vendor/github.com/drone/envsubst/funcs.go create mode 100644 vendor/github.com/drone/envsubst/go.mod create mode 100644 vendor/github.com/drone/envsubst/go.sum create mode 100644 vendor/github.com/drone/envsubst/parse/node.go create mode 100644 vendor/github.com/drone/envsubst/parse/parse.go create mode 100644 vendor/github.com/drone/envsubst/parse/scan.go create mode 100644 vendor/github.com/drone/envsubst/path/match.go create mode 100644 vendor/github.com/drone/envsubst/template.go create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/auth.go create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/client.go rename vendor/github.com/{viveksyngh => openfaas}/faas-cli/proxy/delete.go (70%) rename vendor/github.com/{viveksyngh => openfaas}/faas-cli/proxy/deploy.go (73%) rename vendor/github.com/{viveksyngh => openfaas}/faas-cli/proxy/describe.go (59%) create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/function_store.go rename vendor/github.com/{viveksyngh => openfaas}/faas-cli/proxy/invoke.go (85%) rename vendor/github.com/{viveksyngh => openfaas}/faas-cli/proxy/list.go (59%) create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/logs.go create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/namespaces.go create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/proxy.go create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/secret.go create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/utils.go create mode 100644 vendor/github.com/openfaas/faas-cli/proxy/version.go create mode 100644 vendor/github.com/openfaas/faas-cli/schema/describe.go create mode 100644 vendor/github.com/openfaas/faas-cli/schema/image.go create mode 100644 vendor/github.com/openfaas/faas-cli/schema/metadata.go create mode 100644 vendor/github.com/openfaas/faas-cli/schema/secret.go create mode 100644 vendor/github.com/openfaas/faas-cli/schema/store_item.go create mode 100644 vendor/github.com/openfaas/faas-provider/LICENSE create mode 100644 vendor/github.com/openfaas/faas-provider/httputil/writers.go create mode 100644 vendor/github.com/openfaas/faas-provider/logs/handler.go create mode 100644 vendor/github.com/openfaas/faas-provider/logs/logs.go create mode 100644 vendor/github.com/openfaas/faas-provider/types/config.go create mode 100644 vendor/github.com/openfaas/faas-provider/types/model.go create mode 100644 vendor/github.com/openfaas/faas-provider/types/read_config.go create mode 100644 vendor/github.com/openfaas/faas-provider/types/requests.go delete mode 100644 vendor/github.com/viveksyngh/faas-cli/proxy/auth.go delete mode 100644 vendor/github.com/viveksyngh/faas-cli/proxy/proxy.go diff --git a/README.md b/README.md index 6819868..852b55f 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ Full documentation, see: https://openfaas-tfe.edwardwilde.com/docs/providers/ope ```hcl resource "openfaas_function" "function_test" { - name = "test-function" - image = "functions/alpine:latest" - f_process = "sha512sum" - labels { + name = "test-function" + image = "functions/alpine:latest" + f_process = "sha512sum" + labels = { Group = "London" Environment = "Test" } - annotations { + annotations = { CreatedDate = "Mon Sep 3 07:15:55 BST 2018" } } diff --git a/examples/authenticated/README.md b/examples/authenticated/README.md index d225eb7..6c94557 100644 --- a/examples/authenticated/README.md +++ b/examples/authenticated/README.md @@ -12,7 +12,7 @@ git clone https://github.com/openfaas/faas && \ ```bash cat <>terraform.tfvars -provider_password = "72b97dd9abe096b91478df91b6549f2998aee27dbdde9a4bbec4182801d6c398" +openfaas_provider_password = "72b97dd9abe096b91478df91b6549f2998aee27dbdde9a4bbec4182801d6c398" EOF ``` 3. `terraform apply` diff --git a/examples/authenticated/database.tf b/examples/authenticated/database.tf index 29c2965..fdb0085 100644 --- a/examples/authenticated/database.tf +++ b/examples/authenticated/database.tf @@ -1,3 +1,4 @@ resource "postgresql_database" "function_db" { name = "function_db" -} \ No newline at end of file +} + diff --git a/examples/authenticated/function.tf b/examples/authenticated/function.tf index e7b2dd5..39efb08 100644 --- a/examples/authenticated/function.tf +++ b/examples/authenticated/function.tf @@ -1,8 +1,8 @@ resource "openfaas_function" "function_test" { - name = "test-function" - image = "functions/alpine:latest" - f_process = "env" - labels { + name = "test-function" + image = "functions/alpine:latest" + f_process = "env" + labels = { Group = "London" Environment = "Test" } @@ -12,11 +12,12 @@ resource "openfaas_function" "function_test" { cpu = "100m" } - env_vars { - database_name = "${postgresql_database.function_db.name}" + env_vars = { + database_name = postgresql_database.function_db.name } - annotations { + annotations = { CreatedDate = "Mon Sep 3 07:15:55 BST 2018" } -} \ No newline at end of file +} + diff --git a/examples/authenticated/main.tf b/examples/authenticated/main.tf index daec750..e8003bd 100644 --- a/examples/authenticated/main.tf +++ b/examples/authenticated/main.tf @@ -1,13 +1,13 @@ provider "openfaas" { - version = "~> 0.1" uri = "http://localhost:8080" user_name = "admin" - password = "${var.openfaas_provider_password}" + password = var.openfaas_provider_password } provider "postgresql" { - host = "localhost" - username = "${var.database_username}" - password = "${var.database_password}" - sslmode = "disable" + host = "localhost" + username = var.database_username + password = var.database_password + sslmode = "disable" } + diff --git a/examples/authenticated/variables.tf b/examples/authenticated/variables.tf index 01bf27a..33fa7d4 100644 --- a/examples/authenticated/variables.tf +++ b/examples/authenticated/variables.tf @@ -1,3 +1,9 @@ -variable "openfaas_provider_password" {} -variable "database_username" {} -variable "database_password" {} \ No newline at end of file +variable "openfaas_provider_password" { +} + +variable "database_username" { +} + +variable "database_password" { +} + diff --git a/examples/authenticated/versions.tf b/examples/authenticated/versions.tf new file mode 100644 index 0000000..ac97c6a --- /dev/null +++ b/examples/authenticated/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/examples/unauthenticated/main.tf b/examples/unauthenticated/main.tf index bfc4493..de04de0 100644 --- a/examples/unauthenticated/main.tf +++ b/examples/unauthenticated/main.tf @@ -1,4 +1,3 @@ provider "openfaas" { - version = "~> 0.1" uri = "http://localhost:8080" } diff --git a/go.mod b/go.mod index 28df6d0..6aa1c52 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,12 @@ module github.com/ewilde/terraform-provider-openfaas go 1.13 require ( + github.com/drone/envsubst v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/terraform-plugin-sdk v1.0.0 github.com/oklog/run v1.1.0 // indirect - github.com/openfaas/faas v0.0.0-20180822191204-91b6c244941c - github.com/openfaas/faas-cli v0.0.0-20180829141537-b7ba9a60dc2c + github.com/openfaas/faas v0.0.0-20180822191204-91b6c244941c // indirect + github.com/openfaas/faas-cli v0.0.0-20200226083118-b0a70a3f4f20 + github.com/openfaas/faas-provider v0.15.0 github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 // indirect - github.com/viveksyngh/faas-cli v0.0.0-20180831214523-1fe7ecc51fb5 ) diff --git a/go.sum b/go.sum index a27fd5e..c802491 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk 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/drone/envsubst v1.0.2 h1:dpYLMAspQHW0a8dZpLRKe9jCNvIGZPhCPrycZzIHdqo= +github.com/drone/envsubst v1.0.2/go.mod h1:bkZbnc/2vh1M12Ecn7EYScpI4YGYU0etwLJICOWi8Z0= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -71,6 +73,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -169,8 +173,10 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openfaas/faas v0.0.0-20180822191204-91b6c244941c h1:+MrLQNzu91zUS7vl8wFeBMkNqZgX+VuS9f01jXgJc78= github.com/openfaas/faas v0.0.0-20180822191204-91b6c244941c/go.mod h1:E0m2rLup0Vvxg53BKxGgaYAGcZa3Xl+vvL7vSi5yQ14= -github.com/openfaas/faas-cli v0.0.0-20180829141537-b7ba9a60dc2c h1:BF+OhyexBSqsYUjZc8YX/kP87X6FGOuTIxaLwgHUIco= -github.com/openfaas/faas-cli v0.0.0-20180829141537-b7ba9a60dc2c/go.mod h1:u/KO+e43wkagC0lqM1eaqNEWEBdg08Q1ugP/idj39MM= +github.com/openfaas/faas-cli v0.0.0-20200226083118-b0a70a3f4f20 h1:v0Yuo8+U2U7vamYrZmXN0MudPdP07fOVJLJ5OXd1q6Q= +github.com/openfaas/faas-cli v0.0.0-20200226083118-b0a70a3f4f20/go.mod h1:u/KO+e43wkagC0lqM1eaqNEWEBdg08Q1ugP/idj39MM= +github.com/openfaas/faas-provider v0.15.0 h1:3x5ma90FL7AqP4NOD6f03AY24y3xBeVF6xGLUx6Xrlc= +github.com/openfaas/faas-provider v0.15.0/go.mod h1:8Fagi2UeMfL+gZAqZWSMQg86i+w1+hBOKtwKRP5sLFI= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -192,8 +198,6 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/viveksyngh/faas-cli v0.0.0-20180831214523-1fe7ecc51fb5 h1:gmJ5Lf4awVArqDtmdJ4UnG+q0yYg9xLBXLTIHIDwj1I= -github.com/viveksyngh/faas-cli v0.0.0-20180831214523-1fe7ecc51fb5/go.mod h1:hjz01B9wMGspSpFAhplBEjwCTaUkkAHVWqxfvb2Z+tU= github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvcciqcxEHac4CYj89xI= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= @@ -204,6 +208,8 @@ github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgK go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4= +go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= diff --git a/openfaas/config.go b/openfaas/config.go index 3ff78fa..3eceba9 100644 --- a/openfaas/config.go +++ b/openfaas/config.go @@ -1,8 +1,8 @@ package openfaas +import "github.com/openfaas/faas-cli/proxy" + type Config struct { - TLSInsecure bool - GatewayURI string - GatewayUserName string - GatewayPassword string + Client *proxy.Client + FunctionNamespace string } diff --git a/openfaas/data_source_openfaas_function.go b/openfaas/data_source_openfaas_function.go index 61675b6..402f553 100644 --- a/openfaas/data_source_openfaas_function.go +++ b/openfaas/data_source_openfaas_function.go @@ -1,11 +1,11 @@ package openfaas import ( + "context" "fmt" "log" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/viveksyngh/faas-cli/proxy" ) func dataSourceOpenFaaSFunction() *schema.Resource { @@ -45,7 +45,7 @@ func dataSourceOpenFaaSFunctionRead(d *schema.ResourceData, meta interface{}) er config := meta.(Config) log.Printf("[DEBUG] Reading function Balancer: %s", name) - function, err := proxy.GetFunctionInfo(config.GatewayURI, name, config.TLSInsecure) + function, err := config.Client.GetFunctionInfo(context.Background(), name, "") if err != nil { return fmt.Errorf("error retrieving function: %s", err) } diff --git a/openfaas/provider.go b/openfaas/provider.go index 68b76b5..19fcbe5 100644 --- a/openfaas/provider.go +++ b/openfaas/provider.go @@ -1,11 +1,20 @@ package openfaas import ( + "crypto/tls" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/terraform" "log" + "net" + "net/http" + "time" - openfaas_config "github.com/openfaas/faas-cli/config" + "github.com/openfaas/faas-cli/config" + "github.com/openfaas/faas-cli/proxy" +) + +var ( + defaultTimeout = 60 * time.Second ) // Provider returns a terraform.ResourceProvider. @@ -54,16 +63,96 @@ func Provider() terraform.ResourceProvider { func providerConfigure(d *schema.ResourceData) (interface{}, error) { log.Printf("[DEBUG] configuring provider") - config := Config{ - GatewayURI: d.Get("uri").(string), - TLSInsecure: d.Get("tls_insecure").(bool), - GatewayUserName: d.Get("user_name").(string), - GatewayPassword: d.Get("password").(string), + + gatewayURI := d.Get("uri").(string) + auth := newCLIAuth("", gatewayURI) + insecure := d.Get("tls_insecure").(bool) + transport := GetDefaultCLITransport(insecure, &defaultTimeout) + client := proxy.NewClient(auth, gatewayURI, transport, &defaultTimeout) + + providerConfig := Config{ + Client: client, + } + + return providerConfig, nil +} + +func newCLIAuth(token string, gateway string) proxy.ClientAuth { + authConfig, _ := config.LookupAuthConfig(gateway) + + var ( + username string + password string + bearerToken string + ) + + if authConfig.Auth == config.BasicAuthType { + username, password, _ = config.DecodeAuth(authConfig.Token) + + return &BasicAuth{ + username: username, + password: password, + } + + } + + // User specified token gets priority + if len(token) > 0 { + bearerToken = token + } else { + bearerToken = authConfig.Token + } + + return &BearerToken{ + token: bearerToken, } +} + +type BasicAuth struct { + username string + password string +} - if config.GatewayUserName != "" && config.GatewayPassword != "" { - openfaas_config.UpdateAuthConfig(config.GatewayURI, config.GatewayUserName, config.GatewayPassword) +func (auth *BasicAuth) Set(req *http.Request) error { + if auth.username == "" { + return nil } - return config, nil + req.SetBasicAuth(auth.username, auth.password) + return nil +} + +type BearerToken struct { + token string +} + +func (c *BearerToken) Set(req *http.Request) error { + req.Header.Set("Authorization", "Bearer "+c.token) + return nil +} + +func GetDefaultCLITransport(tlsInsecure bool, timeout *time.Duration) *http.Transport { + if timeout != nil || tlsInsecure { + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DisableKeepAlives: false, + } + + if timeout != nil { + tr.DialContext = (&net.Dialer{ + Timeout: *timeout, + }).DialContext + + tr.IdleConnTimeout = 120 * time.Millisecond + tr.ExpectContinueTimeout = 1500 * time.Millisecond + } + + if tlsInsecure { + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: tlsInsecure} + } + tr.DisableKeepAlives = false + + return tr + } + return nil } diff --git a/openfaas/resource_openfaas_function.go b/openfaas/resource_openfaas_function.go index d22bf27..e57b136 100644 --- a/openfaas/resource_openfaas_function.go +++ b/openfaas/resource_openfaas_function.go @@ -1,13 +1,13 @@ package openfaas import ( + "context" "fmt" "strings" "strconv" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/viveksyngh/faas-cli/proxy" ) func resourceOpenFaaSFunction() *schema.Resource { @@ -110,10 +110,12 @@ func resourceOpenFaaSFunction() *schema.Resource { func resourceOpenFaaSFunctionCreate(d *schema.ResourceData, meta interface{}) error { name := d.Get("name").(string) - deploySpec := expandDeploymentSpec(d, meta, name) - statusCode, output := proxy.Deploy(deploySpec, false, true) + deploySpec := expandDeploymentSpec(d, name) + config := meta.(Config) + + statusCode := config.Client.DeployFunction(context.Background(), deploySpec) if statusCode >= 300 { - return fmt.Errorf("error deploying function %s status code %d reason %s", name, statusCode, output) + return fmt.Errorf("error deploying function %s status code %d", name, statusCode) } d.SetId(name) @@ -123,7 +125,8 @@ func resourceOpenFaaSFunctionCreate(d *schema.ResourceData, meta interface{}) er func resourceOpenFaaSFunctionRead(d *schema.ResourceData, meta interface{}) error { name := d.Id() config := meta.(Config) - function, err := proxy.GetFunctionInfo(config.GatewayURI, name, config.TLSInsecure) + + function, err := config.Client.GetFunctionInfo(context.Background(), name, "") if err != nil { if isFunctionNotFound(err) { @@ -139,11 +142,11 @@ func resourceOpenFaaSFunctionRead(d *schema.ResourceData, meta interface{}) erro func resourceOpenFaaSFunctionUpdate(d *schema.ResourceData, meta interface{}) error { name := d.Get("name").(string) - deploySpec := expandDeploymentSpec(d, meta, name) - - statusCode, output := proxy.Deploy(deploySpec, true, true) + deploySpec := expandDeploymentSpec(d, name) + config := meta.(Config) + statusCode := config.Client.DeployFunction(context.Background(), deploySpec) if statusCode >= 300 { - return fmt.Errorf("error deploying function %s status code %d reason %s", name, statusCode, output) + return fmt.Errorf("error deploying function %s status code %d", name, statusCode) } return nil @@ -153,12 +156,13 @@ func resourceOpenFaaSFunctionDelete(d *schema.ResourceData, meta interface{}) er name := d.Get("name").(string) config := meta.(Config) - err := proxy.DeleteFunction(config.GatewayURI, name) + err := config.Client.DeleteFunction(context.Background(), name, "") return err } func isFunctionNotFound(err error) bool { - return strings.Contains(err.Error(), "404") + return strings.Contains(err.Error(), "404") || + strings.Contains(err.Error(), "No such function") } var whiteListLabels = map[string]string{ diff --git a/openfaas/resource_openfaas_function_test.go b/openfaas/resource_openfaas_function_test.go index 45aad00..a8530bc 100644 --- a/openfaas/resource_openfaas_function_test.go +++ b/openfaas/resource_openfaas_function_test.go @@ -1,21 +1,21 @@ package openfaas import ( + "context" "errors" "fmt" + "github.com/openfaas/faas-provider/types" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" - "github.com/openfaas/faas/gateway/requests" - "github.com/viveksyngh/faas-cli/proxy" ) // TestAccResourceOpenFaaSFunction_basic requires an anonymous OpenFaaS // deployment running on localhost:8080, with a secret foo. i.e. `faas secret create foo --from-literal baz` func TestAccResourceOpenFaaSFunction_basic(t *testing.T) { - var conf requests.Function + var conf types.FunctionStatus functionName := fmt.Sprintf("testaccopenfaasfunction-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ @@ -58,7 +58,7 @@ func testAccCheckOpenFaaSFunctionDestroy(s *terraform.State) error { continue } - _, err := proxy.GetFunctionInfo(config.GatewayURI, rs.Primary.ID, config.TLSInsecure) + _, err := config.Client.GetFunctionInfo(context.Background(), rs.Primary.ID, "") if err == nil { return fmt.Errorf("function %q still exists", rs.Primary.ID) @@ -68,14 +68,14 @@ func testAccCheckOpenFaaSFunctionDestroy(s *terraform.State) error { if isFunctionNotFound(err) { return nil } else { - return fmt.Errorf("unexpected error checking function destroyed: %s", err) + return fmt.Errorf("unexpected error checking function destroyed: %s", err.Error()) } } return nil } -func testAccCheckOpenFaaSFunctionExists(n string, res *requests.Function) resource.TestCheckFunc { +func testAccCheckOpenFaaSFunctionExists(n string, res *types.FunctionStatus) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -88,7 +88,7 @@ func testAccCheckOpenFaaSFunctionExists(n string, res *requests.Function) resour config := testAccProvider.Meta().(Config) - function, err := proxy.GetFunctionInfo(config.GatewayURI, rs.Primary.ID, config.TLSInsecure) + function, err := config.Client.GetFunctionInfo(context.Background(), rs.Primary.ID, "") if err != nil { return err diff --git a/openfaas/structure.go b/openfaas/structure.go index 5173b08..88ab8f8 100644 --- a/openfaas/structure.go +++ b/openfaas/structure.go @@ -2,16 +2,13 @@ package openfaas import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/openfaas/faas-cli/proxy" "github.com/openfaas/faas-cli/stack" - "github.com/openfaas/faas/gateway/requests" - "github.com/viveksyngh/faas-cli/proxy" + "github.com/openfaas/faas-provider/types" ) -func expandDeploymentSpec(d *schema.ResourceData, meta interface{}, name string) *proxy.DeployFunctionSpec { - config := meta.(Config) - +func expandDeploymentSpec(d *schema.ResourceData, name string) *proxy.DeployFunctionSpec { deploySpec := &proxy.DeployFunctionSpec{ - Gateway: config.GatewayURI, FunctionName: name, Image: d.Get("image").(string), } @@ -107,7 +104,7 @@ func expandStringMap(m map[string]interface{}) map[string]string { return list } -func flattenOpenFaaSFunctionResource(d *schema.ResourceData, function requests.Function) error { +func flattenOpenFaaSFunctionResource(d *schema.ResourceData, function types.FunctionStatus) error { d.Set("name", function.Name) d.Set("image", function.Image) d.Set("f_process", function.EnvProcess) diff --git a/vendor/github.com/drone/envsubst/.drone.yml b/vendor/github.com/drone/envsubst/.drone.yml new file mode 100644 index 0000000..2b06213 --- /dev/null +++ b/vendor/github.com/drone/envsubst/.drone.yml @@ -0,0 +1,8 @@ +kind: pipeline +name: default + +steps: +- name: build + image: golang:1.11 + commands: + - go test -v ./... diff --git a/vendor/github.com/drone/envsubst/.gitignore b/vendor/github.com/drone/envsubst/.gitignore new file mode 100644 index 0000000..2d83068 --- /dev/null +++ b/vendor/github.com/drone/envsubst/.gitignore @@ -0,0 +1 @@ +coverage.out diff --git a/vendor/github.com/viveksyngh/faas-cli/LICENSE b/vendor/github.com/drone/envsubst/LICENSE similarity index 88% rename from vendor/github.com/viveksyngh/faas-cli/LICENSE rename to vendor/github.com/drone/envsubst/LICENSE index e366418..1de55b7 100644 --- a/vendor/github.com/viveksyngh/faas-cli/LICENSE +++ b/vendor/github.com/drone/envsubst/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2017 Alex Ellis +Copyright (c) 2017 drone.io Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,14 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/drone/envsubst/README b/vendor/github.com/drone/envsubst/README new file mode 100644 index 0000000..31c2ac5 --- /dev/null +++ b/vendor/github.com/drone/envsubst/README @@ -0,0 +1,33 @@ +Go package for expanding variables in a string using ${var} syntax. Includes support for bash string replacement functions. + +Documentation: + + http://godoc.org/github.com/drone/envsubst + +Supported Functions: + + ${var^} + ${var^^} + ${var,} + ${var,,} + ${var:position} + ${var:position:length} + ${var#substring} + ${var##substring} + ${var%substring} + ${var%%substring} + ${var/substring/replacement} + ${var//substring/replacement} + ${var/#substring/replacement} + ${var/%substring/replacement} + ${#var} + ${var=default} + ${var:=default} + ${var:-default} + +Unsupported Functions: + + ${var-default} + ${var+default} + ${var:?default} + ${var:+default} diff --git a/vendor/github.com/drone/envsubst/eval.go b/vendor/github.com/drone/envsubst/eval.go new file mode 100644 index 0000000..375ca4c --- /dev/null +++ b/vendor/github.com/drone/envsubst/eval.go @@ -0,0 +1,19 @@ +package envsubst + +import "os" + +// Eval replaces ${var} in the string based on the mapping function. +func Eval(s string, mapping func(string) string) (string, error) { + t, err := Parse(s) + if err != nil { + return s, err + } + return t.Execute(mapping) +} + +// EvalEnv replaces ${var} in the string according to the values of the +// current environment variables. References to undefined variables are +// replaced by the empty string. +func EvalEnv(s string) (string, error) { + return Eval(s, os.Getenv) +} diff --git a/vendor/github.com/drone/envsubst/funcs.go b/vendor/github.com/drone/envsubst/funcs.go new file mode 100644 index 0000000..94a3be1 --- /dev/null +++ b/vendor/github.com/drone/envsubst/funcs.go @@ -0,0 +1,228 @@ +package envsubst + +import ( + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "github.com/drone/envsubst/path" +) + +// defines a parameter substitution function. +type substituteFunc func(string, ...string) string + +// toLen returns the length of string s. +func toLen(s string, args ...string) string { + return strconv.Itoa(len(s)) +} + +// toLower returns a copy of the string s with all characters +// mapped to their lower case. +func toLower(s string, args ...string) string { + return strings.ToLower(s) +} + +// toUpper returns a copy of the string s with all characters +// mapped to their upper case. +func toUpper(s string, args ...string) string { + return strings.ToUpper(s) +} + +// toLowerFirst returns a copy of the string s with the first +// character mapped to its lower case. +func toLowerFirst(s string, args ...string) string { + if s == "" { + return s + } + r, n := utf8.DecodeRuneInString(s) + return string(unicode.ToLower(r)) + s[n:] +} + +// toUpperFirst returns a copy of the string s with the first +// character mapped to its upper case. +func toUpperFirst(s string, args ...string) string { + if s == "" { + return s + } + r, n := utf8.DecodeRuneInString(s) + return string(unicode.ToUpper(r)) + s[n:] +} + +// toDefault returns a copy of the string s if not empty, else +// returns a copy of the first string arugment. +func toDefault(s string, args ...string) string { + if len(s) == 0 && len(args) == 1 { + s = args[0] + } + return s +} + +// toSubstr returns a slice of the string s at the specified +// length and position. +func toSubstr(s string, args ...string) string { + if len(args) == 0 { + return s // should never happen + } + + pos, err := strconv.Atoi(args[0]) + if err != nil { + // bash returns the string if the position + // cannot be parsed. + return s + } + + if len(args) == 1 { + if pos < len(s) { + return s[pos:] + } + // if the position exceeds the length of the + // string an empty string is returned + return "" + } + + length, err := strconv.Atoi(args[1]) + if err != nil { + // bash returns the string if the length + // cannot be parsed. + return s + } + + if pos+length >= len(s) { + // if the position exceeds the length of the + // string just return the rest of it like bash + return s[pos:] + } + + return s[pos : pos+length] +} + +// replaceAll returns a copy of the string s with all instances +// of the substring replaced with the replacement string. +func replaceAll(s string, args ...string) string { + switch len(args) { + case 0: + return s + case 1: + return strings.Replace(s, args[0], "", -1) + default: + return strings.Replace(s, args[0], args[1], -1) + } +} + +// replaceFirst returns a copy of the string s with the first +// instance of the substring replaced with the replacement string. +func replaceFirst(s string, args ...string) string { + switch len(args) { + case 0: + return s + case 1: + return strings.Replace(s, args[0], "", 1) + default: + return strings.Replace(s, args[0], args[1], 1) + } +} + +// replacePrefix returns a copy of the string s with the matching +// prefix replaced with the replacement string. +func replacePrefix(s string, args ...string) string { + if len(args) != 2 { + return s + } + if strings.HasPrefix(s, args[0]) { + return strings.Replace(s, args[0], args[1], 1) + } + return s +} + +// replaceSuffix returns a copy of the string s with the matching +// suffix replaced with the replacement string. +func replaceSuffix(s string, args ...string) string { + if len(args) != 2 { + return s + } + if strings.HasSuffix(s, args[0]) { + s = strings.TrimSuffix(s, args[0]) + s = s + args[1] + } + return s +} + +// TODO + +func trimShortestPrefix(s string, args ...string) string { + if len(args) != 0 { + s = trimShortest(s, args[0]) + } + return s +} + +func trimShortestSuffix(s string, args ...string) string { + if len(args) != 0 { + r := reverse(s) + rarg := reverse(args[0]) + s = reverse(trimShortest(r, rarg)) + } + return s +} + +func trimLongestPrefix(s string, args ...string) string { + if len(args) != 0 { + s = trimLongest(s, args[0]) + } + return s +} + +func trimLongestSuffix(s string, args ...string) string { + if len(args) != 0 { + r := reverse(s) + rarg := reverse(args[0]) + s = reverse(trimLongest(r, rarg)) + } + return s +} + +func trimShortest(s, arg string) string { + var shortestMatch string + for i := 0; i < len(s); i++ { + match, err := path.Match(arg, s[0:len(s)-i]) + + if err != nil { + return s + } + + if match { + shortestMatch = s[0 : len(s)-i] + } + } + + if shortestMatch != "" { + return strings.TrimPrefix(s, shortestMatch) + } + + return s +} + +func trimLongest(s, arg string) string { + for i := 0; i < len(s); i++ { + match, err := path.Match(arg, s[0:len(s)-i]) + + if err != nil { + return s + } + + if match { + return strings.TrimPrefix(s, s[0:len(s)-i]) + } + } + + return s +} + +func reverse(s string) string { + r := []rune(s) + for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { + r[i], r[j] = r[j], r[i] + } + return string(r) +} diff --git a/vendor/github.com/drone/envsubst/go.mod b/vendor/github.com/drone/envsubst/go.mod new file mode 100644 index 0000000..7b2ba94 --- /dev/null +++ b/vendor/github.com/drone/envsubst/go.mod @@ -0,0 +1,3 @@ +module github.com/drone/envsubst + +require github.com/google/go-cmp v0.2.0 diff --git a/vendor/github.com/drone/envsubst/go.sum b/vendor/github.com/drone/envsubst/go.sum new file mode 100644 index 0000000..5f4f636 --- /dev/null +++ b/vendor/github.com/drone/envsubst/go.sum @@ -0,0 +1,2 @@ +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= diff --git a/vendor/github.com/drone/envsubst/parse/node.go b/vendor/github.com/drone/envsubst/parse/node.go new file mode 100644 index 0000000..09787eb --- /dev/null +++ b/vendor/github.com/drone/envsubst/parse/node.go @@ -0,0 +1,86 @@ +package parse + +// Node is an element in the parse tree. +type Node interface { + node() +} + +// empty string node +var empty = new(TextNode) + +// a template is represented by a tree consisting of one +// or more of the following nodes. +type ( + // TextNode represents a string of text. + TextNode struct { + Value string + } + + // FuncNode represents a string function. + FuncNode struct { + Param string + Name string + Args []Node + } + + // ListNode represents a list of nodes. + ListNode struct { + Nodes []Node + } + + // ParamNode struct{ + // Name string + // } + // + // CaseNode struct { + // Name string + // First bool + // } + // + // LowerNode struct { + // Name string + // First bool + // } + // + // SubstrNode struct { + // Name string + // Pos Node + // Len Node + // } + // + // ReplaceNode struct { + // Name string + // Substring Node + // Replacement Node + // } + // + // TrimNode struct{ + // + // } + // + // DefaultNode struct { + // Name string + // Default Node + // } +) + +// newTextNode returns a new TextNode. +func newTextNode(text string) *TextNode { + return &TextNode{Value: text} +} + +// newListNode returns a new ListNode. +func newListNode(nodes ...Node) *ListNode { + return &ListNode{Nodes: nodes} +} + +// newFuncNode returns a new FuncNode. +func newFuncNode(name string) *FuncNode { + return &FuncNode{Param: name} +} + +// node() defines the node in a parse tree + +func (*TextNode) node() {} +func (*ListNode) node() {} +func (*FuncNode) node() {} diff --git a/vendor/github.com/drone/envsubst/parse/parse.go b/vendor/github.com/drone/envsubst/parse/parse.go new file mode 100644 index 0000000..683b44d --- /dev/null +++ b/vendor/github.com/drone/envsubst/parse/parse.go @@ -0,0 +1,374 @@ +package parse + +import "errors" + +// ErrBadSubstitution represents a substitution parsing error. +var ErrBadSubstitution = errors.New("bad substitution") + +// Tree is the representation of a single parsed SQL statement. +type Tree struct { + Root Node + + // Parsing only; cleared after parse. + scanner *scanner +} + +// Parse parses the string and returns a Tree. +func Parse(buf string) (*Tree, error) { + t := new(Tree) + t.scanner = new(scanner) + return t.Parse(buf) +} + +// Parse parses the string buffer to construct an ast +// representation for expansion. +func (t *Tree) Parse(buf string) (tree *Tree, err error) { + t.scanner.init(buf) + t.Root, err = t.parseAny() + return t, err +} + +func (t *Tree) parseAny() (Node, error) { + t.scanner.accept = acceptRune + t.scanner.mode = scanIdent | scanLbrack | scanEscape + + switch t.scanner.scan() { + case tokenIdent: + left := newTextNode( + t.scanner.string(), + ) + right, err := t.parseAny() + switch { + case err != nil: + return nil, err + case right == empty: + return left, nil + } + return newListNode(left, right), nil + case tokenEOF: + return empty, nil + case tokenLbrack: + left, err := t.parseFunc() + if err != nil { + return nil, err + } + + right, err := t.parseAny() + switch { + case err != nil: + return nil, err + case right == empty: + return left, nil + } + return newListNode(left, right), nil + } + + return nil, ErrBadSubstitution +} + +func (t *Tree) parseFunc() (Node, error) { + switch t.scanner.peek() { + case '#': + return t.parseLenFunc() + } + + var name string + t.scanner.accept = acceptIdent + t.scanner.mode = scanIdent + + switch t.scanner.scan() { + case tokenIdent: + name = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + switch t.scanner.peek() { + case ':': + return t.parseDefaultOrSubstr(name) + case '=': + return t.parseDefaultFunc(name) + case ',', '^': + return t.parseCasingFunc(name) + case '/': + return t.parseReplaceFunc(name) + case '#': + return t.parseRemoveFunc(name, acceptHashFunc) + case '%': + return t.parseRemoveFunc(name, acceptPercentFunc) + } + + t.scanner.accept = acceptIdent + t.scanner.mode = scanRbrack + switch t.scanner.scan() { + case tokenRbrack: + return newFuncNode(name), nil + default: + return nil, ErrBadSubstitution + } +} + +// parse a substitution function parameter. +func (t *Tree) parseParam(accept acceptFunc, mode byte) (Node, error) { + t.scanner.accept = accept + t.scanner.mode = mode | scanLbrack + switch t.scanner.scan() { + case tokenLbrack: + return t.parseFunc() + case tokenIdent: + return newTextNode( + t.scanner.string(), + ), nil + default: + return nil, ErrBadSubstitution + } +} + +// parse either a default or substring substitution function. +func (t *Tree) parseDefaultOrSubstr(name string) (Node, error) { + t.scanner.read() + r := t.scanner.peek() + t.scanner.unread() + switch r { + case '=', '-', '?', '+': + return t.parseDefaultFunc(name) + default: + return t.parseSubstrFunc(name) + } +} + +// parses the ${param:offset} string function +// parses the ${param:offset:length} string function +func (t *Tree) parseSubstrFunc(name string) (Node, error) { + node := new(FuncNode) + node.Param = name + + t.scanner.accept = acceptOneColon + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + node.Name = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + // scan arg[1] + { + param, err := t.parseParam(rejectColonClose, scanIdent) + if err != nil { + return nil, err + } + + // param.Value = t.scanner.string() + node.Args = append(node.Args, param) + } + + // expect delimiter or close + t.scanner.accept = acceptColon + t.scanner.mode = scanIdent | scanRbrack + switch t.scanner.scan() { + case tokenRbrack: + return node, nil + case tokenIdent: + // no-op + default: + return nil, ErrBadSubstitution + } + + // scan arg[2] + { + param, err := t.parseParam(acceptNotClosing, scanIdent) + if err != nil { + return nil, err + } + node.Args = append(node.Args, param) + } + + return node, t.consumeRbrack() +} + +// parses the ${param%word} string function +// parses the ${param%%word} string function +// parses the ${param#word} string function +// parses the ${param##word} string function +func (t *Tree) parseRemoveFunc(name string, accept acceptFunc) (Node, error) { + node := new(FuncNode) + node.Param = name + + t.scanner.accept = accept + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + node.Name = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + // scan arg[1] + { + param, err := t.parseParam(acceptNotClosing, scanIdent) + if err != nil { + return nil, err + } + + // param.Value = t.scanner.string() + node.Args = append(node.Args, param) + } + + return node, t.consumeRbrack() +} + +// parses the ${param/pattern/string} string function +// parses the ${param//pattern/string} string function +// parses the ${param/#pattern/string} string function +// parses the ${param/%pattern/string} string function +func (t *Tree) parseReplaceFunc(name string) (Node, error) { + node := new(FuncNode) + node.Param = name + + t.scanner.accept = acceptReplaceFunc + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + node.Name = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + // scan arg[1] + { + param, err := t.parseParam(acceptNotSlash, scanIdent|scanEscape) + if err != nil { + return nil, err + } + node.Args = append(node.Args, param) + } + + // expect delimiter + t.scanner.accept = acceptSlash + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + // no-op + default: + return nil, ErrBadSubstitution + } + + // check for blank string + switch t.scanner.peek() { + case '}': + return node, t.consumeRbrack() + } + + // scan arg[2] + { + param, err := t.parseParam(acceptNotClosing, scanIdent|scanEscape) + if err != nil { + return nil, err + } + node.Args = append(node.Args, param) + } + + return node, t.consumeRbrack() +} + +// parses the ${parameter=word} string function +// parses the ${parameter:=word} string function +// parses the ${parameter:-word} string function +// parses the ${parameter:?word} string function +// parses the ${parameter:+word} string function +func (t *Tree) parseDefaultFunc(name string) (Node, error) { + node := new(FuncNode) + node.Param = name + + t.scanner.accept = acceptDefaultFunc + if t.scanner.peek() == '=' { + t.scanner.accept = acceptOneEqual + } + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + node.Name = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + // scan arg[1] + { + param, err := t.parseParam(acceptNotClosing, scanIdent) + if err != nil { + return nil, err + } + + // param.Value = t.scanner.string() + node.Args = append(node.Args, param) + } + + return node, t.consumeRbrack() +} + +// parses the ${param,} string function +// parses the ${param,,} string function +// parses the ${param^} string function +// parses the ${param^^} string function +func (t *Tree) parseCasingFunc(name string) (Node, error) { + node := new(FuncNode) + node.Param = name + + t.scanner.accept = acceptCasingFunc + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + node.Name = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + return node, t.consumeRbrack() +} + +// parses the ${#param} string function +func (t *Tree) parseLenFunc() (Node, error) { + node := new(FuncNode) + + t.scanner.accept = acceptOneHash + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + node.Name = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + t.scanner.accept = acceptIdent + t.scanner.mode = scanIdent + switch t.scanner.scan() { + case tokenIdent: + node.Param = t.scanner.string() + default: + return nil, ErrBadSubstitution + } + + return node, t.consumeRbrack() +} + +// consumeRbrack consumes a right closing bracket. If a closing +// bracket token is not consumed an ErrBadSubstitution is returned. +func (t *Tree) consumeRbrack() error { + t.scanner.mode = scanRbrack + if t.scanner.scan() != tokenRbrack { + return ErrBadSubstitution + } + return nil +} + +// consumeDelimiter consumes a function argument delimiter. If a +// delimiter is not consumed an ErrBadSubstitution is returned. +// func (t *Tree) consumeDelimiter(accept acceptFunc, mode uint) error { +// t.scanner.accept = accept +// t.scanner.mode = mode +// if t.scanner.scan() != tokenRbrack { +// return ErrBadSubstitution +// } +// return nil +// } diff --git a/vendor/github.com/drone/envsubst/parse/scan.go b/vendor/github.com/drone/envsubst/parse/scan.go new file mode 100644 index 0000000..94812c1 --- /dev/null +++ b/vendor/github.com/drone/envsubst/parse/scan.go @@ -0,0 +1,280 @@ +package parse + +import ( + "unicode" + "unicode/utf8" +) + +// eof rune sent when end of file is reached +var eof = rune(0) + +// token is a lexical token. +type token uint + +// list of lexical tokens. +const ( + // special tokens + tokenIllegal token = iota + tokenEOF + + // identifiers and literals + tokenIdent + + // operators and delimiters + tokenLbrack + tokenRbrack + tokenQuote +) + +// predefined mode bits to control recognition of tokens. +const ( + scanIdent byte = 1 << iota + scanLbrack + scanRbrack + scanEscape +) + +// returns true if rune is accepted. +type acceptFunc func(r rune, i int) bool + +// scanner implements a lexical scanner that reads unicode +// characters and tokens from a string buffer. +type scanner struct { + buf string + pos int + start int + width int + mode byte + + accept acceptFunc +} + +// init initializes a scanner with a new buffer. +func (s *scanner) init(buf string) { + s.buf = buf + s.pos = 0 + s.start = 0 + s.width = 0 + s.accept = nil +} + +// read returns the next unicode character. It returns eof at +// the end of the string buffer. +func (s *scanner) read() rune { + if s.pos >= len(s.buf) { + s.width = 0 + return eof + } + r, w := utf8.DecodeRuneInString(s.buf[s.pos:]) + s.width = w + s.pos += s.width + return r +} + +func (s *scanner) unread() { + s.pos -= s.width +} + +// skip skips over the curring unicode character in the buffer +// by slicing and removing from the buffer. +func (s *scanner) skip() { + l := s.buf[:s.pos-1] + r := s.buf[s.pos:] + s.buf = l + r +} + +// peek returns the next unicode character in the buffer without +// advancing the scanner. It returns eof if the scanner's position +// is at the last character of the source. +func (s *scanner) peek() rune { + r := s.read() + s.unread() + return r +} + +// string returns the string corresponding to the most recently +// scanned token. Valid after calling scan(). +func (s *scanner) string() string { + return s.buf[s.start:s.pos] +} + +// scan reads the next token or Unicode character from source and +// returns it. It returns EOF at the end of the source. +func (s *scanner) scan() token { + s.start = s.pos + r := s.read() + switch { + case r == eof: + return tokenEOF + case s.scanLbrack(r): + return tokenLbrack + case s.scanRbrack(r): + return tokenRbrack + case s.scanIdent(r): + return tokenIdent + } + return tokenIllegal +} + +// scanIdent reads the next token or Unicode character from source +// and returns true if the Ident character is accepted. +func (s *scanner) scanIdent(r rune) bool { + if s.mode&scanIdent == 0 { + return false + } + if s.scanEscaped(r) { + s.skip() + } else if !s.accept(r, s.pos-s.start) { + return false + } +loop: + for { + r := s.read() + switch { + case r == eof: + s.unread() + break loop + case s.scanLbrack(r): + s.unread() + s.unread() + break loop + } + if s.scanEscaped(r) { + s.skip() + continue + } + if !s.accept(r, s.pos-s.start) { + s.unread() + break loop + } + } + return true +} + +// scanLbrack reads the next token or Unicode character from source +// and returns true if the open bracket is encountered. +func (s *scanner) scanLbrack(r rune) bool { + if s.mode&scanLbrack == 0 { + return false + } + if r == '$' { + if s.read() == '{' { + return true + } + s.unread() + } + return false +} + +// scanRbrack reads the next token or Unicode character from source +// and returns true if the closing bracket is encountered. +func (s *scanner) scanRbrack(r rune) bool { + if s.mode&scanRbrack == 0 { + return false + } + return r == '}' +} + +// scanEscaped reads the next token or Unicode character from source +// and returns true if it being escaped and should be sipped. +func (s *scanner) scanEscaped(r rune) bool { + if s.mode&scanEscape == 0 { + return false + } + if r == '$' { + if s.peek() == '$' { + return true + } + } + if r != '\\' { + return false + } + switch s.peek() { + case '/', '\\': + return true + default: + return false + } +} + +// +// scanner functions accept or reject runes. +// + +func acceptRune(r rune, i int) bool { + return true +} + +func acceptIdent(r rune, i int) bool { + return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' +} + +func acceptColon(r rune, i int) bool { + return r == ':' +} + +func acceptOneHash(r rune, i int) bool { + return r == '#' && i == 1 +} + +func acceptNone(r rune, i int) bool { + return false +} + +func acceptNotClosing(r rune, i int) bool { + return r != '}' +} + +func acceptHashFunc(r rune, i int) bool { + return r == '#' && i < 3 +} + +func acceptPercentFunc(r rune, i int) bool { + return r == '%' && i < 3 +} + +func acceptDefaultFunc(r rune, i int) bool { + switch { + case i == 1 && r == ':': + return true + case i == 2 && (r == '=' || r == '-' || r == '?' || r == '+'): + return true + default: + return false + } +} + +func acceptReplaceFunc(r rune, i int) bool { + switch { + case i == 1 && r == '/': + return true + case i == 2 && (r == '/' || r == '#' || r == '%'): + return true + default: + return false + } +} + +func acceptOneEqual(r rune, i int) bool { + return i == 1 && r == '=' +} + +func acceptOneColon(r rune, i int) bool { + return i == 1 && r == ':' +} + +func rejectColonClose(r rune, i int) bool { + return r != ':' && r != '}' +} + +func acceptSlash(r rune, i int) bool { + return r == '/' +} + +func acceptNotSlash(r rune, i int) bool { + return r != '/' +} + +func acceptCasingFunc(r rune, i int) bool { + return (r == ',' || r == '^') && i < 3 +} diff --git a/vendor/github.com/drone/envsubst/path/match.go b/vendor/github.com/drone/envsubst/path/match.go new file mode 100644 index 0000000..9306b0c --- /dev/null +++ b/vendor/github.com/drone/envsubst/path/match.go @@ -0,0 +1,207 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package path + +import ( + "errors" + "unicode/utf8" +) + +// ErrBadPattern indicates a globbing pattern was malformed. +var ErrBadPattern = errors.New("syntax error in pattern") + +// Match reports whether name matches the shell file name pattern. +// The pattern syntax is: +// +// pattern: +// { term } +// term: +// '*' matches any sequence of non-/ characters +// '?' matches any single non-/ character +// '[' [ '^' ] { character-range } ']' +// character class (must be non-empty) +// c matches character c (c != '*', '?', '\\', '[') +// '\\' c matches character c +// +// character-range: +// c matches character c (c != '\\', '-', ']') +// '\\' c matches character c +// lo '-' hi matches character c for lo <= c <= hi +// +// Match requires pattern to match all of name, not just a substring. +// The only possible returned error is ErrBadPattern, when pattern +// is malformed. +// +func Match(pattern, name string) (matched bool, err error) { +Pattern: + for len(pattern) > 0 { + var star bool + var chunk string + star, chunk, pattern = scanChunk(pattern) + if star && chunk == "" { + // Trailing * matches rest of string unless it has a /. + // return !strings.Contains(name, "/"), nil + + // Return rest of string + return true, nil + } + // Look for match at current position. + t, ok, err := matchChunk(chunk, name) + // if we're the last chunk, make sure we've exhausted the name + // otherwise we'll give a false result even if we could still match + // using the star + if ok && (len(t) == 0 || len(pattern) > 0) { + name = t + continue + } + if err != nil { + return false, err + } + if star { + // Look for match skipping i+1 bytes. + for i := 0; i < len(name); i++ { + t, ok, err := matchChunk(chunk, name[i+1:]) + if ok { + // if we're the last chunk, make sure we exhausted the name + if len(pattern) == 0 && len(t) > 0 { + continue + } + name = t + continue Pattern + } + if err != nil { + return false, err + } + } + } + return false, nil + } + return len(name) == 0, nil +} + +// scanChunk gets the next segment of pattern, which is a non-star string +// possibly preceded by a star. +func scanChunk(pattern string) (star bool, chunk, rest string) { + for len(pattern) > 0 && pattern[0] == '*' { + pattern = pattern[1:] + star = true + } + inrange := false + var i int +Scan: + for i = 0; i < len(pattern); i++ { + switch pattern[i] { + case '\\': + // error check handled in matchChunk: bad pattern. + if i+1 < len(pattern) { + i++ + } + case '[': + inrange = true + case ']': + inrange = false + case '*': + if !inrange { + break Scan + } + } + } + return star, pattern[0:i], pattern[i:] +} + +// matchChunk checks whether chunk matches the beginning of s. +// If so, it returns the remainder of s (after the match). +// Chunk is all single-character operators: literals, char classes, and ?. +func matchChunk(chunk, s string) (rest string, ok bool, err error) { + for len(chunk) > 0 { + if len(s) == 0 { + return + } + switch chunk[0] { + case '[': + // character class + r, n := utf8.DecodeRuneInString(s) + s = s[n:] + chunk = chunk[1:] + // possibly negated + notNegated := true + if len(chunk) > 0 && chunk[0] == '^' { + notNegated = false + chunk = chunk[1:] + } + // parse all ranges + match := false + nrange := 0 + for { + if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { + chunk = chunk[1:] + break + } + var lo, hi rune + if lo, chunk, err = getEsc(chunk); err != nil { + return + } + hi = lo + if chunk[0] == '-' { + if hi, chunk, err = getEsc(chunk[1:]); err != nil { + return + } + } + if lo <= r && r <= hi { + match = true + } + nrange++ + } + if match != notNegated { + return + } + + case '?': + _, n := utf8.DecodeRuneInString(s) + s = s[n:] + chunk = chunk[1:] + + case '\\': + chunk = chunk[1:] + if len(chunk) == 0 { + err = ErrBadPattern + return + } + fallthrough + + default: + if chunk[0] != s[0] { + return + } + s = s[1:] + chunk = chunk[1:] + } + } + return s, true, nil +} + +// getEsc gets a possibly-escaped character from chunk, for a character class. +func getEsc(chunk string) (r rune, nchunk string, err error) { + if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { + err = ErrBadPattern + return + } + if chunk[0] == '\\' { + chunk = chunk[1:] + if len(chunk) == 0 { + err = ErrBadPattern + return + } + } + r, n := utf8.DecodeRuneInString(chunk) + if r == utf8.RuneError && n == 1 { + err = ErrBadPattern + } + nchunk = chunk[n:] + if len(nchunk) == 0 { + err = ErrBadPattern + } + return +} diff --git a/vendor/github.com/drone/envsubst/template.go b/vendor/github.com/drone/envsubst/template.go new file mode 100644 index 0000000..b725d4f --- /dev/null +++ b/vendor/github.com/drone/envsubst/template.go @@ -0,0 +1,157 @@ +package envsubst + +import ( + "bytes" + "io" + "io/ioutil" + + "github.com/drone/envsubst/parse" +) + +// state represents the state of template execution. It is not part of the +// template so that multiple executions can run in parallel. +type state struct { + template *Template + writer io.Writer + node parse.Node // current node + + // maps variable names to values + mapper func(string) string +} + +// Template is the representation of a parsed shell format string. +type Template struct { + tree *parse.Tree +} + +// Parse creates a new shell format template and parses the template +// definition from string s. +func Parse(s string) (t *Template, err error) { + t = new(Template) + t.tree, err = parse.Parse(s) + if err != nil { + return nil, err + } + return t, nil +} + +// ParseFile creates a new shell format template and parses the template +// definition from the named file. +func ParseFile(path string) (*Template, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return Parse(string(b)) +} + +// Execute applies a parsed template to the specified data mapping. +func (t *Template) Execute(mapping func(string) string) (str string, err error) { + b := new(bytes.Buffer) + s := new(state) + s.node = t.tree.Root + s.mapper = mapping + s.writer = b + err = t.eval(s) + if err != nil { + return + } + return b.String(), nil +} + +func (t *Template) eval(s *state) (err error) { + switch node := s.node.(type) { + case *parse.TextNode: + err = t.evalText(s, node) + case *parse.FuncNode: + err = t.evalFunc(s, node) + case *parse.ListNode: + err = t.evalList(s, node) + } + return err +} + +func (t *Template) evalText(s *state, node *parse.TextNode) error { + _, err := io.WriteString(s.writer, node.Value) + return err +} + +func (t *Template) evalList(s *state, node *parse.ListNode) (err error) { + for _, n := range node.Nodes { + s.node = n + err = t.eval(s) + if err != nil { + return err + } + } + return nil +} + +func (t *Template) evalFunc(s *state, node *parse.FuncNode) error { + var w = s.writer + var buf bytes.Buffer + var args []string + for _, n := range node.Args { + buf.Reset() + s.writer = &buf + s.node = n + err := t.eval(s) + if err != nil { + return err + } + args = append(args, buf.String()) + } + + // restore the origin writer + s.writer = w + s.node = node + + v := s.mapper(node.Param) + + fn := lookupFunc(node.Name, len(args)) + + _, err := io.WriteString(s.writer, fn(v, args...)) + return err +} + +// lookupFunc returns the parameters substitution function by name. If the +// named function does not exists, a default function is returned. +func lookupFunc(name string, args int) substituteFunc { + switch name { + case ",": + return toLowerFirst + case ",,": + return toLower + case "^": + return toUpperFirst + case "^^": + return toUpper + case "#": + if args == 0 { + return toLen + } + return trimShortestPrefix + case "##": + return trimLongestPrefix + case "%": + return trimShortestSuffix + case "%%": + return trimLongestSuffix + case ":": + return toSubstr + case "/#": + return replacePrefix + case "/%": + return replaceSuffix + case "/": + return replaceFirst + case "//": + return replaceAll + case "=", ":=", ":-": + return toDefault + case ":?", ":+", "-", "+": + return toDefault + default: + return toDefault + } +} diff --git a/vendor/github.com/openfaas/faas-cli/config/config_file.go b/vendor/github.com/openfaas/faas-cli/config/config_file.go index 5c9be0f..1584662 100644 --- a/vendor/github.com/openfaas/faas-cli/config/config_file.go +++ b/vendor/github.com/openfaas/faas-cli/config/config_file.go @@ -1,4 +1,4 @@ -// Copyright (c) OpenFaaS Project 2017. All rights reserved. +// Copyright (c) OpenFaaS Author(s) 2017. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. package config @@ -23,6 +23,16 @@ var ( DefaultFile = "config.yml" ) +//AuthType auth type +type AuthType string + +const ( + //BasicAuthType basic authentication type + BasicAuthType = "basic" + //Oauth2AuthType oauth2 authentication type + Oauth2AuthType = "oauth2" +) + // ConfigFile for OpenFaaS CLI exclusively. type ConfigFile struct { AuthConfigs []AuthConfig `yaml:"auths"` @@ -30,9 +40,9 @@ type ConfigFile struct { } type AuthConfig struct { - Gateway string `yaml:"gateway,omitempty"` - Auth string `yaml:"auth,omitempty"` - Token string `yaml:"token,omitempty"` + Gateway string `yaml:"gateway,omitempty"` + Auth AuthType `yaml:"auth,omitempty"` + Token string `yaml:"token,omitempty"` } // New initializes a config file for the given file path @@ -149,20 +159,12 @@ func DecodeAuth(input string) (string, string, error) { } // UpdateAuthConfig creates or updates the username and password for a given gateway -func UpdateAuthConfig(gateway string, username string, password string) error { +func UpdateAuthConfig(gateway, token string, authType AuthType) error { _, err := url.ParseRequestURI(gateway) if err != nil || len(gateway) < 1 { return fmt.Errorf("invalid gateway URL") } - if len(username) < 1 { - return fmt.Errorf("username can't be an empty string") - } - - if len(password) < 1 { - return fmt.Errorf("password can't be an empty string") - } - configPath, err := EnsureFile() if err != nil { return err @@ -179,8 +181,8 @@ func UpdateAuthConfig(gateway string, username string, password string) error { auth := AuthConfig{ Gateway: gateway, - Auth: "basic", - Token: EncodeAuth(username, password), + Auth: authType, + Token: token, } index := -1 @@ -205,36 +207,35 @@ func UpdateAuthConfig(gateway string, username string, password string) error { } // LookupAuthConfig returns the username and password for a given gateway -func LookupAuthConfig(gateway string) (string, string, error) { +func LookupAuthConfig(gateway string) (AuthConfig, error) { + var authConfig AuthConfig + if !fileExists() { - return "", "", fmt.Errorf("config file not found") + return authConfig, fmt.Errorf("config file not found") } configPath, err := EnsureFile() if err != nil { - return "", "", err + return authConfig, err } cfg, err := New(configPath) if err != nil { - return "", "", err + return authConfig, err } if err := cfg.load(); err != nil { - return "", "", err + return authConfig, err } for _, v := range cfg.AuthConfigs { if gateway == v.Gateway { - user, pass, err := DecodeAuth(v.Token) - if err != nil { - return "", "", err - } - return user, pass, nil + authConfig = v + return authConfig, nil } } - return "", "", fmt.Errorf("no auth config found for %s", gateway) + return authConfig, fmt.Errorf("no auth config found for %s", gateway) } // RemoveAuthConfig deletes the username and password for a given gateway diff --git a/vendor/github.com/openfaas/faas-cli/proxy/auth.go b/vendor/github.com/openfaas/faas-cli/proxy/auth.go new file mode 100644 index 0000000..6bf1986 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/auth.go @@ -0,0 +1,48 @@ +// Copyright (c) OpenFaaS Author(s) 2017. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package proxy + +import ( + "net/http" + + "github.com/openfaas/faas-cli/config" +) + +//SetAuth sets basic auth for the given gateway +func SetAuth(req *http.Request, gateway string) { + authConfig, err := config.LookupAuthConfig(gateway) + if err != nil { + // no auth info found + return + } + + switch authConfig.Auth { + case config.BasicAuthType: + SetBasicAuth(req, authConfig) + return + case config.Oauth2AuthType: + SetOauth2(req, authConfig) + return + } +} + +//SetToken sets authentication token +func SetToken(req *http.Request, token string) { + req.Header.Set("Authorization", "Bearer "+token) +} + +//SetBasicAuth set basic authentication +func SetBasicAuth(req *http.Request, authConfig config.AuthConfig) { + username, password, err := config.DecodeAuth(authConfig.Token) + if err != nil { + // no auth info found + return + } + req.SetBasicAuth(username, password) +} + +//SetOauth2 set oauth2 token +func SetOauth2(req *http.Request, authConfig config.AuthConfig) { + SetToken(req, authConfig.Token) +} diff --git a/vendor/github.com/openfaas/faas-cli/proxy/client.go b/vendor/github.com/openfaas/faas-cli/proxy/client.go new file mode 100644 index 0000000..c8c8b00 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/client.go @@ -0,0 +1,114 @@ +package proxy + +import ( + "context" + "io" + "log" + "net/http" + "net/url" + "strings" + "time" +) + +//Client an API client to perform all operations +type Client struct { + httpClient *http.Client + //ClientAuth a type implementing ClientAuth interface for client authentication + ClientAuth ClientAuth + //GatewayURL base URL of OpenFaaS gateway + GatewayURL *url.URL + //UserAgent user agent for the client + UserAgent string +} + +//ClientAuth an interface for client authentication. +// to add authentication to the client implement this interface +type ClientAuth interface { + Set(req *http.Request) error +} + +//NewClient initializes a new API client +func NewClient(auth ClientAuth, gatewayURL string, transport http.RoundTripper, timeout *time.Duration) *Client { + gatewayURL = strings.TrimRight(gatewayURL, "/") + baseURL, err := url.Parse(gatewayURL) + if err != nil { + log.Fatalf("invalid gateway URL: %s", gatewayURL) + } + + client := &http.Client{} + if timeout != nil { + client.Timeout = *timeout + } + + if transport != nil { + client.Transport = transport + } + + return &Client{ + ClientAuth: auth, + httpClient: client, + GatewayURL: baseURL, + } +} + +//newRequest create a new HTTP request with authentication +func (c *Client) newRequest(method, path string, body io.Reader) (*http.Request, error) { + u, err := url.Parse(path) + if err != nil { + return nil, err + } + rel := &url.URL{Path: u.Path, RawQuery: u.RawQuery} + url := c.GatewayURL.ResolveReference(rel) + + req, err := http.NewRequest(method, url.String(), body) + if err != nil { + return nil, err + } + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + if c.UserAgent != "" { + req.Header.Set("User-Agent", c.UserAgent) + } + + c.ClientAuth.Set(req) + + return req, err +} + +//doRequest perform an HTTP request with context +func (c *Client) doRequest(ctx context.Context, req *http.Request) (*http.Response, error) { + req = req.WithContext(ctx) + resp, err := c.httpClient.Do(req) + + if err != nil { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + } + + return resp, err +} + +func addQueryParams(u string, params map[string]string) (string, error) { + parsedURL, err := url.Parse(u) + if err != nil { + return u, err + } + + qs := parsedURL.Query() + for key, value := range params { + qs.Add(key, value) + } + parsedURL.RawQuery = qs.Encode() + return parsedURL.String(), nil +} + +//AddCheckRedirect add CheckRedirect to the client +func (c *Client) AddCheckRedirect(checkRedirect func(*http.Request, []*http.Request) error) { + c.httpClient.CheckRedirect = checkRedirect +} diff --git a/vendor/github.com/viveksyngh/faas-cli/proxy/delete.go b/vendor/github.com/openfaas/faas-cli/proxy/delete.go similarity index 70% rename from vendor/github.com/viveksyngh/faas-cli/proxy/delete.go rename to vendor/github.com/openfaas/faas-cli/proxy/delete.go index 66fcc09..617e8d8 100644 --- a/vendor/github.com/viveksyngh/faas-cli/proxy/delete.go +++ b/vendor/github.com/openfaas/faas-cli/proxy/delete.go @@ -5,34 +5,38 @@ package proxy import ( "bytes" + "context" "encoding/json" "fmt" "io/ioutil" "net/http" - "strings" "github.com/openfaas/faas/gateway/requests" ) -// DeleteFunction delete a function from the FaaS server -func DeleteFunction(gateway string, functionName string) error { - gateway = strings.TrimRight(gateway, "/") +// DeleteFunction delete a function from the OpenFaaS server +func (c *Client) DeleteFunction(ctx context.Context, functionName string, namespace string) error { + var err error delReq := requests.DeleteFunctionRequest{FunctionName: functionName} reqBytes, _ := json.Marshal(&delReq) reader := bytes.NewReader(reqBytes) + deleteEndpoint := "/system/functions" + if len(namespace) > 0 { + deleteEndpoint, err = addQueryParams(deleteEndpoint, map[string]string{namespaceKey: namespace}) + if err != nil { + return err + } + } - c := http.Client{} - req, err := http.NewRequest("DELETE", gateway+"/system/functions", reader) + req, err := c.newRequest(http.MethodDelete, deleteEndpoint, reader) if err != nil { fmt.Println(err) return err } - req.Header.Set("Content-Type", "application/json") - SetAuth(req, gateway) - delRes, delErr := c.Do(req) + delRes, delErr := c.doRequest(ctx, req) if delErr != nil { - fmt.Printf("Error removing existing function: %s, gateway=%s, functionName=%s\n", delErr.Error(), gateway, functionName) + fmt.Printf("Error removing existing function: %s, gateway=%s, functionName=%s\n", delErr.Error(), c.GatewayURL.String(), functionName) return delErr } diff --git a/vendor/github.com/viveksyngh/faas-cli/proxy/deploy.go b/vendor/github.com/openfaas/faas-cli/proxy/deploy.go similarity index 73% rename from vendor/github.com/viveksyngh/faas-cli/proxy/deploy.go rename to vendor/github.com/openfaas/faas-cli/proxy/deploy.go index bfdc1a0..7e2a3ff 100644 --- a/vendor/github.com/viveksyngh/faas-cli/proxy/deploy.go +++ b/vendor/github.com/openfaas/faas-cli/proxy/deploy.go @@ -5,15 +5,20 @@ package proxy import ( "bytes" + "context" "encoding/json" "fmt" "io/ioutil" "net/http" - "strings" "time" "github.com/openfaas/faas-cli/stack" - "github.com/openfaas/faas/gateway/requests" + + types "github.com/openfaas/faas-provider/types" +) + +var ( + defaultCommandTimeout = 60 * time.Second ) // FunctionResourceRequest defines a request to set function resources @@ -25,7 +30,6 @@ type FunctionResourceRequest struct { // DeployFunctionSpec defines the spec used when deploying a function type DeployFunctionSpec struct { FProcess string - Gateway string FunctionName string Image string RegistryAuth string @@ -41,21 +45,29 @@ type DeployFunctionSpec struct { FunctionResourceRequest FunctionResourceRequest ReadOnlyRootFilesystem bool TLSInsecure bool + Token string + Namespace string +} + +func generateFuncStr(spec *DeployFunctionSpec) string { + + if len(spec.Namespace) > 0 { + return fmt.Sprintf("%s.%s", spec.FunctionName, spec.Namespace) + } + return spec.FunctionName } // DeployFunction first tries to deploy a function and if it exists will then attempt // a rolling update. Warnings are suppressed for the second API call (if required.) -func DeployFunction(spec *DeployFunctionSpec) int { +func (c *Client) DeployFunction(context context.Context, spec *DeployFunctionSpec) int { rollingUpdateInfo := fmt.Sprintf("Function %s already exists, attempting rolling-update.", spec.FunctionName) - warnInsecureGateway := true - statusCode, deployOutput := Deploy(spec, spec.Update, warnInsecureGateway) + statusCode, deployOutput := c.deploy(context, spec, spec.Update) - warnInsecureGateway = false if spec.Update == true && statusCode == http.StatusNotFound { // Re-run the function with update=false - statusCode, deployOutput = Deploy(spec, false, warnInsecureGateway) + statusCode, deployOutput = c.deploy(context, spec, false) } else if statusCode == http.StatusOK { fmt.Println(rollingUpdateInfo) } @@ -64,8 +76,8 @@ func DeployFunction(spec *DeployFunctionSpec) int { return statusCode } -// Deploy a function to an OpenFaaS gateway over REST -func Deploy(spec *DeployFunctionSpec, update bool, warnInsecureGateway bool) (int, string) { +// deploy a function to an OpenFaaS gateway over REST +func (c *Client) deploy(context context.Context, spec *DeployFunctionSpec, update bool) (int, string) { var deployOutput string // Need to alter Gateway to allow nil/empty string as fprocess, to avoid this repetition. @@ -74,19 +86,11 @@ func Deploy(spec *DeployFunctionSpec, update bool, warnInsecureGateway bool) (in fprocessTemplate = spec.FProcess } - if warnInsecureGateway { - if (spec.RegistryAuth != "") && !strings.HasPrefix(spec.Gateway, "https") { - fmt.Println("WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.") - } - } - - gateway := strings.TrimRight(spec.Gateway, "/") - if spec.Replace { - DeleteFunction(gateway, spec.FunctionName) + c.DeleteFunction(context, spec.FunctionName, spec.Namespace) } - req := requests.CreateFunctionRequest{ + req := types.FunctionDeployment{ EnvProcess: fprocessTemplate, Image: spec.Image, RegistryAuth: spec.RegistryAuth, @@ -98,10 +102,11 @@ func Deploy(spec *DeployFunctionSpec, update bool, warnInsecureGateway bool) (in Labels: &spec.Labels, Annotations: &spec.Annotations, ReadOnlyRootFilesystem: spec.ReadOnlyRootFilesystem, + Namespace: spec.Namespace, } hasLimits := false - req.Limits = &requests.FunctionResources{} + req.Limits = &types.FunctionResources{} if spec.FunctionResourceRequest.Limits != nil && len(spec.FunctionResourceRequest.Limits.Memory) > 0 { hasLimits = true req.Limits.Memory = spec.FunctionResourceRequest.Limits.Memory @@ -115,7 +120,7 @@ func Deploy(spec *DeployFunctionSpec, update bool, warnInsecureGateway bool) (in } hasRequests := false - req.Requests = &requests.FunctionResources{} + req.Requests = &types.FunctionResources{} if spec.FunctionResourceRequest.Requests != nil && len(spec.FunctionResourceRequest.Requests.Memory) > 0 { hasRequests = true req.Requests.Memory = spec.FunctionResourceRequest.Requests.Memory @@ -133,9 +138,6 @@ func Deploy(spec *DeployFunctionSpec, update bool, warnInsecureGateway bool) (in reader := bytes.NewReader(reqBytes) var request *http.Request - timeout := 60 * time.Second - client := MakeHTTPClient(&timeout, spec.TLSInsecure) - method := http.MethodPost // "application/json" if update { @@ -143,16 +145,17 @@ func Deploy(spec *DeployFunctionSpec, update bool, warnInsecureGateway bool) (in } var err error - request, err = http.NewRequest(method, gateway+"/system/functions", reader) - SetAuth(request, gateway) + request, err = c.newRequest(method, "/system/functions", reader) + if err != nil { deployOutput += fmt.Sprintln(err) return http.StatusInternalServerError, deployOutput } - res, err := client.Do(request) + res, err := c.doRequest(context, request) + if err != nil { - deployOutput += fmt.Sprintln("Is FaaS deployed? Do you need to specify the --gateway flag?") + deployOutput += fmt.Sprintln("Is OpenFaaS deployed? Do you need to specify the --gateway flag?") deployOutput += fmt.Sprintln(err) return http.StatusInternalServerError, deployOutput } @@ -165,16 +168,11 @@ func Deploy(spec *DeployFunctionSpec, update bool, warnInsecureGateway bool) (in case http.StatusOK, http.StatusCreated, http.StatusAccepted: deployOutput += fmt.Sprintf("Deployed. %s.\n", res.Status) - deployedURL := fmt.Sprintf("URL: %s/function/%s", gateway, spec.FunctionName) + deployedURL := fmt.Sprintf("URL: %s/function/%s", c.GatewayURL.String(), generateFuncStr(spec)) deployOutput += fmt.Sprintln(deployedURL) case http.StatusUnauthorized: deployOutput += fmt.Sprintln("unauthorized access, run \"faas-cli login\" to setup authentication for this server") - /* - case http.StatusNotFound: - if replace && !update { - deployOutput += fmt.Sprintln("Could not delete-and-replace function because it is not found (404)") - } - */ + default: bytesOut, err := ioutil.ReadAll(res.Body) if err == nil { diff --git a/vendor/github.com/viveksyngh/faas-cli/proxy/describe.go b/vendor/github.com/openfaas/faas-cli/proxy/describe.go similarity index 59% rename from vendor/github.com/viveksyngh/faas-cli/proxy/describe.go rename to vendor/github.com/openfaas/faas-cli/proxy/describe.go index 61a4990..404bd11 100644 --- a/vendor/github.com/viveksyngh/faas-cli/proxy/describe.go +++ b/vendor/github.com/openfaas/faas-cli/proxy/describe.go @@ -4,33 +4,38 @@ package proxy import ( + "context" "encoding/json" "fmt" "io/ioutil" "net/http" - "strings" - "time" - "github.com/openfaas/faas/gateway/requests" + types "github.com/openfaas/faas-provider/types" ) //GetFunctionInfo get an OpenFaaS function information -func GetFunctionInfo(gateway string, functionName string, tlsInsecure bool) (requests.Function, error) { - var result requests.Function +func (c *Client) GetFunctionInfo(ctx context.Context, functionName string, namespace string) (types.FunctionStatus, error) { + var ( + result types.FunctionStatus + err error + ) - gateway = strings.TrimRight(gateway, "/") - timeout := 60 * time.Second - client := MakeHTTPClient(&timeout, tlsInsecure) + functionPath := fmt.Sprintf("%s/%s", functionPath, functionName) + if len(namespace) > 0 { + functionPath, err = addQueryParams(functionPath, map[string]string{namespaceKey: namespace}) + if err != nil { + return result, err + } + } - getRequest, err := http.NewRequest(http.MethodGet, gateway+"/system/function/"+functionName, nil) + getRequest, err := c.newRequest(http.MethodGet, functionPath, nil) if err != nil { - return result, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", gateway) + return result, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) } - SetAuth(getRequest, gateway) - res, err := client.Do(getRequest) + res, err := c.doRequest(ctx, getRequest) if err != nil { - return result, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", gateway) + return result, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) } @@ -42,15 +47,17 @@ func GetFunctionInfo(gateway string, functionName string, tlsInsecure bool) (req case http.StatusOK: bytesOut, err := ioutil.ReadAll(res.Body) if err != nil { - return result, fmt.Errorf("cannot read result from OpenFaaS on URL: %s", gateway) + return result, fmt.Errorf("cannot read result from OpenFaaS on URL: %s", c.GatewayURL.String()) } jsonErr := json.Unmarshal(bytesOut, &result) if jsonErr != nil { - return result, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%s", gateway, jsonErr.Error()) + return result, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%s", c.GatewayURL.String(), jsonErr.Error()) } case http.StatusUnauthorized: return result, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + case http.StatusNotFound: + return result, fmt.Errorf("No such function: %s", functionName) default: bytesOut, err := ioutil.ReadAll(res.Body) if err == nil { diff --git a/vendor/github.com/openfaas/faas-cli/proxy/function_store.go b/vendor/github.com/openfaas/faas-cli/proxy/function_store.go new file mode 100644 index 0000000..09cd857 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/function_store.go @@ -0,0 +1,52 @@ +package proxy + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/openfaas/faas-cli/schema" +) + +// FunctionStoreList returns functions from a store URL +func FunctionStoreList(store string) ([]schema.StoreItem, error) { + var results []schema.StoreItem + + store = strings.TrimRight(store, "/") + + timeout := 60 * time.Second + tlsInsecure := false + + client := MakeHTTPClient(&timeout, tlsInsecure) + + res, err := client.Get(store) + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS store at URL: %s", store) + } + + if res.Body != nil { + defer res.Body.Close() + } + + switch res.StatusCode { + case http.StatusOK: + bytesOut, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("cannot read result from OpenFaaS store at URL: %s", store) + } + + jsonErr := json.Unmarshal(bytesOut, &results) + if jsonErr != nil { + return nil, fmt.Errorf("cannot parse result from OpenFaaS store at URL: %s\n%s", store, jsonErr.Error()) + } + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + return nil, fmt.Errorf("server returned unexpected status code: %d - %s", res.StatusCode, string(bytesOut)) + } + } + return results, nil +} diff --git a/vendor/github.com/viveksyngh/faas-cli/proxy/invoke.go b/vendor/github.com/openfaas/faas-cli/proxy/invoke.go similarity index 85% rename from vendor/github.com/viveksyngh/faas-cli/proxy/invoke.go rename to vendor/github.com/openfaas/faas-cli/proxy/invoke.go index 8b2cc7d..5dea390 100644 --- a/vendor/github.com/viveksyngh/faas-cli/proxy/invoke.go +++ b/vendor/github.com/openfaas/faas-cli/proxy/invoke.go @@ -7,7 +7,6 @@ import ( "bytes" "os" - "crypto/tls" "fmt" "io/ioutil" "net/http" @@ -16,20 +15,15 @@ import ( ) // InvokeFunction a function -func InvokeFunction(gateway string, name string, bytesIn *[]byte, contentType string, query []string, headers []string, async bool, httpMethod string, tlsInsecure bool) (*[]byte, error) { +func InvokeFunction(gateway string, name string, bytesIn *[]byte, contentType string, query []string, headers []string, async bool, httpMethod string, tlsInsecure bool, namespace string) (*[]byte, error) { var resBytes []byte gateway = strings.TrimRight(gateway, "/") reader := bytes.NewReader(*bytesIn) - var timeout *time.Duration - client := MakeHTTPClient(timeout, tlsInsecure) - tr := &http.Transport{ - DisableKeepAlives: false, - TLSClientConfig: &tls.Config{InsecureSkipVerify: tlsInsecure}, - } - client.Transport = tr + var disableFunctionTimeout *time.Duration + client := MakeHTTPClient(disableFunctionTimeout, tlsInsecure) qs, qsErr := buildQueryString(query) if qsErr != nil { @@ -51,7 +45,11 @@ func InvokeFunction(gateway string, name string, bytesIn *[]byte, contentType st return nil, httpMethodErr } - gatewayURL := gateway + functionEndpoint + name + qs + gatewayURL := gateway + functionEndpoint + name + if len(namespace) > 0 { + gatewayURL += "." + namespace + } + gatewayURL += qs req, err := http.NewRequest(httpMethod, gatewayURL, reader) if err != nil { @@ -66,7 +64,9 @@ func InvokeFunction(gateway string, name string, bytesIn *[]byte, contentType st req.Header.Add(name, value) } - SetAuth(req, gateway) + // Removed by AE - the system-level basic auth secrets should not be transmitted + // to functions. Functions should implement their own auth. + // SetAuth(req, gateway) res, err := client.Do(req) @@ -126,7 +126,7 @@ func parseHeaders(headers []string) (map[string]string, error) { headerMap := make(map[string]string) for _, header := range headers { - headerValues := strings.Split(header, "=") + headerValues := strings.SplitN(header, "=", 2) if len(headerValues) != 2 { return headerMap, fmt.Errorf("the --header or -H flag must take the form of key=value") } @@ -147,7 +147,9 @@ func parseHeaders(headers []string) (map[string]string, error) { // validateMethod validates the HTTP request method func validateHTTPMethod(httpMethod string) error { - var allowedMethods = []string{http.MethodGet, http.MethodPost} + var allowedMethods = []string{ + http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, + } helpString := strings.Join(allowedMethods, "/") if !contains(allowedMethods, httpMethod) { diff --git a/vendor/github.com/viveksyngh/faas-cli/proxy/list.go b/vendor/github.com/openfaas/faas-cli/proxy/list.go similarity index 59% rename from vendor/github.com/viveksyngh/faas-cli/proxy/list.go rename to vendor/github.com/openfaas/faas-cli/proxy/list.go index 6181bfb..5681f98 100644 --- a/vendor/github.com/viveksyngh/faas-cli/proxy/list.go +++ b/vendor/github.com/openfaas/faas-cli/proxy/list.go @@ -4,34 +4,44 @@ package proxy import ( + "context" "encoding/json" "fmt" "io/ioutil" "net/http" - "strings" - "time" - "github.com/openfaas/faas/gateway/requests" + types "github.com/openfaas/faas-provider/types" ) // ListFunctions list deployed functions -func ListFunctions(gateway string, tlsInsecure bool) ([]requests.Function, error) { - var results []requests.Function +func (c *Client) ListFunctions(ctx context.Context, namespace string) ([]types.FunctionStatus, error) { + var ( + results []types.FunctionStatus + listEndpoint string + err error + ) - gateway = strings.TrimRight(gateway, "/") - timeout := 60 * time.Second - client := MakeHTTPClient(&timeout, tlsInsecure) + c.AddCheckRedirect(func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }) - getRequest, err := http.NewRequest(http.MethodGet, gateway+"/system/functions", nil) - SetAuth(getRequest, gateway) + listEndpoint = systemPath + if len(namespace) > 0 { + listEndpoint, err = addQueryParams(listEndpoint, map[string]string{namespaceKey: namespace}) + if err != nil { + return results, err + } + } + + getRequest, err := c.newRequest(http.MethodGet, listEndpoint, nil) if err != nil { - return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", gateway) + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) } - res, err := client.Do(getRequest) + res, err := c.doRequest(ctx, getRequest) if err != nil { - return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", gateway) + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) } if res.Body != nil { @@ -43,11 +53,11 @@ func ListFunctions(gateway string, tlsInsecure bool) ([]requests.Function, error bytesOut, err := ioutil.ReadAll(res.Body) if err != nil { - return nil, fmt.Errorf("cannot read result from OpenFaaS on URL: %s", gateway) + return nil, fmt.Errorf("cannot read result from OpenFaaS on URL: %s", c.GatewayURL.String()) } jsonErr := json.Unmarshal(bytesOut, &results) if jsonErr != nil { - return nil, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%s", gateway, jsonErr.Error()) + return nil, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%s", c.GatewayURL.String(), jsonErr.Error()) } case http.StatusUnauthorized: return nil, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") diff --git a/vendor/github.com/openfaas/faas-cli/proxy/logs.go b/vendor/github.com/openfaas/faas-cli/proxy/logs.go new file mode 100644 index 0000000..8fe3967 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/logs.go @@ -0,0 +1,97 @@ +package proxy + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/openfaas/faas-provider/logs" +) + +// GetLogs return stream for the logs +func (c *Client) GetLogs(ctx context.Context, params logs.Request) (<-chan logs.Message, error) { + + logRequest, err := c.newRequest(http.MethodGet, "/system/logs", nil) + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + logRequest.URL.RawQuery = reqAsQueryValues(params).Encode() + + res, err := c.doRequest(ctx, logRequest) + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + logStream := make(chan logs.Message, 1000) + switch res.StatusCode { + case http.StatusOK: + go func() { + defer close(logStream) + defer res.Body.Close() + + decoder := json.NewDecoder(res.Body) + for decoder.More() { + msg := logs.Message{} + err := decoder.Decode(&msg) + if err != nil { + log.Printf("cannot parse log results: %s\n", err.Error()) + return + } + logStream <- msg + } + }() + case http.StatusUnauthorized: + return nil, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + return nil, fmt.Errorf("server returned unexpected status code: %d - %s", res.StatusCode, string(bytesOut)) + } + } + return logStream, nil +} + +func reqAsQueryValues(r logs.Request) url.Values { + query := url.Values{} + query.Add("name", r.Name) + query.Add("follow", strconv.FormatBool(r.Follow)) + if r.Instance != "" { + query.Add("instance", r.Instance) + } + + if r.Since != nil { + query.Add("since", r.Since.Format(time.RFC3339)) + } + + if r.Tail != 0 { + query.Add("tail", strconv.Itoa(r.Tail)) + } + + return query +} + +func makeStreamingHTTPClient(tlsInsecure bool) http.Client { + client := http.Client{} + + if tlsInsecure { + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + } + + if tlsInsecure { + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: tlsInsecure} + } + + client.Transport = tr + } + + return client +} diff --git a/vendor/github.com/openfaas/faas-cli/proxy/namespaces.go b/vendor/github.com/openfaas/faas-cli/proxy/namespaces.go new file mode 100644 index 0000000..0a45d0b --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/namespaces.go @@ -0,0 +1,54 @@ +package proxy + +import ( + "context" + "encoding/json" + + "fmt" + "io/ioutil" + "net/http" +) + +// ListNamespaces lists available function namespaces +func (c *Client) ListNamespaces(ctx context.Context) ([]string, error) { + var namespaces []string + c.AddCheckRedirect(func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }) + + getRequest, err := c.newRequest(http.MethodGet, namespacesPath, nil) + + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + res, err := c.doRequest(ctx, getRequest) + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + if res.Body != nil { + defer res.Body.Close() + } + + switch res.StatusCode { + case http.StatusOK: + + bytesOut, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("cannot read namespaces from OpenFaaS on URL: %s", c.GatewayURL.String()) + } + jsonErr := json.Unmarshal(bytesOut, &namespaces) + if jsonErr != nil { + return nil, fmt.Errorf("cannot parse namespaces from OpenFaaS on URL: %s\n%s", c.GatewayURL.String(), jsonErr.Error()) + } + case http.StatusUnauthorized: + return nil, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + return nil, fmt.Errorf("server returned unexpected status code: %d - %s", res.StatusCode, string(bytesOut)) + } + } + return namespaces, nil +} diff --git a/vendor/github.com/openfaas/faas-cli/proxy/proxy.go b/vendor/github.com/openfaas/faas-cli/proxy/proxy.go new file mode 100644 index 0000000..c303022 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/proxy.go @@ -0,0 +1,48 @@ +// Copyright (c) Alex Ellis 2017. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package proxy + +import ( + "crypto/tls" + "net" + "net/http" + "time" +) + +// MakeHTTPClient makes a HTTP client with good defaults for timeouts. +func MakeHTTPClient(timeout *time.Duration, tlsInsecure bool) http.Client { + return makeHTTPClientWithDisableKeepAlives(timeout, tlsInsecure, false) +} + +// makeHTTPClientWithDisableKeepAlives makes a HTTP client with good defaults for timeouts. +func makeHTTPClientWithDisableKeepAlives(timeout *time.Duration, tlsInsecure bool, disableKeepAlives bool) http.Client { + client := http.Client{} + + if timeout != nil || tlsInsecure { + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DisableKeepAlives: disableKeepAlives, + } + + if timeout != nil { + client.Timeout = *timeout + tr.DialContext = (&net.Dialer{ + Timeout: *timeout, + }).DialContext + + tr.IdleConnTimeout = 120 * time.Millisecond + tr.ExpectContinueTimeout = 1500 * time.Millisecond + } + + if tlsInsecure { + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: tlsInsecure} + } + + tr.DisableKeepAlives = disableKeepAlives + + client.Transport = tr + } + + return client +} diff --git a/vendor/github.com/openfaas/faas-cli/proxy/secret.go b/vendor/github.com/openfaas/faas-cli/proxy/secret.go new file mode 100644 index 0000000..e004273 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/secret.go @@ -0,0 +1,190 @@ +package proxy + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + types "github.com/openfaas/faas-provider/types" +) + +const ( + secretEndpoint = "/system/secrets" +) + +// GetSecretList get secrets list +func (c *Client) GetSecretList(ctx context.Context, namespace string) ([]types.Secret, error) { + var ( + results []types.Secret + err error + secretPath = secretEndpoint + ) + + if len(namespace) > 0 { + secretPath, err = addQueryParams(secretPath, map[string]string{namespaceKey: namespace}) + } + + getRequest, err := c.newRequest(http.MethodGet, secretPath, nil) + + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + res, err := c.doRequest(ctx, getRequest) + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + if res.Body != nil { + defer res.Body.Close() + } + + switch res.StatusCode { + case http.StatusOK, http.StatusAccepted: + + bytesOut, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("cannot read result from OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + jsonErr := json.Unmarshal(bytesOut, &results) + if jsonErr != nil { + return nil, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%s", c.GatewayURL.String(), jsonErr.Error()) + } + + case http.StatusUnauthorized: + return nil, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + return nil, fmt.Errorf("server returned unexpected status code: %d - %s", res.StatusCode, string(bytesOut)) + } + } + + return results, nil +} + +// UpdateSecret update a secret via the OpenFaaS API by name +func (c *Client) UpdateSecret(ctx context.Context, secret types.Secret) (int, string) { + var output string + reqBytes, _ := json.Marshal(&secret) + + putRequest, err := c.newRequest(http.MethodPut, secretEndpoint, bytes.NewBuffer(reqBytes)) + + if err != nil { + output += fmt.Sprintf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + return http.StatusInternalServerError, output + } + + res, err := c.doRequest(ctx, putRequest) + if err != nil { + output += fmt.Sprintf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + return http.StatusInternalServerError, output + } + + if res.Body != nil { + defer res.Body.Close() + } + + switch res.StatusCode { + case http.StatusOK, http.StatusAccepted: + output += fmt.Sprintf("Updated: %s\n", res.Status) + break + + case http.StatusNotFound: + output += fmt.Sprintf("unable to find secret: %s", secret.Name) + + case http.StatusUnauthorized: + output += fmt.Sprintf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + output += fmt.Sprintf("server returned unexpected status code: %d - %s", res.StatusCode, string(bytesOut)) + } + } + + return res.StatusCode, output +} + +// RemoveSecret remove a secret via the OpenFaaS API by name +func (c *Client) RemoveSecret(ctx context.Context, secret types.Secret) error { + body, _ := json.Marshal(secret) + req, err := c.newRequest(http.MethodDelete, secretEndpoint, bytes.NewBuffer(body)) + if err != nil { + return fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + res, err := c.doRequest(ctx, req) + if err != nil { + return fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + if res.Body != nil { + defer res.Body.Close() + } + + switch res.StatusCode { + case http.StatusOK, http.StatusAccepted: + break + case http.StatusNotFound: + return fmt.Errorf("unable to find secret: %s", secret.Name) + case http.StatusUnauthorized: + return fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + return fmt.Errorf("server returned unexpected status code: %d - %s", res.StatusCode, string(bytesOut)) + } + } + + return nil +} + +// CreateSecret create secret +func (c *Client) CreateSecret(ctx context.Context, secret types.Secret) (int, string) { + var output string + reqBytes, _ := json.Marshal(&secret) + reader := bytes.NewReader(reqBytes) + + request, err := c.newRequest(http.MethodPost, secretEndpoint, reader) + + if err != nil { + output += fmt.Sprintf("cannot connect to OpenFaaS on URL: %s\n", c.GatewayURL.String()) + return http.StatusInternalServerError, output + } + + res, err := c.doRequest(ctx, request) + if err != nil { + output += fmt.Sprintf("cannot connect to OpenFaaS on URL: %s\n", c.GatewayURL.String()) + return http.StatusInternalServerError, output + } + + if res.Body != nil { + defer res.Body.Close() + } + + switch res.StatusCode { + case http.StatusOK, http.StatusCreated, http.StatusAccepted: + output += fmt.Sprintf("Created: %s\n", res.Status) + + case http.StatusUnauthorized: + output += fmt.Sprintln("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + + case http.StatusConflict: + output += fmt.Sprintf("secret with the name %q already exists\n", secret.Name) + + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + output += fmt.Sprintf("server returned unexpected status code: %d - %s\n", res.StatusCode, string(bytesOut)) + } + } + + return res.StatusCode, output +} diff --git a/vendor/github.com/openfaas/faas-cli/proxy/utils.go b/vendor/github.com/openfaas/faas-cli/proxy/utils.go new file mode 100644 index 0000000..04de0c9 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/utils.go @@ -0,0 +1,51 @@ +package proxy + +import ( + "fmt" + "net/url" + "path" +) + +const ( + systemPath = "/system/functions" + functionPath = "/system/function" + namespacesPath = "/system/namespaces" + namespaceKey = "namespace" +) + +func createSystemEndpoint(gateway, namespace string) (string, error) { + gatewayURL, err := url.Parse(gateway) + if err != nil { + return "", fmt.Errorf("invalid gateway URL: %s", err.Error()) + } + gatewayURL.Path = path.Join(gatewayURL.Path, systemPath) + if len(namespace) > 0 { + q := gatewayURL.Query() + q.Set("namespace", namespace) + gatewayURL.RawQuery = q.Encode() + } + return gatewayURL.String(), nil +} + +func createFunctionEndpoint(gateway, functionName, namespace string) (string, error) { + gatewayURL, err := url.Parse(gateway) + if err != nil { + return "", fmt.Errorf("invalid gateway URL: %s", err.Error()) + } + gatewayURL.Path = path.Join(gatewayURL.Path, functionPath, functionName) + if len(namespace) > 0 { + q := gatewayURL.Query() + q.Set("namespace", namespace) + gatewayURL.RawQuery = q.Encode() + } + return gatewayURL.String(), nil +} + +func createNamespacesEndpoint(gateway string) (string, error) { + gatewayURL, err := url.Parse(gateway) + if err != nil { + return "", fmt.Errorf("invalid gateway URL: %s", err.Error()) + } + gatewayURL.Path = path.Join(gatewayURL.Path, namespacesPath) + return gatewayURL.String(), nil +} diff --git a/vendor/github.com/openfaas/faas-cli/proxy/version.go b/vendor/github.com/openfaas/faas-cli/proxy/version.go new file mode 100644 index 0000000..0070b1f --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/proxy/version.go @@ -0,0 +1,53 @@ +// Copyright (c) OpenFaaS Author(s) 2018. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package proxy + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +//GetSystemInfo get system information from /system/info endpoint +func (c *Client) GetSystemInfo(ctx context.Context) (map[string]interface{}, error) { + infoEndPoint := "/system/info" + req, err := c.newRequest(http.MethodGet, infoEndPoint, nil) + if err != nil { + return nil, fmt.Errorf("invalid HTTP method or invalid URL") + } + + response, err := c.doRequest(ctx, req) + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String()) + } + + if response.Body != nil { + defer response.Body.Close() + } + info := make(map[string]interface{}) + + switch response.StatusCode { + case http.StatusOK: + bytesOut, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("cannot read result from OpenFaaS on URL: %s", c.GatewayURL.String()) + } + err = json.Unmarshal(bytesOut, &info) + if err != nil { + return nil, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%s", c.GatewayURL.String(), err.Error()) + } + + case http.StatusUnauthorized: + return nil, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + default: + bytesOut, err := ioutil.ReadAll(response.Body) + if err == nil { + return nil, fmt.Errorf("server returned unexpected status code: %d - %s", response.StatusCode, string(bytesOut)) + } + } + + return info, nil +} diff --git a/vendor/github.com/openfaas/faas-cli/schema/describe.go b/vendor/github.com/openfaas/faas-cli/schema/describe.go new file mode 100644 index 0000000..354b57f --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/schema/describe.go @@ -0,0 +1,19 @@ +// Copyright (c) OpenFaaS Author(s) 2018. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package schema + +//FunctionDescription information related to a function +type FunctionDescription struct { + Name string + Status string + Replicas int + AvailableReplicas int + InvocationCount int + Image string + EnvProcess string + URL string + AsyncURL string + Labels *map[string]string + Annotations *map[string]string +} diff --git a/vendor/github.com/openfaas/faas-cli/schema/image.go b/vendor/github.com/openfaas/faas-cli/schema/image.go new file mode 100644 index 0000000..4fd3b01 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/schema/image.go @@ -0,0 +1,84 @@ +package schema + +import ( + "fmt" + "strings" +) + +// BuildFormat defines the docker image tag format that is used during the build process +type BuildFormat int + +// DefaultFormat as defined in the YAML file or appending :latest +const DefaultFormat BuildFormat = 0 + +// SHAFormat uses "latest-" as the docker tag +const SHAFormat BuildFormat = 1 + +// BranchAndSHAFormat uses "latest--" as the docker tag +const BranchAndSHAFormat BuildFormat = 2 + +// DescribeFormat uses the git-describe output as the docker tag +const DescribeFormat BuildFormat = 3 + +// Type implements pflag.Value +func (i *BuildFormat) Type() string { + return "string" +} + +// String implements Stringer +func (i *BuildFormat) String() string { + if i == nil { + return "latest" + } + + switch *i { + case DefaultFormat: + return "latest" + case SHAFormat: + return "sha" + case BranchAndSHAFormat: + return "branch" + case DescribeFormat: + return "describe" + default: + return "latest" + } +} + +// Set implements pflag.Value +func (i *BuildFormat) Set(value string) error { + switch strings.ToLower(value) { + case "", "default", "latest": + *i = DefaultFormat + case "sha": + *i = SHAFormat + case "branch": + *i = BranchAndSHAFormat + case "describe": + *i = DescribeFormat + default: + return fmt.Errorf("unknown image tag format: '%s'", value) + } + return nil +} + +// BuildImageName builds a Docker image tag for build, push or deploy +func BuildImageName(format BuildFormat, image string, version string, branch string) string { + imageVal := image + if strings.Contains(image, ":") == false { + imageVal += ":latest" + } + + switch format { + case SHAFormat: + return imageVal + "-" + version + case BranchAndSHAFormat: + return imageVal + "-" + branch + "-" + version + case DescribeFormat: + // should we trim the existing image tag and do a proper replace with + // the describe describe value + return imageVal + "-" + version + default: + return imageVal + } +} diff --git a/vendor/github.com/openfaas/faas-cli/schema/metadata.go b/vendor/github.com/openfaas/faas-cli/schema/metadata.go new file mode 100644 index 0000000..d8d797d --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/schema/metadata.go @@ -0,0 +1,10 @@ +// Copyright (c) OpenFaaS Author(s) 2018. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package schema + +// Metadata metadata of the object +type Metadata struct { + Name string `yaml:"name"` + Namespace string `yaml:"namespace,omitempty"` +} diff --git a/vendor/github.com/openfaas/faas-cli/schema/secret.go b/vendor/github.com/openfaas/faas-cli/schema/secret.go new file mode 100644 index 0000000..9c0ef59 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/schema/secret.go @@ -0,0 +1,13 @@ +package schema + +type KubernetesSecret struct { + Kind string `json:"kind"` + ApiVersion string `json:"apiVersion"` + Metadata KubernetesSecretMetadata `json:"metadata"` + Data map[string]string `json:"data"` +} + +type KubernetesSecretMetadata struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} diff --git a/vendor/github.com/openfaas/faas-cli/schema/store_item.go b/vendor/github.com/openfaas/faas-cli/schema/store_item.go new file mode 100644 index 0000000..b14d0b2 --- /dev/null +++ b/vendor/github.com/openfaas/faas-cli/schema/store_item.go @@ -0,0 +1,17 @@ +package schema + +// StoreItem represents an item of store +type StoreItem struct { + Icon string `json:"icon"` + Title string `json:"title"` + Description string `json:"description"` + Image string `json:"image"` + Name string `json:"name"` + Fprocess string `json:"fprocess"` + Network string `json:"network"` + RepoURL string `json:"repo_url"` + Environment map[string]string `json:"environment"` + Labels map[string]string `json:"labels"` + Annotations map[string]string `json:"annotations"` + ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem"` +} diff --git a/vendor/github.com/openfaas/faas-cli/stack/schema.go b/vendor/github.com/openfaas/faas-cli/stack/schema.go index f5495c7..346e145 100644 --- a/vendor/github.com/openfaas/faas-cli/stack/schema.go +++ b/vendor/github.com/openfaas/faas-cli/stack/schema.go @@ -13,7 +13,8 @@ type Provider struct { // Function as deployed or built on FaaS type Function struct { // Name of deployed function - Name string `yaml:"-"` + Name string `yaml:"-"` + Language string `yaml:"lang"` // Handler Local folder to use for function @@ -56,6 +57,32 @@ type Function struct { // Annotations Annotations *map[string]string `yaml:"annotations"` + + // Namespace of the function + Namespace string `yaml:"namespace,omitempty"` +} + +// Configuration for the stack.yml file +type Configuration struct { + StackConfig StackConfiguration `yaml:"configuration"` +} + +// StackConfiguration for the overall stack.yml +type StackConfiguration struct { + TemplateConfigs []TemplateSource `yaml:"templates"` + // CopyExtraPaths specifies additional paths (relative to the stack file) that will be copied + // into the functions build context, e.g. specifying `"common"` will look for and copy the + // "common/" folder of file in the same root as the stack file. All paths must be contained + // within the project root defined by the location of the stack file. + // + // The yaml uses the shorter name `copy` to make it easier for developers to read and use + CopyExtraPaths []string `yaml:"copy"` +} + +// TemplateSource for build templates +type TemplateSource struct { + Name string `yaml:"name"` + Source string `yaml:"source,omitempty"` } // FunctionResources Memory and CPU @@ -71,19 +98,24 @@ type EnvironmentFile struct { // Services root level YAML file to define FaaS function-set type Services struct { - Functions map[string]Function `yaml:"functions,omitempty"` - Provider Provider `yaml:"provider,omitempty"` + Version string `yaml:"version,omitempty"` + Functions map[string]Function `yaml:"functions,omitempty"` + Provider Provider `yaml:"provider,omitempty"` + StackConfiguration StackConfiguration `yaml:"configuration,omitempty"` } // LanguageTemplate read from template.yml within root of a language template folder type LanguageTemplate struct { - Language string `yaml:"language"` - FProcess string `yaml:"fprocess"` - BuildOptions []BuildOption `yaml:"build_options"` + Language string `yaml:"language,omitempty"` + FProcess string `yaml:"fprocess,omitempty"` + BuildOptions []BuildOption `yaml:"build_options,omitempty"` // WelcomeMessage is printed to the user after generating a function - WelcomeMessage string `yaml:"welcome_message"` + WelcomeMessage string `yaml:"welcome_message,omitempty"` + // HandlerFolder to copy the function code into + HandlerFolder string `yaml:"handler_folder,omitempty"` } +// BuildOption a named build option for one or more packages type BuildOption struct { Name string `yaml:"name"` Packages []string `yaml:"packages"` diff --git a/vendor/github.com/openfaas/faas-cli/stack/stack.go b/vendor/github.com/openfaas/faas-cli/stack/stack.go index 917f11e..c68b01c 100644 --- a/vendor/github.com/openfaas/faas-cli/stack/stack.go +++ b/vendor/github.com/openfaas/faas-cli/stack/stack.go @@ -9,18 +9,26 @@ import ( "net" "net/http" "net/url" + "os" "regexp" "time" - "github.com/ryanuber/go-glob" + envsubst "github.com/drone/envsubst" + glob "github.com/ryanuber/go-glob" yaml "gopkg.in/yaml.v2" ) -const providerName = "faas" -const providerNameLong = "openfaas" +const legacyProviderName = "faas" +const providerName = "openfaas" +const defaultSchemaVersion = "1.0" -// ParseYAMLData parse YAML file into a stack of "services". -func ParseYAMLFile(yamlFile, regex, filter string) (*Services, error) { +// ValidSchemaVersions available schema versions +var ValidSchemaVersions = []string{ + "1.0", +} + +// ParseYAMLFile parse YAML file into a stack of "services". +func ParseYAMLFile(yamlFile, regex, filter string, envsubst bool) (*Services, error) { var err error var fileData []byte urlParsed, err := url.Parse(yamlFile) @@ -36,16 +44,45 @@ func ParseYAMLFile(yamlFile, regex, filter string) (*Services, error) { return nil, err } } - return ParseYAMLData(fileData, regex, filter) + return ParseYAMLData(fileData, regex, filter, envsubst) +} + +func substituteEnvironment(data []byte) ([]byte, error) { + + ret, err := envsubst.Parse(string(data)) + if err != nil { + return nil, err + } + + res, resErr := ret.Execute(func(input string) string { + if val, ok := os.LookupEnv(input); ok { + return val + } + return "" + }) + + return []byte(res), resErr } // ParseYAMLData parse YAML data into a stack of "services". -func ParseYAMLData(fileData []byte, regex string, filter string) (*Services, error) { +func ParseYAMLData(fileData []byte, regex string, filter string, envsubst bool) (*Services, error) { var services Services regexExists := len(regex) > 0 filterExists := len(filter) > 0 - err := yaml.Unmarshal(fileData, &services) + var source []byte + if envsubst { + substData, substErr := substituteEnvironment(fileData) + + if substErr != nil { + return &services, substErr + } + source = substData + } else { + source = fileData + } + + err := yaml.Unmarshal(source, &services) if err != nil { fmt.Printf("Error with YAML file\n") return nil, err @@ -57,8 +94,12 @@ func ParseYAMLData(fileData []byte, regex string, filter string) (*Services, err } } - if services.Provider.Name != providerName && services.Provider.Name != providerNameLong { - return nil, fmt.Errorf("['%s', '%s'] is the only valid provider for this tool - found: %s", providerName, providerNameLong, services.Provider.Name) + if services.Provider.Name != providerName { + return nil, fmt.Errorf(`['%s'] is the only valid "provider.name" for the OpenFaaS CLI, but you gave: %s`, providerName, services.Provider.Name) + } + + if len(services.Version) > 0 && !IsValidSchemaVersion(services.Version) { + return nil, fmt.Errorf("%s are the only valid versions for the stack file - found: %s", ValidSchemaVersions, services.Version) } if regexExists && filterExists { @@ -136,3 +177,13 @@ func fetchYAML(address *url.URL) ([]byte, error) { return resBytes, err } + +// IsValidSchemaVersion validates schema version +func IsValidSchemaVersion(schemaVersion string) bool { + for _, validVersion := range ValidSchemaVersions { + if schemaVersion == validVersion { + return true + } + } + return false +} diff --git a/vendor/github.com/openfaas/faas-provider/LICENSE b/vendor/github.com/openfaas/faas-provider/LICENSE new file mode 100644 index 0000000..e547e74 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Alex Ellis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/openfaas/faas-provider/httputil/writers.go b/vendor/github.com/openfaas/faas-provider/httputil/writers.go new file mode 100644 index 0000000..c07e993 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/httputil/writers.go @@ -0,0 +1,12 @@ +package httputil + +import ( + "fmt" + "net/http" +) + +// Errorf sets the response status code and write formats the provided message as the +// response body +func Errorf(w http.ResponseWriter, statusCode int, msg string, args ...interface{}) { + http.Error(w, fmt.Sprintf(msg, args...), statusCode) +} diff --git a/vendor/github.com/openfaas/faas-provider/logs/handler.go b/vendor/github.com/openfaas/faas-provider/logs/handler.go new file mode 100644 index 0000000..70616d8 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/logs/handler.go @@ -0,0 +1,144 @@ +package logs + +import ( + "context" + "encoding/json" + "log" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/openfaas/faas-provider/httputil" +) + +// Requester submits queries the logging system. +// This will be passed to the log handler constructor. +type Requester interface { + // Query submits a log request to the actual logging system. + Query(context.Context, Request) (<-chan Message, error) +} + +// NewLogHandlerFunc creates an http HandlerFunc from the supplied log Requestor. +func NewLogHandlerFunc(requestor Requester, timeout time.Duration) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Body != nil { + defer r.Body.Close() + } + + cn, ok := w.(http.CloseNotifier) + if !ok { + log.Println("LogHandler: response is not a CloseNotifier, required for streaming response") + http.NotFound(w, r) + return + } + flusher, ok := w.(http.Flusher) + if !ok { + log.Println("LogHandler: response is not a Flusher, required for streaming response") + http.NotFound(w, r) + return + } + + logRequest, err := parseRequest(r) + if err != nil { + log.Printf("LogHandler: could not parse request %s", err) + httputil.Errorf(w, http.StatusUnprocessableEntity, "could not parse the log request") + return + } + + ctx, cancelQuery := context.WithTimeout(r.Context(), timeout) + defer cancelQuery() + messages, err := requestor.Query(ctx, logRequest) + if err != nil { + // add smarter error handling here + httputil.Errorf(w, http.StatusInternalServerError, "function log request failed") + return + } + + // Send the initial headers saying we're gonna stream the response. + w.Header().Set("Connection", "Keep-Alive") + w.Header().Set("Transfer-Encoding", "chunked") + w.Header().Set(http.CanonicalHeaderKey("Content-Type"), "application/x-ndjson") + w.WriteHeader(http.StatusOK) + flusher.Flush() + + // ensure that we always try to send the closing chunk, not the inverted order due to how + // the defer stack works. We need two flush statements to ensure that the empty slice is + // sent as its own chunk + defer flusher.Flush() + defer w.Write([]byte{}) + defer flusher.Flush() + + jsonEncoder := json.NewEncoder(w) + for messages != nil { + select { + case <-cn.CloseNotify(): + log.Println("LogHandler: client stopped listening") + return + case msg, ok := <-messages: + if !ok { + log.Println("LogHandler: end of log stream") + messages = nil + return + } + + // serialize and write the msg to the http ResponseWriter + err := jsonEncoder.Encode(msg) + if err != nil { + // can't actually write the status header here so we should json serialize an error + // and return that because we have already sent the content type and status code + log.Printf("LogHandler: failed to serialize log message: '%s'\n", msg.String()) + log.Println(err.Error()) + // write json error message here ? + jsonEncoder.Encode(Message{Text: "failed to serialize log message"}) + flusher.Flush() + return + } + + flusher.Flush() + } + } + + return + } +} + +// parseRequest extracts the logRequest from the GET variables or from the POST body +func parseRequest(r *http.Request) (logRequest Request, err error) { + query := r.URL.Query() + logRequest.Name = getValue(query, "name") + logRequest.Namespace = getValue(query, "namespace") + logRequest.Instance = getValue(query, "instance") + tailStr := getValue(query, "tail") + if tailStr != "" { + logRequest.Tail, err = strconv.Atoi(tailStr) + if err != nil { + return logRequest, err + } + } + + // ignore error because it will default to false if we can't parse it + logRequest.Follow, _ = strconv.ParseBool(getValue(query, "follow")) + + sinceStr := getValue(query, "since") + if sinceStr != "" { + since, err := time.Parse(time.RFC3339, sinceStr) + logRequest.Since = &since + if err != nil { + return logRequest, err + } + } + + return logRequest, nil +} + +// getValue returns the value for the given key. If the key has more than one value, it returns the +// last value. if the value does not exist, it returns the empty string. +func getValue(queryValues url.Values, name string) string { + values := queryValues[name] + if len(values) == 0 { + return "" + } + + return values[len(values)-1] +} diff --git a/vendor/github.com/openfaas/faas-provider/logs/logs.go b/vendor/github.com/openfaas/faas-provider/logs/logs.go new file mode 100644 index 0000000..4069c40 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/logs/logs.go @@ -0,0 +1,62 @@ +// Package logs provides the standard interface and handler for OpenFaaS providers to expose function logs. +// +// The package defines the Requester interface that OpenFaaS providers should implement and then expose using +// the predefined NewLogHandlerFunc. See the example folder for a minimal log provider implementation. +// +// The Requester is where the actual specific logic for connecting to and querying the log system should be implemented. +// +package logs + +import ( + "fmt" + "time" +) + +// Request is the query to return the function logs. +type Request struct { + // Name is the function name and is required + Name string `json:"name"` + // Namespace is the namespace the function is deployed to, how a namespace is defined + // is faas-provider specific + Namespace string `json:"namespace"` + // Instance is the optional container name, that allows you to request logs from a specific function instance + Instance string `json:"instance"` + // Since is the optional datetime value to start the logs from + Since *time.Time `json:"since"` + // Tail sets the maximum number of log messages to return, <=0 means unlimited + Tail int `json:"tail"` + // Follow is allows the user to request a stream of logs until the timeout + Follow bool `json:"follow"` +} + +// String implements that Stringer interface and prints the log Request in a consistent way that +// allows you to safely compare if two requests have the same value. +func (r Request) String() string { + return fmt.Sprintf( + "name:%s namespace: %s instance:%s since:%v tail:%d follow:%v", + r.Name, r.Namespace, r.Instance, r.Since, r.Tail, r.Follow, + ) +} + +// Message is a specific log message from a function container log stream +type Message struct { + // Name is the function name + Name string `json:"name"` + // Namespace is the namespace the function is deployed to, how a namespace is defined + // is faas-provider specific + Namespace string `json:"namespace"` + // instance is the name/id of the specific function instance + Instance string `json:"instance"` + // Timestamp is the timestamp of when the log message was recorded + Timestamp time.Time `json:"timestamp"` + // Text is the raw log message content + Text string `json:"text"` +} + +// String implements the Stringer interface and allows for nice and simple string formatting of a log Message. +func (m Message) String() string { + return fmt.Sprintf( + "%s %s (%s %s) %s", + m.Timestamp.String(), m.Name, m.Namespace, m.Instance, m.Text, + ) +} diff --git a/vendor/github.com/openfaas/faas-provider/types/config.go b/vendor/github.com/openfaas/faas-provider/types/config.go new file mode 100644 index 0000000..6f4fd15 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/types/config.go @@ -0,0 +1,87 @@ +package types + +import ( + "net/http" + "time" +) + +const ( + defaultReadTimeout = 10 * time.Second + defaultMaxIdleConns = 1024 +) + +// FaaSHandlers provide handlers for OpenFaaS +type FaaSHandlers struct { + // FunctionProxy provides the function invocation proxy logic. Use proxy.NewHandlerFunc to + // use the standard OpenFaaS proxy implementation or provide completely custom proxy logic. + FunctionProxy http.HandlerFunc + + FunctionReader http.HandlerFunc + DeployHandler http.HandlerFunc + + DeleteHandler http.HandlerFunc + ReplicaReader http.HandlerFunc + ReplicaUpdater http.HandlerFunc + SecretHandler http.HandlerFunc + // LogHandler provides streaming json logs of functions + LogHandler http.HandlerFunc + + // UpdateHandler an existing function/service + UpdateHandler http.HandlerFunc + // HealthHandler defines the default health endpoint bound to "/healthz + // If the handler is not set, then the "/healthz" path will not be configured + HealthHandler http.HandlerFunc + InfoHandler http.HandlerFunc + ListNamespaceHandler http.HandlerFunc +} + +// FaaSConfig set config for HTTP handlers +type FaaSConfig struct { + // TCPPort is the public port for the API. + TCPPort *int + // HTTP timeout for reading a request from clients. + ReadTimeout time.Duration + // HTTP timeout for writing a response from functions. + WriteTimeout time.Duration + // EnableHealth enables/disables the default health endpoint bound to "/healthz". + // + // Deprecated: basic auth is enabled automatcally by setting the HealthHandler in the FaaSHandlers + // struct. This value is not longer read or used. + EnableHealth bool + // EnableBasicAuth enforces basic auth on the API. If set, reads secrets from file-system + // location specificed in `SecretMountPath`. + EnableBasicAuth bool + // SecretMountPath specifies where to read secrets from for embedded basic auth. + SecretMountPath string + // MaxIdleConns with a default value of 1024, can be used for tuning HTTP proxy performance. + MaxIdleConns int + // MaxIdleConnsPerHost with a default value of 1024, can be used for tuning HTTP proxy performance. + MaxIdleConnsPerHost int +} + +// GetReadTimeout is a helper to safely return the configured ReadTimeout or the default value of 10s +func (c *FaaSConfig) GetReadTimeout() time.Duration { + if c.ReadTimeout <= 0*time.Second { + return defaultReadTimeout + } + return c.ReadTimeout +} + +// GetMaxIdleConns is a helper to safely return the configured MaxIdleConns or the default value of 1024 +func (c *FaaSConfig) GetMaxIdleConns() int { + if c.MaxIdleConns < 1 { + return defaultMaxIdleConns + } + + return c.MaxIdleConns +} + +// GetMaxIdleConns is a helper to safely return the configured MaxIdleConns or the default value which +// should then match the MaxIdleConns +func (c *FaaSConfig) GetMaxIdleConnsPerHost() int { + if c.MaxIdleConnsPerHost < 1 { + return c.GetMaxIdleConns() + } + + return c.MaxIdleConnsPerHost +} diff --git a/vendor/github.com/openfaas/faas-provider/types/model.go b/vendor/github.com/openfaas/faas-provider/types/model.go new file mode 100644 index 0000000..9e73c21 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/types/model.go @@ -0,0 +1,99 @@ +package types + +// FunctionDeployment represents a request to create or update a Function. +type FunctionDeployment struct { + + // Service corresponds to a Service + Service string `json:"service"` + + // Image corresponds to a Docker image + Image string `json:"image"` + + // Network is specific to Docker Swarm - default overlay network is: func_functions + Network string `json:"network"` + + // EnvProcess corresponds to the fprocess variable for your container watchdog. + EnvProcess string `json:"envProcess"` + + // EnvVars provides overrides for functions. + EnvVars map[string]string `json:"envVars"` + + // RegistryAuth is the registry authentication (optional) + // in the same encoded format as Docker native credentials + // (see ~/.docker/config.json) + RegistryAuth string `json:"registryAuth,omitempty"` + + // Constraints are specific to back-end orchestration platform + Constraints []string `json:"constraints"` + + // Secrets list of secrets to be made available to function + Secrets []string `json:"secrets"` + + // Labels are metadata for functions which may be used by the + // back-end for making scheduling or routing decisions + Labels *map[string]string `json:"labels"` + + // Annotations are metadata for functions which may be used by the + // back-end for management, orchestration, events and build tasks + Annotations *map[string]string `json:"annotations"` + + // Limits for function + Limits *FunctionResources `json:"limits"` + + // Requests of resources requested by function + Requests *FunctionResources `json:"requests"` + + // ReadOnlyRootFilesystem removes write-access from the root filesystem + // mount-point. + ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem"` + + // Namespace for the function to be deployed into + Namespace string `json:"namespace,omitempty"` +} + +// FunctionResources Memory and CPU +type FunctionResources struct { + Memory string `json:"memory"` + CPU string `json:"cpu"` +} + +// FunctionStatus exported for system/functions endpoint +type FunctionStatus struct { + + // Name corresponds to a Service + Name string `json:"name"` + + // Image corresponds to a Docker image + Image string `json:"image"` + + // InvocationCount count of invocations + InvocationCount float64 `json:"invocationCount"` + + // Replicas desired within the cluster + Replicas uint64 `json:"replicas"` + + // EnvProcess is the process to pass to the watchdog, if in use + EnvProcess string `json:"envProcess"` + + // AvailableReplicas is the count of replicas ready to receive + // invocations as reported by the backend + AvailableReplicas uint64 `json:"availableReplicas"` + + // Labels are metadata for functions which may be used by the + // backend for making scheduling or routing decisions + Labels *map[string]string `json:"labels"` + + // Annotations are metadata for functions which may be used by the + // backend for management, orchestration, events and build tasks + Annotations *map[string]string `json:"annotations"` + + // Namespace where the function can be accessed + Namespace string `json:"namespace,omitempty"` +} + +// Secret for underlying orchestrator +type Secret struct { + Name string `json:"name"` + Namespace string `json:"namespace,omitempty"` + Value string `json:"value,omitempty"` +} diff --git a/vendor/github.com/openfaas/faas-provider/types/read_config.go b/vendor/github.com/openfaas/faas-provider/types/read_config.go new file mode 100644 index 0000000..d28a9d1 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/types/read_config.go @@ -0,0 +1,112 @@ +package types + +import ( + "fmt" + "os" + "strconv" + "time" +) + +// OsEnv implements interface to wrap os.Getenv +type OsEnv struct { +} + +// Getenv wraps os.Getenv +func (OsEnv) Getenv(key string) string { + return os.Getenv(key) +} + +// HasEnv provides interface for os.Getenv +type HasEnv interface { + Getenv(key string) string +} + +// ReadConfig constitutes config from env variables +type ReadConfig struct { +} + +// ParseIntValue parses the the int in val or, if there is an error, returns the +// specified default value +func ParseIntValue(val string, fallback int) int { + if len(val) > 0 { + parsedVal, parseErr := strconv.Atoi(val) + if parseErr == nil && parsedVal >= 0 { + return parsedVal + } + } + return fallback +} + +// ParseIntOrDurationValue parses the the duration in val or, if there is an error, returns the +// specified default value +func ParseIntOrDurationValue(val string, fallback time.Duration) time.Duration { + if len(val) > 0 { + parsedVal, parseErr := strconv.Atoi(val) + if parseErr == nil && parsedVal >= 0 { + return time.Duration(parsedVal) * time.Second + } + } + + duration, durationErr := time.ParseDuration(val) + if durationErr != nil { + return fallback + } + + return duration +} + +// ParseBoolValue parses the the boolean in val or, if there is an error, returns the +// specified default value +func ParseBoolValue(val string, fallback bool) bool { + if len(val) > 0 { + return val == "true" + } + return fallback +} + +// ParseString verifies the string in val is not empty. When empty, it returns the +// specified default value +func ParseString(val string, fallback string) string { + if len(val) > 0 { + return val + } + return fallback +} + +// Read fetches config from environmental variables. +func (ReadConfig) Read(hasEnv HasEnv) (*FaaSConfig, error) { + cfg := &FaaSConfig{ + ReadTimeout: ParseIntOrDurationValue(hasEnv.Getenv("read_timeout"), time.Second*10), + WriteTimeout: ParseIntOrDurationValue(hasEnv.Getenv("write_timeout"), time.Second*10), + EnableBasicAuth: ParseBoolValue(hasEnv.Getenv("basic_auth"), false), + // default value from Gateway + SecretMountPath: ParseString(hasEnv.Getenv("secret_mount_path"), "/run/secrets/"), + } + + port := ParseIntValue(hasEnv.Getenv("port"), 8080) + cfg.TCPPort = &port + + cfg.MaxIdleConns = 1024 + maxIdleConns := hasEnv.Getenv("max_idle_conns") + if len(maxIdleConns) > 0 { + val, err := strconv.Atoi(maxIdleConns) + if err != nil { + return nil, fmt.Errorf("invalid value for max_idle_conns: %s", maxIdleConns) + } + cfg.MaxIdleConns = val + + } + + cfg.MaxIdleConnsPerHost = 1024 + maxIdleConnsPerHost := hasEnv.Getenv("max_idle_conns_per_host") + if len(maxIdleConnsPerHost) > 0 { + val, err := strconv.Atoi(maxIdleConnsPerHost) + if err != nil { + return nil, fmt.Errorf("invalid value for max_idle_conns_per_host: %s", maxIdleConnsPerHost) + } + cfg.MaxIdleConnsPerHost = val + + } + + return cfg, nil +} diff --git a/vendor/github.com/openfaas/faas-provider/types/requests.go b/vendor/github.com/openfaas/faas-provider/types/requests.go new file mode 100644 index 0000000..bef4af4 --- /dev/null +++ b/vendor/github.com/openfaas/faas-provider/types/requests.go @@ -0,0 +1,22 @@ +// Copyright (c) Alex Ellis 2017. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package types + +type ScaleServiceRequest struct { + ServiceName string `json:"serviceName"` + Replicas uint64 `json:"replicas"` +} + +// InfoResponse provides information about the underlying provider +type InfoResponse struct { + Provider string `json:"provider"` + Version ProviderVersion `json:"version"` + Orchestration string `json:"orchestration"` +} + +// ProviderVersion provides the commit sha and release version number of the underlying provider +type ProviderVersion struct { + SHA string `json:"sha"` + Release string `json:"release"` +} diff --git a/vendor/github.com/viveksyngh/faas-cli/proxy/auth.go b/vendor/github.com/viveksyngh/faas-cli/proxy/auth.go deleted file mode 100644 index b1a64b7..0000000 --- a/vendor/github.com/viveksyngh/faas-cli/proxy/auth.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) OpenFaaS Project 2017. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -package proxy - -import ( - "net/http" - - "github.com/openfaas/faas-cli/config" -) - -//SetAuth sets basic auth for the given gateway -func SetAuth(req *http.Request, gateway string) { - username, password, err := config.LookupAuthConfig(gateway) - if err != nil { - // no auth info found - return - } - - req.SetBasicAuth(username, password) -} diff --git a/vendor/github.com/viveksyngh/faas-cli/proxy/proxy.go b/vendor/github.com/viveksyngh/faas-cli/proxy/proxy.go deleted file mode 100644 index 9a053ac..0000000 --- a/vendor/github.com/viveksyngh/faas-cli/proxy/proxy.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Alex Ellis 2017. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -package proxy - -import ( - "crypto/tls" - "net" - "net/http" - "time" -) - -// MakeHTTPClient makes a HTTP client with good defaults for timeouts. -func MakeHTTPClient(timeout *time.Duration, tlsInsecure bool) http.Client { - if timeout != nil { - return http.Client{ - Timeout: *timeout, - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: *timeout, - // KeepAlive: 0, - }).DialContext, - // MaxIdleConns: 1, - // DisableKeepAlives: true, - IdleConnTimeout: 120 * time.Millisecond, - ExpectContinueTimeout: 1500 * time.Millisecond, - TLSClientConfig: &tls.Config{InsecureSkipVerify: tlsInsecure}, - }, - } - } - - // This should be used for faas-cli invoke etc. - return http.Client{} -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 830a01d..17f9d60 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -56,6 +56,10 @@ github.com/bgentry/go-netrc/netrc github.com/bgentry/speakeasy # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew +# github.com/drone/envsubst v1.0.2 +github.com/drone/envsubst +github.com/drone/envsubst/parse +github.com/drone/envsubst/path # github.com/fatih/color v1.7.0 github.com/fatih/color # github.com/golang/protobuf v1.3.2 @@ -196,9 +200,15 @@ github.com/mitchellh/reflectwalk github.com/oklog/run # github.com/openfaas/faas v0.0.0-20180822191204-91b6c244941c github.com/openfaas/faas/gateway/requests -# github.com/openfaas/faas-cli v0.0.0-20180829141537-b7ba9a60dc2c +# github.com/openfaas/faas-cli v0.0.0-20200226083118-b0a70a3f4f20 github.com/openfaas/faas-cli/config +github.com/openfaas/faas-cli/proxy +github.com/openfaas/faas-cli/schema github.com/openfaas/faas-cli/stack +# github.com/openfaas/faas-provider v0.15.0 +github.com/openfaas/faas-provider/httputil +github.com/openfaas/faas-provider/logs +github.com/openfaas/faas-provider/types # github.com/posener/complete v1.2.1 github.com/posener/complete github.com/posener/complete/cmd @@ -214,8 +224,6 @@ github.com/ulikunitz/xz github.com/ulikunitz/xz/internal/hash github.com/ulikunitz/xz/internal/xlog github.com/ulikunitz/xz/lzma -# github.com/viveksyngh/faas-cli v0.0.0-20180831214523-1fe7ecc51fb5 -github.com/viveksyngh/faas-cli/proxy # github.com/vmihailenco/msgpack v3.3.3+incompatible github.com/vmihailenco/msgpack github.com/vmihailenco/msgpack/codes