├── img ├── ondemand.gif ├── traefik.png └── gophers-traefik.png ├── vendor ├── github.com │ ├── stretchr │ │ └── testify │ │ │ ├── require │ │ │ ├── require_forward.go.tmpl │ │ │ ├── require.go.tmpl │ │ │ ├── forward_requirements.go │ │ │ ├── doc.go │ │ │ └── requirements.go │ │ │ ├── assert │ │ │ ├── assertion_format.go.tmpl │ │ │ ├── assertion_forward.go.tmpl │ │ │ ├── errors.go │ │ │ ├── forward_assertions.go │ │ │ ├── doc.go │ │ │ ├── http_assertions.go │ │ │ └── assertion_compare.go │ │ │ └── LICENSE │ ├── davecgh │ │ └── go-spew │ │ │ ├── LICENSE │ │ │ └── spew │ │ │ ├── bypasssafe.go │ │ │ ├── bypass.go │ │ │ ├── spew.go │ │ │ ├── doc.go │ │ │ ├── common.go │ │ │ ├── format.go │ │ │ └── config.go │ └── pmezard │ │ └── go-difflib │ │ └── LICENSE ├── gopkg.in │ └── yaml.v3 │ │ ├── .travis.yml │ │ ├── NOTICE │ │ ├── writerc.go │ │ ├── LICENSE │ │ ├── sorter.go │ │ ├── README.md │ │ ├── yamlprivateh.go │ │ ├── resolve.go │ │ └── readerc.go └── modules.txt ├── release.config.js ├── go.mod ├── .gitignore ├── examples ├── docker_classic │ ├── dynamic-config.yml │ ├── README.md │ └── docker-compose.yml ├── docker_swarm │ ├── README.md │ └── docker-stack.yml ├── kubernetes │ ├── values.yaml │ ├── docker-compose.yml │ ├── deploy-whoami.yml │ ├── README.md │ └── manifests.yml └── multiple_containers │ ├── README.md │ └── docker-stack.yml ├── traefik_dev.yml ├── .traefik.yml ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── traefik.yml ├── pkg ├── strategy │ ├── strategy.go │ ├── blocking_strategy.go │ ├── dynamic_strategy.go │ ├── dynamic_strategy_test.go │ ├── blocking_strategy_test.go │ └── strategy_test_cases.go └── pages │ ├── error.html │ ├── error.go │ ├── loading.html │ └── loading.go ├── go.sum ├── docker-compose.yml ├── ondemand_test.go ├── ondemand.go ├── README.md └── LICENSE /img/ondemand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acouvreur/traefik-ondemand-plugin/HEAD/img/ondemand.gif -------------------------------------------------------------------------------- /img/traefik.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acouvreur/traefik-ondemand-plugin/HEAD/img/traefik.png -------------------------------------------------------------------------------- /img/gophers-traefik.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acouvreur/traefik-ondemand-plugin/HEAD/img/gophers-traefik.png -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/require_forward.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentWithoutT "a"}} 2 | func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { 3 | if h, ok := a.t.(tHelper); ok { h.Helper() } 4 | {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentFormat}} 2 | func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool { 3 | if h, ok := t.(tHelper); ok { h.Helper() } 4 | return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentWithoutT "a"}} 2 | func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { 3 | if h, ok := a.t.(tHelper); ok { h.Helper() } 4 | return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/require.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.Comment}} 2 | func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { 3 | if h, ok := t.(tHelper); ok { h.Helper() } 4 | if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } 5 | t.FailNow() 6 | } 7 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.4.x" 5 | - "1.5.x" 6 | - "1.6.x" 7 | - "1.7.x" 8 | - "1.8.x" 9 | - "1.9.x" 10 | - "1.10.x" 11 | - "1.11.x" 12 | - "1.12.x" 13 | - "1.13.x" 14 | - "tip" 15 | 16 | go_import_path: gopkg.in/yaml.v3 17 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "branches": [ 3 | { "name": "main" }, 4 | { "name": "beta", "channel": "beta", "prerelease": "beta" }, 5 | ], 6 | "plugins": [ 7 | "@semantic-release/commit-analyzer", 8 | "@semantic-release/release-notes-generator", 9 | "@semantic-release/github" 10 | ] 11 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/acouvreur/traefik-ondemand-plugin 2 | 3 | go 1.17 4 | 5 | require github.com/stretchr/testify v1.6.1 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.0 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | traefik -------------------------------------------------------------------------------- /examples/docker_classic/dynamic-config.yml: -------------------------------------------------------------------------------- 1 | http: 2 | 3 | services: 4 | whoami: 5 | loadBalancer: 6 | servers: 7 | - url: "http://whoami:80" 8 | 9 | routers: 10 | whoami: 11 | rule: PathPrefix(`/whoami`) 12 | entryPoints: 13 | - "http" 14 | middlewares: 15 | - ondemand@docker 16 | service: "whoami" -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/errors.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // AnError is an error instance useful for testing. If the code does not care 8 | // about error specifics, and only needs to return the error for example, this 9 | // error should be used to make the test code more readable. 10 | var AnError = errors.New("assert.AnError general error for testing") 11 | -------------------------------------------------------------------------------- /examples/docker_swarm/README.md: -------------------------------------------------------------------------------- 1 | # Docker swarm 2 | 3 | ## Run the demo 4 | 5 | 1. `git clone git@github.com:acouvreur/traefik-ondemand-plugin.git` 6 | 2. `cd traefik-ondemand-plugin/examples/docker_swarm` 7 | 3. `docker swarm init` 8 | 4. `export TRAEFIK_PILOT_TOKEN=...` 9 | 5. `docker stack deploy -c docker-stack.yml DOCKER_SWARM` 10 | 6. Load `http://localhost/nginx` 11 | 7. Wait 1 minute 12 | 8. Service is scaled to 0/0 -------------------------------------------------------------------------------- /examples/kubernetes/values.yaml: -------------------------------------------------------------------------------- 1 | # traefik helm values 2 | additionalArguments: 3 | - "--pilot.token=XXXXX_YOURTOKEN_XXXXXXXXXXXXXXXX" 4 | - "--experimental.plugins.traefik-ondemand-plugin.modulename=github.com/acouvreur/traefik-ondemand-plugin" 5 | - "--experimental.plugins.traefik-ondemand-plugin.version=v1.2.0" 6 | - "--providers.kubernetesingress.allowEmptyServices=true" 7 | 8 | experimental: 9 | plugins: 10 | enabled: true 11 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/davecgh/go-spew v1.1.0 2 | ## explicit 3 | github.com/davecgh/go-spew/spew 4 | # github.com/pmezard/go-difflib v1.0.0 5 | ## explicit 6 | github.com/pmezard/go-difflib/difflib 7 | # github.com/stretchr/testify v1.6.1 8 | ## explicit; go 1.13 9 | github.com/stretchr/testify/assert 10 | github.com/stretchr/testify/require 11 | # gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c 12 | ## explicit 13 | gopkg.in/yaml.v3 14 | -------------------------------------------------------------------------------- /traefik_dev.yml: -------------------------------------------------------------------------------- 1 | pilot: 2 | token: "$TRAEFIK_PILOT_TOKEN" 3 | 4 | api: 5 | dashboard: true 6 | insecure: true 7 | 8 | experimental: 9 | localPlugins: 10 | traefik-ondemand-plugin: 11 | moduleName: github.com/acouvreur/traefik-ondemand-plugin 12 | 13 | entryPoints: 14 | http: 15 | address: ":80" 16 | forwardedHeaders: 17 | insecure: true 18 | 19 | providers: 20 | docker: 21 | swarmMode: true 22 | exposedByDefault: false -------------------------------------------------------------------------------- /.traefik.yml: -------------------------------------------------------------------------------- 1 | displayName: Containers On Demand 2 | type: middleware 3 | 4 | import: github.com/acouvreur/traefik-ondemand-plugin 5 | 6 | summary: 'Start your containers/services on the first request they recieve, and shut them down after a specified duration after the last request they received. Kubernetes, Docker classic and docker swarm compatible.' 7 | 8 | testData: 9 | serviceUrl: http://ondemand:10000 10 | name: TRAEFIK_HACKATHON_whoami 11 | timeout: 1m 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: Set up Go 1.17 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: ^1.17 17 | 18 | - name: Check out code into the Go module directory 19 | uses: actions/checkout@v2 20 | 21 | - name: Build 22 | run: go build -v . 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/forward_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | // Assertions provides assertion methods around the 4 | // TestingT interface. 5 | type Assertions struct { 6 | t TestingT 7 | } 8 | 9 | // New makes a new Assertions object for the specified TestingT. 10 | func New(t TestingT) *Assertions { 11 | return &Assertions{ 12 | t: t, 13 | } 14 | } 15 | 16 | //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs" 17 | -------------------------------------------------------------------------------- /traefik.yml: -------------------------------------------------------------------------------- 1 | pilot: 2 | token: "$TRAEFIK_PILOT_TOKEN" 3 | 4 | api: 5 | dashboard: true 6 | insecure: true 7 | 8 | experimental: 9 | plugins: 10 | traefik-ondemand-plugin: 11 | moduleName: "github.com/acouvreur/traefik-ondemand-plugin" 12 | version: "v0.1.1" 13 | 14 | entryPoints: 15 | http: 16 | address: ":80" 17 | forwardedHeaders: 18 | insecure: true 19 | 20 | providers: 21 | docker: 22 | swarmMode: true 23 | exposedByDefault: false 24 | file: 25 | filename: "/etc/traefik/config.yml" 26 | watch: true -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/forward_requirements.go: -------------------------------------------------------------------------------- 1 | package require 2 | 3 | // Assertions provides assertion methods around the 4 | // TestingT interface. 5 | type Assertions struct { 6 | t TestingT 7 | } 8 | 9 | // New makes a new Assertions object for the specified TestingT. 10 | func New(t TestingT) *Assertions { 11 | return &Assertions{ 12 | t: t, 13 | } 14 | } 15 | 16 | //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs" 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - beta 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: '14.17' 22 | 23 | - name: Release 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | run: npx semantic-release -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2016 Canonical Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /examples/kubernetes/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | server: 4 | image: "rancher/k3s:${K3S_VERSION:-latest}" 5 | command: server --no-deploy traefik 6 | tmpfs: 7 | - /run 8 | - /var/run 9 | ulimits: 10 | nproc: 65535 11 | nofile: 12 | soft: 65535 13 | hard: 65535 14 | privileged: true 15 | restart: always 16 | environment: 17 | - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml 18 | - K3S_KUBECONFIG_MODE=666 19 | volumes: 20 | - k3s-server:/var/lib/rancher/k3s 21 | # This is just so that we get the kubeconfig file out 22 | - .:/output 23 | ports: 24 | - 6443:6443 # Kubernetes API Server 25 | - 80:80 # Ingress controller port 80 26 | - 443:443 # Ingress controller port 443 27 | 28 | volumes: 29 | k3s-server: {} 30 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2012-2016 Dave Collins 4 | 5 | Permission to use, copy, modify, and distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /examples/docker_classic/README.md: -------------------------------------------------------------------------------- 1 | # Docker classic 2 | 3 | ## Run the demo 4 | 5 | 1. `git clone git@github.com:acouvreur/traefik-ondemand-plugin.git` 6 | 2. `cd traefik-ondemand-plugin/examples/docker_classic` 7 | 3. `export TRAEFIK_PILOT_TOKEN=...` 8 | 4. `docker-compose up` 9 | 10 | The log: `level=error msg="middleware \"ondemand@docker\" does not exist" entryPointName=http routerName=whoami@file` is expected because the file provider is parsed before the docker containers. However this should appear only once and not cause any issue. 11 | 5. `docker stop docker_classic_whoami_1` 12 | 6. Load `http://localhost/whoami` 13 | 7. Wait 1 minute 14 | 8. Container is stopped 15 | 16 | ## Limitations 17 | 18 | ### Cannot use service labels 19 | 20 | Cannot use labels because as soon as the container is stopped, the labels are not treated by Traefik. 21 | 22 | The route doesn't exist anymore, so we use dynamic-config.yml file instead. -------------------------------------------------------------------------------- /examples/multiple_containers/README.md: -------------------------------------------------------------------------------- 1 | # Docker swarm 2 | 3 | ## Run the demo 4 | 5 | 1. `git clone git@github.com:acouvreur/traefik-ondemand-plugin.git` 6 | 2. `cd traefik-ondemand-plugin/examples/multiple_containers` 7 | 3. `docker swarm init` 8 | 4. `export TRAEFIK_PILOT_TOKEN=...` 9 | 5. `docker stack deploy -c docker-stack.yml DOCKER_SWARM` 10 | 6. Load `http://localhost/nginx` 11 | 7. Load `http://localhost/whoami` 12 | 8. After 1 minute whoami is scaled to 0/0 13 | 9. After 5 minutes nginx is scaled to 0/0 14 | 10. `docker stack rm DOCKER_SWARM` 15 | 16 | ## Limitations 17 | 18 | ### Define a middleware per service/container 19 | 20 | Due to Traefik plugin, the interface is to provide a config and a `ServeHTTP` request. 21 | 22 | This function has no access to the Traefik configuration, thus no way to determine the container/service associated to the request. 23 | 24 | See https://github.com/acouvreur/traefik-ondemand-plugin/issues/8#issuecomment-931940533. -------------------------------------------------------------------------------- /pkg/strategy/strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/http" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Net client is a custom client to timeout after 2 seconds if the service is not ready 12 | var netClient = &http.Client{ 13 | Timeout: time.Second * 2, 14 | } 15 | 16 | type Strategy interface { 17 | ServeHTTP(rw http.ResponseWriter, req *http.Request) 18 | } 19 | 20 | func getServiceStatus(request string) (string, error) { 21 | 22 | // This request wakes up the service if he's scaled to 0 23 | resp, err := netClient.Get(request) 24 | if err != nil { 25 | return "error", err 26 | } 27 | 28 | defer resp.Body.Close() 29 | body, err := ioutil.ReadAll(resp.Body) 30 | if err != nil { 31 | return "parsing error", err 32 | } 33 | 34 | if resp.StatusCode >= 400 { 35 | return "error from ondemand service", errors.New(string(body)) 36 | } 37 | 38 | return strings.TrimSuffix(string(body), "\n"), nil 39 | } 40 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/doc.go: -------------------------------------------------------------------------------- 1 | // Package require implements the same assertions as the `assert` package but 2 | // stops test execution when a test fails. 3 | // 4 | // Example Usage 5 | // 6 | // The following is a complete example using require in a standard test function: 7 | // import ( 8 | // "testing" 9 | // "github.com/stretchr/testify/require" 10 | // ) 11 | // 12 | // func TestSomething(t *testing.T) { 13 | // 14 | // var a string = "Hello" 15 | // var b string = "Hello" 16 | // 17 | // require.Equal(t, a, b, "The two words should be the same.") 18 | // 19 | // } 20 | // 21 | // Assertions 22 | // 23 | // The `require` package have same global functions as in the `assert` package, 24 | // but instead of returning a boolean result they call `t.FailNow()`. 25 | // 26 | // Every assertion function also takes an optional string message as the final argument, 27 | // allowing custom error messages to be appended to the message the assertion method outputs. 28 | package require 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 7 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /examples/kubernetes/deploy-whoami.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: whoami 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: whoami 10 | template: 11 | metadata: 12 | labels: 13 | app: whoami 14 | spec: 15 | containers: 16 | - name: whoami 17 | image: containous/whoami 18 | --- 19 | apiVersion: v1 20 | kind: Service 21 | metadata: 22 | name: whoami-service 23 | spec: 24 | ports: 25 | - name: http 26 | targetPort: 80 27 | port: 80 28 | selector: 29 | app: whoami 30 | --- 31 | apiVersion: networking.k8s.io/v1 32 | kind: Ingress 33 | metadata: 34 | name: whoami-ingress 35 | annotations: 36 | kubernetes.io/ingress.class: traefik 37 | traefik.ingress.kubernetes.io/router.middlewares: default-ondemand-whoami@kubernetescrd 38 | spec: 39 | rules: 40 | - host: localhost 41 | http: 42 | paths: 43 | - path: / 44 | pathType: ImplementationSpecific 45 | backend: 46 | service: 47 | name: whoami-service 48 | port: 49 | number: 80 -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/requirements.go: -------------------------------------------------------------------------------- 1 | package require 2 | 3 | // TestingT is an interface wrapper around *testing.T 4 | type TestingT interface { 5 | Errorf(format string, args ...interface{}) 6 | FailNow() 7 | } 8 | 9 | type tHelper interface { 10 | Helper() 11 | } 12 | 13 | // ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful 14 | // for table driven tests. 15 | type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) 16 | 17 | // ValueAssertionFunc is a common function prototype when validating a single value. Can be useful 18 | // for table driven tests. 19 | type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) 20 | 21 | // BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful 22 | // for table driven tests. 23 | type BoolAssertionFunc func(TestingT, bool, ...interface{}) 24 | 25 | // ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful 26 | // for table driven tests. 27 | type ErrorAssertionFunc func(TestingT, error, ...interface{}) 28 | 29 | //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs" 30 | -------------------------------------------------------------------------------- /examples/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes 2 | 3 | ## Run the demo 4 | 5 | # you need docker-compose, kubectl and helm (v3) installed 6 | 7 | 1. `git clone git@github.com:acouvreur/traefik-ondemand-plugin.git` 8 | 2. `cd traefik-ondemand-plugin/examples/kubernetes` 9 | 3. `docker-compose up` 10 | 4. Wait 1 minute 11 | 5. `export KUBECONFIG=./kubeconfig.yaml` 12 | 5. `helm repo add traefik https://helm.traefik.io/traefik` 13 | 6. `helm repo update` 14 | 7. Edit values.yaml and add your traefik pilot.token 15 | 8. `helm install traefik traefik/traefik -f values.yaml --namespace kube-system ` 16 | 9. `kubectl apply -f deploy-whoami.yml` 17 | 10. `kubectl apply -f manifests.yml` 18 | 11. `kubectl scale deploy whoami --replicas=0` 19 | 12. Browse to http://localhost/ 20 | 13. `kubectl get deployments -o wide` 21 | ``` 22 | NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR 23 | whoami 1/1 1 1 16m whoami containous/whoami app=whoami 24 | ``` 25 | 13. After 1 minute: `kubectl get deployments -o wide` 26 | ``` 27 | NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR 28 | whoami 0/0 0 0 17m whoami containous/whoami app=whoami` 29 | ``` 30 | 14. Browse to http://localhost/ -------------------------------------------------------------------------------- /vendor/github.com/pmezard/go-difflib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Patrick Mezard 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | The names of its contributors may not be used to endorse or promote 14 | products derived from this software without specific prior written 15 | permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 18 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/doc.go: -------------------------------------------------------------------------------- 1 | // Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. 2 | // 3 | // Example Usage 4 | // 5 | // The following is a complete example using assert in a standard test function: 6 | // import ( 7 | // "testing" 8 | // "github.com/stretchr/testify/assert" 9 | // ) 10 | // 11 | // func TestSomething(t *testing.T) { 12 | // 13 | // var a string = "Hello" 14 | // var b string = "Hello" 15 | // 16 | // assert.Equal(t, a, b, "The two words should be the same.") 17 | // 18 | // } 19 | // 20 | // if you assert many times, use the format below: 21 | // 22 | // import ( 23 | // "testing" 24 | // "github.com/stretchr/testify/assert" 25 | // ) 26 | // 27 | // func TestSomething(t *testing.T) { 28 | // assert := assert.New(t) 29 | // 30 | // var a string = "Hello" 31 | // var b string = "Hello" 32 | // 33 | // assert.Equal(a, b, "The two words should be the same.") 34 | // } 35 | // 36 | // Assertions 37 | // 38 | // Assertions allow you to easily write test code, and are global funcs in the `assert` package. 39 | // All assertion functions take, as the first argument, the `*testing.T` object provided by the 40 | // testing framework. This allows the assertion funcs to write the failings and other details to 41 | // the correct place. 42 | // 43 | // Every assertion function also takes an optional string message as the final argument, 44 | // allowing custom error messages to be appended to the message the assertion method outputs. 45 | package assert 46 | -------------------------------------------------------------------------------- /pkg/strategy/blocking_strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type BlockingStrategy struct { 12 | Requests []string 13 | Name string 14 | Next http.Handler 15 | Timeout time.Duration 16 | BlockDelay time.Duration 17 | BlockCheckInterval time.Duration 18 | } 19 | 20 | type InternalServerError struct { 21 | ServiceName string `json:"serviceName"` 22 | Error string `json:"error"` 23 | } 24 | 25 | // ServeHTTP retrieve the service status 26 | func (e *BlockingStrategy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 27 | 28 | for start := time.Now(); time.Since(start) < e.BlockDelay; { 29 | notReadyCount := 0 30 | for _, request := range e.Requests { 31 | 32 | log.Printf("Sending request: %s", request) 33 | status, err := getServiceStatus(request) 34 | log.Printf("Status: %s", status) 35 | 36 | if err != nil { 37 | rw.Header().Set("Content-Type", "application/json") 38 | rw.WriteHeader(http.StatusInternalServerError) 39 | json.NewEncoder(rw).Encode(InternalServerError{ServiceName: e.Name, Error: err.Error()}) 40 | return 41 | } 42 | 43 | if status != "started" { 44 | notReadyCount++ 45 | } 46 | } 47 | if notReadyCount == 0 { 48 | // Services all started forward request 49 | e.Next.ServeHTTP(rw, req) 50 | return 51 | } 52 | 53 | time.Sleep(e.BlockCheckInterval) 54 | } 55 | 56 | rw.Header().Set("Content-Type", "application/json") 57 | rw.WriteHeader(http.StatusServiceUnavailable) 58 | json.NewEncoder(rw).Encode(InternalServerError{ServiceName: e.Name, Error: fmt.Sprintf("Service was unreachable within %s", e.BlockDelay)}) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/strategy/dynamic_strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/acouvreur/traefik-ondemand-plugin/pkg/pages" 9 | ) 10 | 11 | type DynamicStrategy struct { 12 | Requests []string 13 | Name string 14 | Next http.Handler 15 | Timeout time.Duration 16 | DisplayName string 17 | LoadingPage string 18 | ErrorPage string 19 | } 20 | 21 | // ServeHTTP retrieve the service status 22 | func (e *DynamicStrategy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 23 | started := make([]bool, len(e.Requests)) 24 | 25 | displayName := e.Name 26 | if len(e.DisplayName) > 0 { 27 | displayName = e.DisplayName 28 | } 29 | 30 | notReadyCount := 0 31 | for requestIndex, request := range e.Requests { 32 | log.Printf("Sending request: %s", request) 33 | status, err := getServiceStatus(request) 34 | log.Printf("Status: %s", status) 35 | 36 | if err != nil { 37 | rw.WriteHeader(http.StatusInternalServerError) 38 | rw.Write([]byte(pages.GetErrorPage(e.ErrorPage, displayName, err.Error()))) 39 | return 40 | } 41 | 42 | if status == "started" { 43 | started[requestIndex] = true 44 | } else if status == "starting" { 45 | started[requestIndex] = false 46 | notReadyCount++ 47 | } else { 48 | // Error 49 | rw.WriteHeader(http.StatusInternalServerError) 50 | rw.Write([]byte(pages.GetErrorPage(e.ErrorPage, displayName, status))) 51 | return 52 | } 53 | } 54 | if notReadyCount == 0 { 55 | // All services are ready, forward request 56 | e.Next.ServeHTTP(rw, req) 57 | } else { 58 | // Services still starting, notify client 59 | rw.WriteHeader(http.StatusAccepted) 60 | rw.Write([]byte(pages.GetLoadingPage(e.LoadingPage, displayName, e.Timeout))) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/docker_classic/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | traefik: 5 | image: traefik 6 | command: 7 | - --api=true 8 | - --api.insecure=true 9 | - --pilot.token=$TRAEFIK_PILOT_TOKEN 10 | - --experimental.plugins.traefik-ondemand-plugin.moduleName=github.com/acouvreur/traefik-ondemand-plugin 11 | - --experimental.plugins.traefik-ondemand-plugin.version=v1.2.0 12 | - --providers.docker=true 13 | - --providers.file.filename=/etc/traefik/dynamic-config.yml 14 | - --entrypoints.http.address=:80 15 | - --entrypoints.https.address=:443 16 | ports: 17 | - "80:80" 18 | - "443:443" 19 | - "8080:8080" 20 | volumes: 21 | - '/var/run/docker.sock:/var/run/docker.sock' 22 | - './dynamic-config.yml:/etc/traefik/dynamic-config.yml' 23 | labels: 24 | - traefik.enable=true 25 | 26 | ondemand: 27 | image: ghcr.io/acouvreur/traefik-ondemand-service:1 28 | command: 29 | - --swarmMode=false 30 | volumes: 31 | - '/var/run/docker.sock:/var/run/docker.sock' 32 | labels: 33 | - traefik.enable=true 34 | - traefik.http.middlewares.ondemand.plugin.traefik-ondemand-plugin.name=docker_classic_whoami_1 35 | - traefik.http.middlewares.ondemand.plugin.traefik-ondemand-plugin.serviceUrl=http://ondemand:10000 36 | - traefik.http.middlewares.ondemand.plugin.traefik-ondemand-plugin.timeout=1m 37 | - traefik.http.services.ondemand.loadbalancer.server.port=10000 38 | 39 | whoami: 40 | image: containous/whoami 41 | # Cannot use labels because as soon as the container is stopped, the labels are not treated by Traefik 42 | # The route doesn't exist anymore. Use dynamic-config.yml file instead. 43 | # labels: 44 | # - traefik.enable 45 | # - traefik.http.routers.whoami.rule=PathPrefix(`/whoami`) 46 | # - traefik.http.routers.whoami.middlewares=ondemand -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypasssafe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when the code is running on Google App Engine, compiled by GopherJS, or 17 | // "-tags safe" is added to the go build command line. The "disableunsafe" 18 | // tag is deprecated and thus should not be used. 19 | // +build js appengine safe disableunsafe 20 | 21 | package spew 22 | 23 | import "reflect" 24 | 25 | const ( 26 | // UnsafeDisabled is a build-time constant which specifies whether or 27 | // not access to the unsafe package is available. 28 | UnsafeDisabled = true 29 | ) 30 | 31 | // unsafeReflectValue typically converts the passed reflect.Value into a one 32 | // that bypasses the typical safety restrictions preventing access to 33 | // unaddressable and unexported data. However, doing this relies on access to 34 | // the unsafe package. This is a stub version which simply returns the passed 35 | // reflect.Value when the unsafe package is not available. 36 | func unsafeReflectValue(v reflect.Value) reflect.Value { 37 | return v 38 | } 39 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/writerc.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011-2019 Canonical Ltd 3 | // Copyright (c) 2006-2010 Kirill Simonov 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | // of the Software, and to permit persons to whom the Software is furnished to do 10 | // so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package yaml 24 | 25 | // Set the writer error and return false. 26 | func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { 27 | emitter.error = yaml_WRITER_ERROR 28 | emitter.problem = problem 29 | return false 30 | } 31 | 32 | // Flush the output buffer. 33 | func yaml_emitter_flush(emitter *yaml_emitter_t) bool { 34 | if emitter.write_handler == nil { 35 | panic("write handler not set") 36 | } 37 | 38 | // Check if the buffer is empty. 39 | if emitter.buffer_pos == 0 { 40 | return true 41 | } 42 | 43 | if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { 44 | return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) 45 | } 46 | emitter.buffer_pos = 0 47 | return true 48 | } 49 | -------------------------------------------------------------------------------- /examples/docker_swarm/docker-stack.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | traefik: 5 | image: traefik 6 | command: 7 | - --api=true 8 | - --api.insecure=true 9 | - --pilot.token=$TRAEFIK_PILOT_TOKEN 10 | - --experimental.plugins.traefik-ondemand-plugin.moduleName=github.com/acouvreur/traefik-ondemand-plugin 11 | - --experimental.plugins.traefik-ondemand-plugin.version=v1.2.0 12 | - --providers.docker=true 13 | - --providers.docker.swarmmode=true 14 | - --providers.file.filename=/etc/traefik/dynamic-config.yml 15 | - --entrypoints.http.address=:80 16 | - --entrypoints.https.address=:443 17 | ports: 18 | - "80:80" 19 | - "443:443" 20 | - "8080:8080" 21 | volumes: 22 | - '/var/run/docker.sock:/var/run/docker.sock' 23 | 24 | ondemand: 25 | image: ghcr.io/acouvreur/traefik-ondemand-service:1 26 | command: 27 | - --swarmMode=true 28 | volumes: 29 | - '/var/run/docker.sock:/var/run/docker.sock' 30 | deploy: 31 | labels: 32 | - traefik.enable=true 33 | - traefik.http.middlewares.ondemand.plugin.traefik-ondemand-plugin.name=DOCKER_SWARM_nginx 34 | - traefik.http.middlewares.ondemand.plugin.traefik-ondemand-plugin.serviceUrl=http://ondemand:10000 35 | - traefik.http.middlewares.ondemand.plugin.traefik-ondemand-plugin.timeout=1m 36 | - traefik.http.services.ondemand.loadbalancer.server.port=10000 37 | 38 | nginx: 39 | image: nginx 40 | deploy: 41 | replicas: 0 42 | labels: 43 | - traefik.enable=true 44 | # If you do not use the swarm load balancer, traefik will evict the service from its pool 45 | # as soon as the service is 0/0. If you do not set that, fallback to dynamic-config.yml file usage. 46 | - traefik.docker.lbswarm=true 47 | - traefik.http.routers.nginx.middlewares=ondemand@docker 48 | - traefik.http.routers.nginx.rule=PathPrefix(`/nginx`) 49 | - traefik.http.services.nginx.loadbalancer.server.port=80 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/kubernetes/manifests.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: traefik-ondemand-service 5 | namespace: kube-system 6 | labels: 7 | app: traefik-ondemand-service 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: traefik-ondemand-service 13 | template: 14 | metadata: 15 | labels: 16 | app: traefik-ondemand-service 17 | spec: 18 | serviceAccountName: traefik-ondemand-service 19 | serviceAccount: traefik-ondemand-service 20 | containers: 21 | - name: traefik-ondemand-service 22 | image: ghcr.io/acouvreur/traefik-ondemand-service:1 23 | args: ["--swarmMode=false", "--kubernetesMode=true"] 24 | ports: 25 | - containerPort: 10000 26 | --- 27 | apiVersion: v1 28 | kind: Service 29 | metadata: 30 | name: traefik-ondemand-service 31 | namespace: kube-system 32 | spec: 33 | selector: 34 | app: traefik-ondemand-service 35 | ports: 36 | - protocol: TCP 37 | port: 10000 38 | targetPort: 10000 39 | --- 40 | apiVersion: v1 41 | kind: ServiceAccount 42 | metadata: 43 | name: traefik-ondemand-service 44 | namespace: kube-system 45 | --- 46 | apiVersion: rbac.authorization.k8s.io/v1 47 | kind: ClusterRole 48 | metadata: 49 | name: traefik-ondemand-service 50 | namespace: kube-system 51 | rules: 52 | - apiGroups: 53 | - apps 54 | resources: 55 | - deployments 56 | - deployments/scale 57 | verbs: 58 | - patch 59 | - get 60 | - update 61 | --- 62 | apiVersion: rbac.authorization.k8s.io/v1 63 | kind: ClusterRoleBinding 64 | metadata: 65 | name: traefik-ondemand-service 66 | namespace: kube-system 67 | roleRef: 68 | apiGroup: rbac.authorization.k8s.io 69 | kind: ClusterRole 70 | name: traefik-ondemand-service 71 | subjects: 72 | - kind: ServiceAccount 73 | name: traefik-ondemand-service 74 | namespace: kube-system 75 | --- 76 | apiVersion: traefik.containo.us/v1alpha1 77 | kind: Middleware 78 | metadata: 79 | name: ondemand-whoami 80 | namespace: default 81 | spec: 82 | plugin: 83 | traefik-ondemand-plugin: 84 | name: deployment_default_whoami_1 85 | serviceUrl: 'http://traefik-ondemand-service:10000' 86 | timeout: 1m -------------------------------------------------------------------------------- /pkg/strategy/dynamic_strategy_test.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSingleDynamicStrategy_ServeHTTP(t *testing.T) { 13 | 14 | for _, test := range SingleServiceTestCases { 15 | test := test 16 | t.Run(test.desc, func(t *testing.T) { 17 | t.Parallel() 18 | 19 | next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 20 | 21 | mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 | fmt.Fprint(w, test.onDemandServiceResponses[0].body) 23 | })) 24 | 25 | defer mockServer.Close() 26 | 27 | dynamicStrategy := &DynamicStrategy{ 28 | Name: "whoami", 29 | Requests: []string{mockServer.URL}, 30 | Next: next, 31 | } 32 | 33 | recorder := httptest.NewRecorder() 34 | 35 | req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil) 36 | 37 | dynamicStrategy.ServeHTTP(recorder, req) 38 | 39 | assert.Equal(t, test.expected.dynamic, recorder.Code) 40 | }) 41 | } 42 | } 43 | 44 | func TestMultipleDynamicStrategy_ServeHTTP(t *testing.T) { 45 | for _, test := range MultipleServicesTestCases { 46 | test := test 47 | t.Run(test.desc, func(t *testing.T) { 48 | t.Parallel() 49 | 50 | next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 51 | 52 | urls := make([]string, len(test.onDemandServiceResponses)) 53 | for responseIndex, response := range test.onDemandServiceResponses { 54 | response := response 55 | mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 | fmt.Fprint(w, response.body) 57 | })) 58 | defer mockServer.Close() 59 | 60 | urls[responseIndex] = mockServer.URL 61 | } 62 | dynamicStrategy := &DynamicStrategy{ 63 | Name: "whoami", 64 | Requests: urls, 65 | Next: next, 66 | } 67 | 68 | recorder := httptest.NewRecorder() 69 | 70 | req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil) 71 | 72 | dynamicStrategy.ServeHTTP(recorder, req) 73 | 74 | assert.Equal(t, test.expected.dynamic, recorder.Code) 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | This project is covered by two different licenses: MIT and Apache. 3 | 4 | #### MIT License #### 5 | 6 | The following files were ported to Go from C files of libyaml, and thus 7 | are still covered by their original MIT license, with the additional 8 | copyright staring in 2011 when the project was ported over: 9 | 10 | apic.go emitterc.go parserc.go readerc.go scannerc.go 11 | writerc.go yamlh.go yamlprivateh.go 12 | 13 | Copyright (c) 2006-2010 Kirill Simonov 14 | Copyright (c) 2006-2011 Kirill Simonov 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy of 17 | this software and associated documentation files (the "Software"), to deal in 18 | the Software without restriction, including without limitation the rights to 19 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 20 | of the Software, and to permit persons to whom the Software is furnished to do 21 | so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | 34 | ### Apache License ### 35 | 36 | All the remaining project files are covered by the Apache license: 37 | 38 | Copyright (c) 2011-2019 Canonical Ltd 39 | 40 | Licensed under the Apache License, Version 2.0 (the "License"); 41 | you may not use this file except in compliance with the License. 42 | You may obtain a copy of the License at 43 | 44 | http://www.apache.org/licenses/LICENSE-2.0 45 | 46 | Unless required by applicable law or agreed to in writing, software 47 | distributed under the License is distributed on an "AS IS" BASIS, 48 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 49 | See the License for the specific language governing permissions and 50 | limitations under the License. 51 | -------------------------------------------------------------------------------- /pkg/strategy/blocking_strategy_test.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestSingleBlockingStrategy_ServeHTTP(t *testing.T) { 14 | for _, test := range SingleServiceTestCases { 15 | test := test 16 | t.Run(test.desc, func(t *testing.T) { 17 | t.Parallel() 18 | 19 | next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 | w.WriteHeader(http.StatusOK) 21 | w.Write([]byte("ok")) 22 | }) 23 | 24 | mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 | w.WriteHeader(test.onDemandServiceResponses[0].status) 26 | fmt.Fprint(w, test.onDemandServiceResponses[0].body) 27 | })) 28 | 29 | defer mockServer.Close() 30 | 31 | blockingStrategy := &BlockingStrategy{ 32 | Name: "whoami", 33 | Requests: []string{mockServer.URL}, 34 | Next: next, 35 | BlockDelay: 1 * time.Second, 36 | } 37 | 38 | recorder := httptest.NewRecorder() 39 | 40 | req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil) 41 | 42 | blockingStrategy.ServeHTTP(recorder, req) 43 | 44 | assert.Equal(t, test.expected.blocking, recorder.Code) 45 | }) 46 | } 47 | } 48 | 49 | func TestMultipleBlockingStrategy_ServeHTTP(t *testing.T) { 50 | 51 | for _, test := range MultipleServicesTestCases { 52 | test := test 53 | t.Run(test.desc, func(t *testing.T) { 54 | t.Parallel() 55 | urls := make([]string, len(test.onDemandServiceResponses)) 56 | next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 57 | w.WriteHeader(http.StatusOK) 58 | w.Write([]byte("ok")) 59 | }) 60 | 61 | for responseIndex, response := range test.onDemandServiceResponses { 62 | response := response 63 | mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 64 | w.WriteHeader(response.status) 65 | fmt.Fprint(w, response.body) 66 | })) 67 | 68 | defer mockServer.Close() 69 | urls[responseIndex] = mockServer.URL 70 | } 71 | fmt.Println(urls) 72 | blockingStrategy := &BlockingStrategy{ 73 | Name: "whoami", 74 | Requests: urls, 75 | Next: next, 76 | BlockDelay: 1 * time.Second, 77 | } 78 | 79 | recorder := httptest.NewRecorder() 80 | 81 | req := httptest.NewRequest(http.MethodGet, "http://mydomain/whoami", nil) 82 | 83 | blockingStrategy.ServeHTTP(recorder, req) 84 | 85 | assert.Equal(t, test.expected.blocking, recorder.Code) 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/multiple_containers/docker-stack.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | traefik: 5 | image: traefik 6 | command: 7 | - --api=true 8 | - --api.insecure=true 9 | - --pilot.token=$TRAEFIK_PILOT_TOKEN 10 | - --experimental.plugins.traefik-ondemand-plugin.moduleName=github.com/acouvreur/traefik-ondemand-plugin 11 | - --experimental.plugins.traefik-ondemand-plugin.version=v1.2.0 12 | - --providers.docker=true 13 | - --providers.docker.swarmmode=true 14 | - --providers.file.filename=/etc/traefik/dynamic-config.yml 15 | - --entrypoints.http.address=:80 16 | - --entrypoints.https.address=:443 17 | ports: 18 | - "80:80" 19 | - "443:443" 20 | - "8080:8080" 21 | volumes: 22 | - '/var/run/docker.sock:/var/run/docker.sock' 23 | 24 | ondemand: 25 | image: ghcr.io/acouvreur/traefik-ondemand-service:1 26 | command: 27 | - --swarmMode=true 28 | volumes: 29 | - '/var/run/docker.sock:/var/run/docker.sock' 30 | deploy: 31 | labels: 32 | - traefik.enable=true 33 | - traefik.http.middlewares.ondemand-nginx.plugin.traefik-ondemand-plugin.name=DOCKER_SWARM_nginx 34 | - traefik.http.middlewares.ondemand-nginx.plugin.traefik-ondemand-plugin.serviceUrl=http://ondemand:10000 35 | - traefik.http.middlewares.ondemand-nginx.plugin.traefik-ondemand-plugin.timeout=5m 36 | - traefik.http.middlewares.ondemand-whoami.plugin.traefik-ondemand-plugin.name=DOCKER_SWARM_whoami 37 | - traefik.http.middlewares.ondemand-whoami.plugin.traefik-ondemand-plugin.serviceUrl=http://ondemand:10000 38 | - traefik.http.middlewares.ondemand-whoami.plugin.traefik-ondemand-plugin.timeout=1m 39 | - traefik.http.services.ondemand.loadbalancer.server.port=10000 40 | 41 | nginx: 42 | image: nginx 43 | deploy: 44 | replicas: 0 45 | labels: 46 | - traefik.enable=true 47 | # If you do not use the swarm load balancer, traefik will evict the service from its pool 48 | # as soon as the service is 0/0. If you do not set that, fallback to dynamic-config.yml file usage. 49 | - traefik.docker.lbswarm=true 50 | - traefik.http.routers.nginx.middlewares=ondemand-nginx@docker 51 | - traefik.http.routers.nginx.rule=PathPrefix(`/nginx`) 52 | - traefik.http.services.nginx.loadbalancer.server.port=80 53 | 54 | whoami: 55 | image: containous/whoami 56 | deploy: 57 | replicas: 0 58 | labels: 59 | - traefik.enable=true 60 | - traefik.docker.lbswarm=true 61 | - traefik.http.routers.whoami.middlewares=ondemand-whoami@docker 62 | - traefik.http.routers.whoami.rule=PathPrefix(`/whoami`) 63 | - traefik.http.services.whoami.loadbalancer.server.port=80 64 | 65 | 66 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | traefik: 5 | image: traefik 6 | entrypoint: sh -c "sed 's/$$TRAEFIK_PILOT_TOKEN/$TRAEFIK_PILOT_TOKEN/' /etc/traefik/traefik-template.yml > /etc/traefik/traefik.yml && traefik" 7 | ports: 8 | - "8000:80" 9 | - "8080:8080" 10 | volumes: 11 | - './traefik_dev.yml:/etc/traefik/traefik-template.yml' 12 | - '/var/run/docker.sock:/var/run/docker.sock' 13 | - '.:/plugins-local/src/github.com/acouvreur/traefik-ondemand-plugin' 14 | environment: 15 | - TRAEFIK_PILOT_TOKEN 16 | deploy: 17 | labels: 18 | - traefik.enable=true 19 | - traefik.http.services.traefik.loadbalancer.server.port=8080 20 | 21 | ondemand: 22 | image: ghcr.io/acouvreur/traefik-ondemand-service:1.7 23 | command: 24 | - --swarmMode=true 25 | volumes: 26 | - '/var/run/docker.sock:/var/run/docker.sock' 27 | 28 | whoami: 29 | image: containous/whoami 30 | deploy: 31 | replicas: 0 32 | labels: 33 | - traefik.enable=true 34 | # If you do not use the swarm load balancer, traefik will evict the service from its pool 35 | # as soon as the service is 0/0. If you do not set that, fallback to dynamic-config.yml file usage. 36 | - traefik.docker.lbswarm=true 37 | - traefik.http.middlewares.ondemand_whoami.plugin.traefik-ondemand-plugin.name=TRAEFIK_HACKATHON_whoami 38 | - traefik.http.middlewares.ondemand_whoami.plugin.traefik-ondemand-plugin.serviceurl=http://ondemand:10000 39 | - traefik.http.middlewares.ondemand_whoami.plugin.traefik-ondemand-plugin.timeout=1m 40 | - traefik.http.routers.whoami.middlewares=ondemand_whoami@docker 41 | - traefik.http.routers.whoami.rule=PathPrefix(`/whoami`) 42 | - traefik.http.services.whoami.loadbalancer.server.port=80 43 | 44 | nginx: 45 | image: nginx 46 | healthcheck: 47 | test: "true" 48 | interval: 1m30s 49 | timeout: 30s 50 | retries: 5 51 | start_period: 30s 52 | deploy: 53 | replicas: 0 54 | labels: 55 | - traefik.enable=true 56 | # If you do not use the swarm load balancer, traefik will evict the service from its pool 57 | # as soon as the service is 0/0. If you do not set that, fallback to dynamic-config.yml file usage. 58 | - traefik.docker.lbswarm=true 59 | - traefik.http.middlewares.ondemand_nginx.plugin.traefik-ondemand-plugin.name=TRAEFIK_HACKATHON_nginx 60 | - traefik.http.middlewares.ondemand_nginx.plugin.traefik-ondemand-plugin.serviceurl=http://ondemand:10000 61 | - traefik.http.middlewares.ondemand_nginx.plugin.traefik-ondemand-plugin.timeout=5m 62 | - traefik.http.middlewares.ondemand_nginx.plugin.traefik-ondemand-plugin.waitui=false 63 | - traefik.http.routers.nginx.middlewares=ondemand_nginx@docker 64 | - traefik.http.routers.nginx.rule=PathPrefix(`/nginx`) 65 | - traefik.http.services.nginx.loadbalancer.server.port=80 -------------------------------------------------------------------------------- /ondemand_test.go: -------------------------------------------------------------------------------- 1 | package traefik_ondemand_plugin 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestNewOndemand(t *testing.T) { 13 | testCases := []struct { 14 | desc string 15 | config *Config 16 | expectedError bool 17 | }{ 18 | { 19 | desc: "Invalid Config (no name)", 20 | config: &Config{ 21 | ServiceUrl: "http://ondemand:1000", 22 | Timeout: "1m", 23 | }, 24 | expectedError: true, 25 | }, 26 | { 27 | desc: "Invalid Config (empty names)", 28 | config: &Config{ 29 | Names: []string{}, 30 | ServiceUrl: "http://ondemand:1000", 31 | Timeout: "1m", 32 | }, 33 | expectedError: true, 34 | }, 35 | { 36 | desc: "Invalid Config (empty serviceUrl)", 37 | config: &Config{ 38 | Name: "whoami", 39 | ServiceUrl: "", 40 | Timeout: "1m", 41 | }, 42 | expectedError: true, 43 | }, 44 | { 45 | desc: "Invalid Config (name and names used simultaneously)", 46 | config: &Config{ 47 | Names: []string{ 48 | "whoami-1", "whoami-2", 49 | }, 50 | Name: "whoami", 51 | ServiceUrl: "http://ondemand:1000", 52 | WaitUi: true, 53 | BlockDelay: "1m", 54 | Timeout: "1m", 55 | }, 56 | expectedError: true, 57 | }, 58 | { 59 | desc: "valid Dynamic Config", 60 | config: &Config{ 61 | Name: "whoami", 62 | ServiceUrl: "http://ondemand:1000", 63 | WaitUi: true, 64 | Timeout: "1m", 65 | }, 66 | expectedError: false, 67 | }, 68 | { 69 | desc: "valid Blocking Config", 70 | config: &Config{ 71 | Name: "whoami", 72 | ServiceUrl: "http://ondemand:1000", 73 | WaitUi: false, 74 | BlockDelay: "1m", 75 | Timeout: "1m", 76 | }, 77 | expectedError: false, 78 | }, 79 | { 80 | desc: "valid Dynamic Multiple Config", 81 | config: &Config{ 82 | Names: []string{ 83 | "whoami-1", "whoami-2", 84 | }, 85 | ServiceUrl: "http://ondemand:1000", 86 | WaitUi: false, 87 | BlockDelay: "1m", 88 | Timeout: "1m", 89 | }, 90 | expectedError: false, 91 | }, 92 | { 93 | desc: "valid Blocking Multiple Config", 94 | config: &Config{ 95 | Names: []string{ 96 | "whoami-1", "whoami-2", 97 | }, 98 | ServiceUrl: "http://ondemand:1000", 99 | WaitUi: true, 100 | BlockDelay: "1m", 101 | Timeout: "1m", 102 | }, 103 | expectedError: false, 104 | }, 105 | } 106 | 107 | for _, test := range testCases { 108 | test := test 109 | t.Run(test.desc, func(t *testing.T) { 110 | t.Parallel() 111 | 112 | next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 113 | ondemand, err := New(context.Background(), next, test.config, "traefikTest") 114 | 115 | if test.expectedError { 116 | assert.Error(t, err) 117 | } else { 118 | require.NoError(t, err) 119 | assert.NotNil(t, ondemand) 120 | } 121 | }) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /ondemand.go: -------------------------------------------------------------------------------- 1 | package traefik_ondemand_plugin 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/acouvreur/traefik-ondemand-plugin/pkg/strategy" 10 | ) 11 | 12 | // Config the plugin configuration 13 | type Config struct { 14 | Name string `yaml:"name"` 15 | Names []string `yaml:"names"` 16 | ServiceUrl string `yaml:"serviceurl"` 17 | Timeout string `yaml:"timeout"` 18 | ErrorPage string `yaml:"errorpage"` 19 | LoadingPage string `yaml:"loadingpage"` 20 | WaitUi bool `yaml:"waitui"` 21 | DisplayName string `yaml:"displayname"` 22 | BlockDelay string `yaml:"blockdelay"` 23 | } 24 | 25 | // CreateConfig creates a config with its default values 26 | func CreateConfig() *Config { 27 | return &Config{ 28 | Timeout: "1m", 29 | WaitUi: true, 30 | BlockDelay: "1m", 31 | DisplayName: "", 32 | ErrorPage: "", 33 | LoadingPage: "", 34 | } 35 | } 36 | 37 | // Ondemand holds the request for the on demand service 38 | type Ondemand struct { 39 | strategy strategy.Strategy 40 | } 41 | 42 | func buildRequest(url string, name string, timeout time.Duration) (string, error) { 43 | request := fmt.Sprintf("%s?name=%s&timeout=%s", url, name, timeout.String()) 44 | return request, nil 45 | } 46 | 47 | // New function creates the configuration 48 | func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { 49 | if len(config.ServiceUrl) == 0 { 50 | return nil, fmt.Errorf("serviceurl cannot be null") 51 | } 52 | 53 | if len(config.Name) != 0 && len(config.Names) != 0 { 54 | return nil, fmt.Errorf("both name and names cannot be used simultaneously") 55 | } 56 | var serviceNames []string 57 | 58 | if len(config.Name) != 0 { 59 | serviceNames = append(serviceNames, config.Name) 60 | } else if len(config.Names) != 0 { 61 | serviceNames = config.Names 62 | } else { 63 | return nil, fmt.Errorf("both name and names cannot be null") 64 | } 65 | 66 | timeout, err := time.ParseDuration(config.Timeout) 67 | 68 | if err != nil { 69 | return nil, err 70 | } 71 | var requests []string 72 | 73 | for _, serviceName := range serviceNames { 74 | request, err := buildRequest(config.ServiceUrl, serviceName, timeout) 75 | 76 | if err != nil { 77 | return nil, fmt.Errorf("error while building request for %s", serviceName) 78 | } 79 | requests = append(requests, request) 80 | } 81 | 82 | strategy, err := config.getServeStrategy(requests, name, next, timeout) 83 | 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return &Ondemand{ 89 | strategy: strategy, 90 | }, nil 91 | } 92 | 93 | func (config *Config) getServeStrategy(requests []string, name string, next http.Handler, timeout time.Duration) (strategy.Strategy, error) { 94 | if config.WaitUi { 95 | return &strategy.DynamicStrategy{ 96 | Requests: requests, 97 | Name: name, 98 | Next: next, 99 | Timeout: timeout, 100 | DisplayName: config.DisplayName, 101 | ErrorPage: config.ErrorPage, 102 | LoadingPage: config.LoadingPage, 103 | }, nil 104 | } else { 105 | 106 | blockDelay, err := time.ParseDuration(config.BlockDelay) 107 | 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | return &strategy.BlockingStrategy{ 113 | Requests: requests, 114 | Name: name, 115 | Next: next, 116 | Timeout: timeout, 117 | BlockDelay: blockDelay, 118 | BlockCheckInterval: 1 * time.Second, 119 | }, nil 120 | } 121 | } 122 | 123 | // ServeHTTP retrieve the service status 124 | func (e *Ondemand) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 125 | e.strategy.ServeHTTP(rw, req) 126 | } 127 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/sorter.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011-2019 Canonical Ltd 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package yaml 17 | 18 | import ( 19 | "reflect" 20 | "unicode" 21 | ) 22 | 23 | type keyList []reflect.Value 24 | 25 | func (l keyList) Len() int { return len(l) } 26 | func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 27 | func (l keyList) Less(i, j int) bool { 28 | a := l[i] 29 | b := l[j] 30 | ak := a.Kind() 31 | bk := b.Kind() 32 | for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { 33 | a = a.Elem() 34 | ak = a.Kind() 35 | } 36 | for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { 37 | b = b.Elem() 38 | bk = b.Kind() 39 | } 40 | af, aok := keyFloat(a) 41 | bf, bok := keyFloat(b) 42 | if aok && bok { 43 | if af != bf { 44 | return af < bf 45 | } 46 | if ak != bk { 47 | return ak < bk 48 | } 49 | return numLess(a, b) 50 | } 51 | if ak != reflect.String || bk != reflect.String { 52 | return ak < bk 53 | } 54 | ar, br := []rune(a.String()), []rune(b.String()) 55 | digits := false 56 | for i := 0; i < len(ar) && i < len(br); i++ { 57 | if ar[i] == br[i] { 58 | digits = unicode.IsDigit(ar[i]) 59 | continue 60 | } 61 | al := unicode.IsLetter(ar[i]) 62 | bl := unicode.IsLetter(br[i]) 63 | if al && bl { 64 | return ar[i] < br[i] 65 | } 66 | if al || bl { 67 | if digits { 68 | return al 69 | } else { 70 | return bl 71 | } 72 | } 73 | var ai, bi int 74 | var an, bn int64 75 | if ar[i] == '0' || br[i] == '0' { 76 | for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- { 77 | if ar[j] != '0' { 78 | an = 1 79 | bn = 1 80 | break 81 | } 82 | } 83 | } 84 | for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { 85 | an = an*10 + int64(ar[ai]-'0') 86 | } 87 | for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { 88 | bn = bn*10 + int64(br[bi]-'0') 89 | } 90 | if an != bn { 91 | return an < bn 92 | } 93 | if ai != bi { 94 | return ai < bi 95 | } 96 | return ar[i] < br[i] 97 | } 98 | return len(ar) < len(br) 99 | } 100 | 101 | // keyFloat returns a float value for v if it is a number/bool 102 | // and whether it is a number/bool or not. 103 | func keyFloat(v reflect.Value) (f float64, ok bool) { 104 | switch v.Kind() { 105 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 106 | return float64(v.Int()), true 107 | case reflect.Float32, reflect.Float64: 108 | return v.Float(), true 109 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 110 | return float64(v.Uint()), true 111 | case reflect.Bool: 112 | if v.Bool() { 113 | return 1, true 114 | } 115 | return 0, true 116 | } 117 | return 0, false 118 | } 119 | 120 | // numLess returns whether a < b. 121 | // a and b must necessarily have the same kind. 122 | func numLess(a, b reflect.Value) bool { 123 | switch a.Kind() { 124 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 125 | return a.Int() < b.Int() 126 | case reflect.Float32, reflect.Float64: 127 | return a.Float() < b.Float() 128 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 129 | return a.Uint() < b.Uint() 130 | case reflect.Bool: 131 | return !a.Bool() && b.Bool() 132 | } 133 | panic("not a number") 134 | } 135 | -------------------------------------------------------------------------------- /pkg/strategy/strategy_test_cases.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | type OnDemandServiceResponses struct { 4 | body string 5 | status int 6 | } 7 | type ExpectedStatusForStrategy struct { 8 | dynamic int 9 | blocking int 10 | } 11 | 12 | type TestCase struct { 13 | desc string 14 | onDemandServiceResponses []OnDemandServiceResponses 15 | expected ExpectedStatusForStrategy 16 | } 17 | 18 | var SingleServiceTestCases = []TestCase{ 19 | { 20 | desc: "service is / keeps on starting", 21 | onDemandServiceResponses: GenerateServicesResponses(1, "starting"), 22 | expected: ExpectedStatusForStrategy{ 23 | dynamic: 202, 24 | blocking: 503, 25 | }, 26 | }, 27 | { 28 | desc: "service is started", 29 | onDemandServiceResponses: GenerateServicesResponses(1, "started"), 30 | expected: ExpectedStatusForStrategy{ 31 | dynamic: 200, 32 | blocking: 200, 33 | }, 34 | }, 35 | { 36 | desc: "ondemand service is in error", 37 | onDemandServiceResponses: GenerateServicesResponses(1, "error"), 38 | expected: ExpectedStatusForStrategy{ 39 | dynamic: 500, 40 | blocking: 500, 41 | }, 42 | }, 43 | } 44 | 45 | func GenerateServicesResponses(count int, serviceBody string) []OnDemandServiceResponses { 46 | responses := make([]OnDemandServiceResponses, count) 47 | for i := 0; i < count; i++ { 48 | if serviceBody == "starting" || serviceBody == "started" { 49 | responses[i] = OnDemandServiceResponses{ 50 | body: serviceBody, 51 | status: 200, 52 | } 53 | } else { 54 | responses[i] = OnDemandServiceResponses{ 55 | body: serviceBody, 56 | status: 503, 57 | } 58 | } 59 | } 60 | return responses 61 | } 62 | 63 | var MultipleServicesTestCases = []TestCase{ 64 | { 65 | desc: "all services are starting", 66 | onDemandServiceResponses: GenerateServicesResponses(5, "starting"), 67 | expected: ExpectedStatusForStrategy{ 68 | dynamic: 202, 69 | blocking: 503, 70 | }, 71 | }, 72 | { 73 | desc: "one started others are starting", 74 | onDemandServiceResponses: append(GenerateServicesResponses(1, "starting"), GenerateServicesResponses(4, "started")...), 75 | expected: ExpectedStatusForStrategy{ 76 | dynamic: 202, 77 | blocking: 503, 78 | }, 79 | }, 80 | { 81 | desc: "one starting others are started", 82 | onDemandServiceResponses: append(GenerateServicesResponses(4, "starting"), GenerateServicesResponses(1, "started")...), 83 | expected: ExpectedStatusForStrategy{ 84 | dynamic: 202, 85 | blocking: 503, 86 | }, 87 | }, 88 | { 89 | desc: "one errored others are starting", 90 | onDemandServiceResponses: append( 91 | GenerateServicesResponses(2, "starting"), 92 | append( 93 | GenerateServicesResponses(1, "error"), 94 | GenerateServicesResponses(2, "starting")..., 95 | )..., 96 | ), 97 | expected: ExpectedStatusForStrategy{ 98 | dynamic: 500, 99 | blocking: 500, 100 | }, 101 | }, 102 | { 103 | desc: "one errored others are started", 104 | onDemandServiceResponses: append( 105 | GenerateServicesResponses(1, "error"), 106 | GenerateServicesResponses(4, "started")..., 107 | ), 108 | expected: ExpectedStatusForStrategy{ 109 | dynamic: 500, 110 | blocking: 500, 111 | }, 112 | }, 113 | { 114 | desc: "one errored others are mix of starting / started", 115 | onDemandServiceResponses: append( 116 | GenerateServicesResponses(2, "started"), 117 | append( 118 | GenerateServicesResponses(1, "error"), 119 | GenerateServicesResponses(2, "starting")..., 120 | )..., 121 | ), 122 | expected: ExpectedStatusForStrategy{ 123 | dynamic: 500, 124 | blocking: 500, 125 | }, 126 | }, 127 | { 128 | desc: "all are started", 129 | onDemandServiceResponses: GenerateServicesResponses(5, "started"), 130 | expected: ExpectedStatusForStrategy{ 131 | dynamic: 200, 132 | blocking: 200, 133 | }, 134 | }, 135 | } 136 | -------------------------------------------------------------------------------- /pkg/pages/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ondemand - Error 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 139 | 140 | 141 | 142 |
143 | 145 |
146 | 147 |
148 |

Error loading {{ .Name }}.

149 | 150 |

There was an error loading your instance.

151 | 152 | 153 |
154 | {{ .Error }} 155 |
156 |
157 | 158 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/README.md: -------------------------------------------------------------------------------- 1 | # YAML support for the Go language 2 | 3 | Introduction 4 | ------------ 5 | 6 | The yaml package enables Go programs to comfortably encode and decode YAML 7 | values. It was developed within [Canonical](https://www.canonical.com) as 8 | part of the [juju](https://juju.ubuntu.com) project, and is based on a 9 | pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) 10 | C library to parse and generate YAML data quickly and reliably. 11 | 12 | Compatibility 13 | ------------- 14 | 15 | The yaml package supports most of YAML 1.2, but preserves some behavior 16 | from 1.1 for backwards compatibility. 17 | 18 | Specifically, as of v3 of the yaml package: 19 | 20 | - YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being 21 | decoded into a typed bool value. Otherwise they behave as a string. Booleans 22 | in YAML 1.2 are _true/false_ only. 23 | - Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_ 24 | as specified in YAML 1.2, because most parsers still use the old format. 25 | Octals in the _0o777_ format are supported though, so new files work. 26 | - Does not support base-60 floats. These are gone from YAML 1.2, and were 27 | actually never supported by this package as it's clearly a poor choice. 28 | 29 | and offers backwards 30 | compatibility with YAML 1.1 in some cases. 31 | 1.2, including support for 32 | anchors, tags, map merging, etc. Multi-document unmarshalling is not yet 33 | implemented, and base-60 floats from YAML 1.1 are purposefully not 34 | supported since they're a poor design and are gone in YAML 1.2. 35 | 36 | Installation and usage 37 | ---------------------- 38 | 39 | The import path for the package is *gopkg.in/yaml.v3*. 40 | 41 | To install it, run: 42 | 43 | go get gopkg.in/yaml.v3 44 | 45 | API documentation 46 | ----------------- 47 | 48 | If opened in a browser, the import path itself leads to the API documentation: 49 | 50 | - [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3) 51 | 52 | API stability 53 | ------------- 54 | 55 | The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in). 56 | 57 | 58 | License 59 | ------- 60 | 61 | The yaml package is licensed under the MIT and Apache License 2.0 licenses. 62 | Please see the LICENSE file for details. 63 | 64 | 65 | Example 66 | ------- 67 | 68 | ```Go 69 | package main 70 | 71 | import ( 72 | "fmt" 73 | "log" 74 | 75 | "gopkg.in/yaml.v3" 76 | ) 77 | 78 | var data = ` 79 | a: Easy! 80 | b: 81 | c: 2 82 | d: [3, 4] 83 | ` 84 | 85 | // Note: struct fields must be public in order for unmarshal to 86 | // correctly populate the data. 87 | type T struct { 88 | A string 89 | B struct { 90 | RenamedC int `yaml:"c"` 91 | D []int `yaml:",flow"` 92 | } 93 | } 94 | 95 | func main() { 96 | t := T{} 97 | 98 | err := yaml.Unmarshal([]byte(data), &t) 99 | if err != nil { 100 | log.Fatalf("error: %v", err) 101 | } 102 | fmt.Printf("--- t:\n%v\n\n", t) 103 | 104 | d, err := yaml.Marshal(&t) 105 | if err != nil { 106 | log.Fatalf("error: %v", err) 107 | } 108 | fmt.Printf("--- t dump:\n%s\n\n", string(d)) 109 | 110 | m := make(map[interface{}]interface{}) 111 | 112 | err = yaml.Unmarshal([]byte(data), &m) 113 | if err != nil { 114 | log.Fatalf("error: %v", err) 115 | } 116 | fmt.Printf("--- m:\n%v\n\n", m) 117 | 118 | d, err = yaml.Marshal(&m) 119 | if err != nil { 120 | log.Fatalf("error: %v", err) 121 | } 122 | fmt.Printf("--- m dump:\n%s\n\n", string(d)) 123 | } 124 | ``` 125 | 126 | This example will generate the following output: 127 | 128 | ``` 129 | --- t: 130 | {Easy! {2 [3 4]}} 131 | 132 | --- t dump: 133 | a: Easy! 134 | b: 135 | c: 2 136 | d: [3, 4] 137 | 138 | 139 | --- m: 140 | map[a:Easy! b:map[c:2 d:[3 4]]] 141 | 142 | --- m dump: 143 | a: Easy! 144 | b: 145 | c: 2 146 | d: 147 | - 3 148 | - 4 149 | ``` 150 | 151 | -------------------------------------------------------------------------------- /pkg/pages/error.go: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "path" 7 | ) 8 | 9 | var errorPage = ` 10 | 11 | 12 | 13 | Ondemand - Error 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 26 | 27 | 28 | 147 | 148 | 149 | 150 |
151 | 153 |
154 | 155 |
156 |

Error loading {{ .Name }}.

157 | 158 |

There was an error loading your instance.

159 | 160 | 161 |
162 | {{ .Error }} 163 |
164 |
165 | 166 | 170 | 171 | 172 | ` 173 | 174 | type ErrorData struct { 175 | Name string 176 | Error string 177 | } 178 | 179 | func GetErrorPage(template_path string, name string, e string) string { 180 | var tpl *template.Template 181 | var err error 182 | if template_path != "" { 183 | tpl, err = template.New(path.Base(template_path)).ParseFiles(template_path) 184 | } else { 185 | tpl, err = template.New("error").Parse(errorPage) 186 | } 187 | if err != nil { 188 | return err.Error() 189 | } 190 | 191 | b := bytes.Buffer{} 192 | err = tpl.Execute(&b, ErrorData{ 193 | Name: name, 194 | Error: e, 195 | }) 196 | if err != nil { 197 | return err.Error() 198 | } 199 | 200 | return b.String() 201 | } 202 | -------------------------------------------------------------------------------- /pkg/pages/loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ondemand - Loading 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 208 | 209 | 210 | 211 |
212 | 214 |
215 | 216 |
217 |

{{ .Name }} is loading...

218 |
219 | 220 |

Your instance is loading, and will be 221 | ready shortly.

222 | 223 | 224 |
225 | Your instance will shutdown automatically after {{ .Timeout }} of 226 | inactivity. 227 |
228 |
229 | 230 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/http_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "strings" 9 | ) 10 | 11 | // httpCode is a helper that returns HTTP code of the response. It returns -1 and 12 | // an error if building a new request fails. 13 | func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { 14 | w := httptest.NewRecorder() 15 | req, err := http.NewRequest(method, url, nil) 16 | if err != nil { 17 | return -1, err 18 | } 19 | req.URL.RawQuery = values.Encode() 20 | handler(w, req) 21 | return w.Code, nil 22 | } 23 | 24 | // HTTPSuccess asserts that a specified handler returns a success status code. 25 | // 26 | // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) 27 | // 28 | // Returns whether the assertion was successful (true) or not (false). 29 | func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 30 | if h, ok := t.(tHelper); ok { 31 | h.Helper() 32 | } 33 | code, err := httpCode(handler, method, url, values) 34 | if err != nil { 35 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 36 | } 37 | 38 | isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent 39 | if !isSuccessCode { 40 | Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code)) 41 | } 42 | 43 | return isSuccessCode 44 | } 45 | 46 | // HTTPRedirect asserts that a specified handler returns a redirect status code. 47 | // 48 | // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} 49 | // 50 | // Returns whether the assertion was successful (true) or not (false). 51 | func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 52 | if h, ok := t.(tHelper); ok { 53 | h.Helper() 54 | } 55 | code, err := httpCode(handler, method, url, values) 56 | if err != nil { 57 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 58 | } 59 | 60 | isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect 61 | if !isRedirectCode { 62 | Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code)) 63 | } 64 | 65 | return isRedirectCode 66 | } 67 | 68 | // HTTPError asserts that a specified handler returns an error status code. 69 | // 70 | // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} 71 | // 72 | // Returns whether the assertion was successful (true) or not (false). 73 | func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 74 | if h, ok := t.(tHelper); ok { 75 | h.Helper() 76 | } 77 | code, err := httpCode(handler, method, url, values) 78 | if err != nil { 79 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 80 | } 81 | 82 | isErrorCode := code >= http.StatusBadRequest 83 | if !isErrorCode { 84 | Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code)) 85 | } 86 | 87 | return isErrorCode 88 | } 89 | 90 | // HTTPStatusCode asserts that a specified handler returns a specified status code. 91 | // 92 | // assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) 93 | // 94 | // Returns whether the assertion was successful (true) or not (false). 95 | func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool { 96 | if h, ok := t.(tHelper); ok { 97 | h.Helper() 98 | } 99 | code, err := httpCode(handler, method, url, values) 100 | if err != nil { 101 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 102 | } 103 | 104 | successful := code == statuscode 105 | if !successful { 106 | Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code)) 107 | } 108 | 109 | return successful 110 | } 111 | 112 | // HTTPBody is a helper that returns HTTP body of the response. It returns 113 | // empty string if building a new request fails. 114 | func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { 115 | w := httptest.NewRecorder() 116 | req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) 117 | if err != nil { 118 | return "" 119 | } 120 | handler(w, req) 121 | return w.Body.String() 122 | } 123 | 124 | // HTTPBodyContains asserts that a specified handler returns a 125 | // body that contains a string. 126 | // 127 | // assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") 128 | // 129 | // Returns whether the assertion was successful (true) or not (false). 130 | func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { 131 | if h, ok := t.(tHelper); ok { 132 | h.Helper() 133 | } 134 | body := HTTPBody(handler, method, url, values) 135 | 136 | contains := strings.Contains(body, fmt.Sprint(str)) 137 | if !contains { 138 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 139 | } 140 | 141 | return contains 142 | } 143 | 144 | // HTTPBodyNotContains asserts that a specified handler returns a 145 | // body that does not contain a string. 146 | // 147 | // assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") 148 | // 149 | // Returns whether the assertion was successful (true) or not (false). 150 | func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { 151 | if h, ok := t.(tHelper); ok { 152 | h.Helper() 153 | } 154 | body := HTTPBody(handler, method, url, values) 155 | 156 | contains := strings.Contains(body, fmt.Sprint(str)) 157 | if contains { 158 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 159 | } 160 | 161 | return !contains 162 | } 163 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypass.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when the code is not running on Google App Engine, compiled by GopherJS, and 17 | // "-tags safe" is not added to the go build command line. The "disableunsafe" 18 | // tag is deprecated and thus should not be used. 19 | // +build !js,!appengine,!safe,!disableunsafe 20 | 21 | package spew 22 | 23 | import ( 24 | "reflect" 25 | "unsafe" 26 | ) 27 | 28 | const ( 29 | // UnsafeDisabled is a build-time constant which specifies whether or 30 | // not access to the unsafe package is available. 31 | UnsafeDisabled = false 32 | 33 | // ptrSize is the size of a pointer on the current arch. 34 | ptrSize = unsafe.Sizeof((*byte)(nil)) 35 | ) 36 | 37 | var ( 38 | // offsetPtr, offsetScalar, and offsetFlag are the offsets for the 39 | // internal reflect.Value fields. These values are valid before golang 40 | // commit ecccf07e7f9d which changed the format. The are also valid 41 | // after commit 82f48826c6c7 which changed the format again to mirror 42 | // the original format. Code in the init function updates these offsets 43 | // as necessary. 44 | offsetPtr = uintptr(ptrSize) 45 | offsetScalar = uintptr(0) 46 | offsetFlag = uintptr(ptrSize * 2) 47 | 48 | // flagKindWidth and flagKindShift indicate various bits that the 49 | // reflect package uses internally to track kind information. 50 | // 51 | // flagRO indicates whether or not the value field of a reflect.Value is 52 | // read-only. 53 | // 54 | // flagIndir indicates whether the value field of a reflect.Value is 55 | // the actual data or a pointer to the data. 56 | // 57 | // These values are valid before golang commit 90a7c3c86944 which 58 | // changed their positions. Code in the init function updates these 59 | // flags as necessary. 60 | flagKindWidth = uintptr(5) 61 | flagKindShift = uintptr(flagKindWidth - 1) 62 | flagRO = uintptr(1 << 0) 63 | flagIndir = uintptr(1 << 1) 64 | ) 65 | 66 | func init() { 67 | // Older versions of reflect.Value stored small integers directly in the 68 | // ptr field (which is named val in the older versions). Versions 69 | // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named 70 | // scalar for this purpose which unfortunately came before the flag 71 | // field, so the offset of the flag field is different for those 72 | // versions. 73 | // 74 | // This code constructs a new reflect.Value from a known small integer 75 | // and checks if the size of the reflect.Value struct indicates it has 76 | // the scalar field. When it does, the offsets are updated accordingly. 77 | vv := reflect.ValueOf(0xf00) 78 | if unsafe.Sizeof(vv) == (ptrSize * 4) { 79 | offsetScalar = ptrSize * 2 80 | offsetFlag = ptrSize * 3 81 | } 82 | 83 | // Commit 90a7c3c86944 changed the flag positions such that the low 84 | // order bits are the kind. This code extracts the kind from the flags 85 | // field and ensures it's the correct type. When it's not, the flag 86 | // order has been changed to the newer format, so the flags are updated 87 | // accordingly. 88 | upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) 89 | upfv := *(*uintptr)(upf) 90 | flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { 92 | flagKindShift = 0 93 | flagRO = 1 << 5 94 | flagIndir = 1 << 6 95 | 96 | // Commit adf9b30e5594 modified the flags to separate the 97 | // flagRO flag into two bits which specifies whether or not the 98 | // field is embedded. This causes flagIndir to move over a bit 99 | // and means that flagRO is the combination of either of the 100 | // original flagRO bit and the new bit. 101 | // 102 | // This code detects the change by extracting what used to be 103 | // the indirect bit to ensure it's set. When it's not, the flag 104 | // order has been changed to the newer format, so the flags are 105 | // updated accordingly. 106 | if upfv&flagIndir == 0 { 107 | flagRO = 3 << 5 108 | flagIndir = 1 << 7 109 | } 110 | } 111 | } 112 | 113 | // unsafeReflectValue converts the passed reflect.Value into a one that bypasses 114 | // the typical safety restrictions preventing access to unaddressable and 115 | // unexported data. It works by digging the raw pointer to the underlying 116 | // value out of the protected value and generating a new unprotected (unsafe) 117 | // reflect.Value to it. 118 | // 119 | // This allows us to check for implementations of the Stringer and error 120 | // interfaces to be used for pretty printing ordinarily unaddressable and 121 | // inaccessible values such as unexported struct fields. 122 | func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { 123 | indirects := 1 124 | vt := v.Type() 125 | upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) 126 | rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) 127 | if rvf&flagIndir != 0 { 128 | vt = reflect.PtrTo(v.Type()) 129 | indirects++ 130 | } else if offsetScalar != 0 { 131 | // The value is in the scalar field when it's not one of the 132 | // reference types. 133 | switch vt.Kind() { 134 | case reflect.Uintptr: 135 | case reflect.Chan: 136 | case reflect.Func: 137 | case reflect.Map: 138 | case reflect.Ptr: 139 | case reflect.UnsafePointer: 140 | default: 141 | upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + 142 | offsetScalar) 143 | } 144 | } 145 | 146 | pv := reflect.NewAt(vt, upv) 147 | rv = pv 148 | for i := 0; i < indirects; i++ { 149 | rv = rv.Elem() 150 | } 151 | return rv 152 | } 153 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/spew.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spew 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | ) 23 | 24 | // Errorf is a wrapper for fmt.Errorf that treats each argument as if it were 25 | // passed with a default Formatter interface returned by NewFormatter. It 26 | // returns the formatted string as a value that satisfies error. See 27 | // NewFormatter for formatting details. 28 | // 29 | // This function is shorthand for the following syntax: 30 | // 31 | // fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) 32 | func Errorf(format string, a ...interface{}) (err error) { 33 | return fmt.Errorf(format, convertArgs(a)...) 34 | } 35 | 36 | // Fprint is a wrapper for fmt.Fprint that treats each argument as if it were 37 | // passed with a default Formatter interface returned by NewFormatter. It 38 | // returns the number of bytes written and any write error encountered. See 39 | // NewFormatter for formatting details. 40 | // 41 | // This function is shorthand for the following syntax: 42 | // 43 | // fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) 44 | func Fprint(w io.Writer, a ...interface{}) (n int, err error) { 45 | return fmt.Fprint(w, convertArgs(a)...) 46 | } 47 | 48 | // Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were 49 | // passed with a default Formatter interface returned by NewFormatter. It 50 | // returns the number of bytes written and any write error encountered. See 51 | // NewFormatter for formatting details. 52 | // 53 | // This function is shorthand for the following syntax: 54 | // 55 | // fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) 56 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { 57 | return fmt.Fprintf(w, format, convertArgs(a)...) 58 | } 59 | 60 | // Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it 61 | // passed with a default Formatter interface returned by NewFormatter. See 62 | // NewFormatter for formatting details. 63 | // 64 | // This function is shorthand for the following syntax: 65 | // 66 | // fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) 67 | func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { 68 | return fmt.Fprintln(w, convertArgs(a)...) 69 | } 70 | 71 | // Print is a wrapper for fmt.Print that treats each argument as if it were 72 | // passed with a default Formatter interface returned by NewFormatter. It 73 | // returns the number of bytes written and any write error encountered. See 74 | // NewFormatter for formatting details. 75 | // 76 | // This function is shorthand for the following syntax: 77 | // 78 | // fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) 79 | func Print(a ...interface{}) (n int, err error) { 80 | return fmt.Print(convertArgs(a)...) 81 | } 82 | 83 | // Printf is a wrapper for fmt.Printf that treats each argument as if it were 84 | // passed with a default Formatter interface returned by NewFormatter. It 85 | // returns the number of bytes written and any write error encountered. See 86 | // NewFormatter for formatting details. 87 | // 88 | // This function is shorthand for the following syntax: 89 | // 90 | // fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) 91 | func Printf(format string, a ...interface{}) (n int, err error) { 92 | return fmt.Printf(format, convertArgs(a)...) 93 | } 94 | 95 | // Println is a wrapper for fmt.Println that treats each argument as if it were 96 | // passed with a default Formatter interface returned by NewFormatter. It 97 | // returns the number of bytes written and any write error encountered. See 98 | // NewFormatter for formatting details. 99 | // 100 | // This function is shorthand for the following syntax: 101 | // 102 | // fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) 103 | func Println(a ...interface{}) (n int, err error) { 104 | return fmt.Println(convertArgs(a)...) 105 | } 106 | 107 | // Sprint is a wrapper for fmt.Sprint that treats each argument as if it were 108 | // passed with a default Formatter interface returned by NewFormatter. It 109 | // returns the resulting string. See NewFormatter for formatting details. 110 | // 111 | // This function is shorthand for the following syntax: 112 | // 113 | // fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) 114 | func Sprint(a ...interface{}) string { 115 | return fmt.Sprint(convertArgs(a)...) 116 | } 117 | 118 | // Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were 119 | // passed with a default Formatter interface returned by NewFormatter. It 120 | // returns the resulting string. See NewFormatter for formatting details. 121 | // 122 | // This function is shorthand for the following syntax: 123 | // 124 | // fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) 125 | func Sprintf(format string, a ...interface{}) string { 126 | return fmt.Sprintf(format, convertArgs(a)...) 127 | } 128 | 129 | // Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it 130 | // were passed with a default Formatter interface returned by NewFormatter. It 131 | // returns the resulting string. See NewFormatter for formatting details. 132 | // 133 | // This function is shorthand for the following syntax: 134 | // 135 | // fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) 136 | func Sprintln(a ...interface{}) string { 137 | return fmt.Sprintln(convertArgs(a)...) 138 | } 139 | 140 | // convertArgs accepts a slice of arguments and returns a slice of the same 141 | // length with each argument converted to a default spew Formatter interface. 142 | func convertArgs(args []interface{}) (formatters []interface{}) { 143 | formatters = make([]interface{}, len(args)) 144 | for index, arg := range args { 145 | formatters[index] = NewFormatter(arg) 146 | } 147 | return formatters 148 | } 149 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/yamlprivateh.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011-2019 Canonical Ltd 3 | // Copyright (c) 2006-2010 Kirill Simonov 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | // of the Software, and to permit persons to whom the Software is furnished to do 10 | // so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package yaml 24 | 25 | const ( 26 | // The size of the input raw buffer. 27 | input_raw_buffer_size = 512 28 | 29 | // The size of the input buffer. 30 | // It should be possible to decode the whole raw buffer. 31 | input_buffer_size = input_raw_buffer_size * 3 32 | 33 | // The size of the output buffer. 34 | output_buffer_size = 128 35 | 36 | // The size of the output raw buffer. 37 | // It should be possible to encode the whole output buffer. 38 | output_raw_buffer_size = (output_buffer_size*2 + 2) 39 | 40 | // The size of other stacks and queues. 41 | initial_stack_size = 16 42 | initial_queue_size = 16 43 | initial_string_size = 16 44 | ) 45 | 46 | // Check if the character at the specified position is an alphabetical 47 | // character, a digit, '_', or '-'. 48 | func is_alpha(b []byte, i int) bool { 49 | return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' 50 | } 51 | 52 | // Check if the character at the specified position is a digit. 53 | func is_digit(b []byte, i int) bool { 54 | return b[i] >= '0' && b[i] <= '9' 55 | } 56 | 57 | // Get the value of a digit. 58 | func as_digit(b []byte, i int) int { 59 | return int(b[i]) - '0' 60 | } 61 | 62 | // Check if the character at the specified position is a hex-digit. 63 | func is_hex(b []byte, i int) bool { 64 | return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' 65 | } 66 | 67 | // Get the value of a hex-digit. 68 | func as_hex(b []byte, i int) int { 69 | bi := b[i] 70 | if bi >= 'A' && bi <= 'F' { 71 | return int(bi) - 'A' + 10 72 | } 73 | if bi >= 'a' && bi <= 'f' { 74 | return int(bi) - 'a' + 10 75 | } 76 | return int(bi) - '0' 77 | } 78 | 79 | // Check if the character is ASCII. 80 | func is_ascii(b []byte, i int) bool { 81 | return b[i] <= 0x7F 82 | } 83 | 84 | // Check if the character at the start of the buffer can be printed unescaped. 85 | func is_printable(b []byte, i int) bool { 86 | return ((b[i] == 0x0A) || // . == #x0A 87 | (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E 88 | (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF 89 | (b[i] > 0xC2 && b[i] < 0xED) || 90 | (b[i] == 0xED && b[i+1] < 0xA0) || 91 | (b[i] == 0xEE) || 92 | (b[i] == 0xEF && // #xE000 <= . <= #xFFFD 93 | !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF 94 | !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) 95 | } 96 | 97 | // Check if the character at the specified position is NUL. 98 | func is_z(b []byte, i int) bool { 99 | return b[i] == 0x00 100 | } 101 | 102 | // Check if the beginning of the buffer is a BOM. 103 | func is_bom(b []byte, i int) bool { 104 | return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF 105 | } 106 | 107 | // Check if the character at the specified position is space. 108 | func is_space(b []byte, i int) bool { 109 | return b[i] == ' ' 110 | } 111 | 112 | // Check if the character at the specified position is tab. 113 | func is_tab(b []byte, i int) bool { 114 | return b[i] == '\t' 115 | } 116 | 117 | // Check if the character at the specified position is blank (space or tab). 118 | func is_blank(b []byte, i int) bool { 119 | //return is_space(b, i) || is_tab(b, i) 120 | return b[i] == ' ' || b[i] == '\t' 121 | } 122 | 123 | // Check if the character at the specified position is a line break. 124 | func is_break(b []byte, i int) bool { 125 | return (b[i] == '\r' || // CR (#xD) 126 | b[i] == '\n' || // LF (#xA) 127 | b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) 128 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) 129 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) 130 | } 131 | 132 | func is_crlf(b []byte, i int) bool { 133 | return b[i] == '\r' && b[i+1] == '\n' 134 | } 135 | 136 | // Check if the character is a line break or NUL. 137 | func is_breakz(b []byte, i int) bool { 138 | //return is_break(b, i) || is_z(b, i) 139 | return ( 140 | // is_break: 141 | b[i] == '\r' || // CR (#xD) 142 | b[i] == '\n' || // LF (#xA) 143 | b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) 144 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) 145 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) 146 | // is_z: 147 | b[i] == 0) 148 | } 149 | 150 | // Check if the character is a line break, space, or NUL. 151 | func is_spacez(b []byte, i int) bool { 152 | //return is_space(b, i) || is_breakz(b, i) 153 | return ( 154 | // is_space: 155 | b[i] == ' ' || 156 | // is_breakz: 157 | b[i] == '\r' || // CR (#xD) 158 | b[i] == '\n' || // LF (#xA) 159 | b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) 160 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) 161 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) 162 | b[i] == 0) 163 | } 164 | 165 | // Check if the character is a line break, space, tab, or NUL. 166 | func is_blankz(b []byte, i int) bool { 167 | //return is_blank(b, i) || is_breakz(b, i) 168 | return ( 169 | // is_blank: 170 | b[i] == ' ' || b[i] == '\t' || 171 | // is_breakz: 172 | b[i] == '\r' || // CR (#xD) 173 | b[i] == '\n' || // LF (#xA) 174 | b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) 175 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) 176 | b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) 177 | b[i] == 0) 178 | } 179 | 180 | // Determine the width of the character. 181 | func width(b byte) int { 182 | // Don't replace these by a switch without first 183 | // confirming that it is being inlined. 184 | if b&0x80 == 0x00 { 185 | return 1 186 | } 187 | if b&0xE0 == 0xC0 { 188 | return 2 189 | } 190 | if b&0xF0 == 0xE0 { 191 | return 3 192 | } 193 | if b&0xF8 == 0xF0 { 194 | return 4 195 | } 196 | return 0 197 | 198 | } 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Traefik Ondemand Plugin 2 | 3 | **Project achieved, please see [acouvreur/sablier](https://github.com/acouvreur/sablier)** 4 | 5 | Traefik middleware to start containers on demand. 6 | 7 | ![Github Actions](https://img.shields.io/github/workflow/status/acouvreur/traefik-ondemand-plugin/Build?style=flat-square) 8 | ![Go Report](https://goreportcard.com/badge/github.com/acouvreur/traefik-ondemand-plugin?style=flat-square) 9 | ![Go Version](https://img.shields.io/github/go-mod/go-version/acouvreur/traefik-ondemand-plugin?style=flat-square) 10 | ![Latest Release](https://img.shields.io/github/release/acouvreur/traefik-ondemand-plugin/all.svg?style=flat-square) 11 | 12 | - [Traefik Ondemand Plugin](#traefik-ondemand-plugin) 13 | - [Features](#features) 14 | - [Usage](#usage) 15 | - [Plugin configuration](#plugin-configuration) 16 | - [Strategies](#strategies) 17 | - [Custom loading/error pages](#custom-loadingerror-pages) 18 | - [Traefik-Ondemand-Service](#traefik-ondemand-service) 19 | - [Examples](#examples) 20 | - [Development](#development) 21 | - [Authors](#authors) 22 | 23 | ## Features 24 | 25 | - Support for **Docker** containers 26 | - Support for **Docker swarm** mode, scale services 27 | - Support for **Kubernetes** Deployments and Statefulsets 28 | - Start your container/service on the first request 29 | - Automatic **scale to zero** after configured timeout upon last request the service received 30 | - Dynamic loading page (cloudflare or grafana cloud style) 31 | - Customize dynamic and loading pages 32 | 33 | ![Demo](./img/ondemand.gif) 34 | 35 | ## Usage 36 | 37 | ### Plugin configuration 38 | 39 | #### Strategies 40 | 41 | **Dynamic Strategy (default)** 42 | 43 | _Serve an HTML page that self reload._ 44 | 45 | ```yml 46 | testData: 47 | serviceUrl: http://ondemand:10000 48 | name: TRAEFIK_HACKATHON_whoami 49 | timeout: 1m 50 | waitui: true 51 | ``` 52 | 53 | **Blocking Strategy** 54 | 55 | Blocking strategy is enabled by setting `waitui` to `false`. 56 | 57 | Instead of displaying a self refreshing page, the request hangs until the service is ready to receive the request. 58 | 59 | The timeout is set by `blockdelay`. 60 | 61 | ```yml 62 | testData: 63 | serviceUrl: http://ondemand:10000 64 | name: TRAEFIK_HACKATHON_whoami 65 | timeout: 1m 66 | waitui: false 67 | blockdelay: 1m 68 | ``` 69 | 70 | *Typical use case: an API calling another API* 71 | 72 | #### Custom loading/error pages 73 | 74 | The `loadingpage` and `errorpage` keys in the plugin configuration can be used to override the default loading and error pages. 75 | 76 | The value should be a path where a template that can be parsed by Go's [html/template](https://pkg.go.dev/html/template) package can be found in the Traefik container. 77 | 78 | An example of both a loading page and an error page template can be found in the [pkg/pages/](pkg/pages/) directory in [loading.html](pkg/pages/loading.html) and [error.html](pkg/pages/error.html) respectively. 79 | 80 | The plugin will default to the built-in loading and error pages if these fields are omitted. 81 | 82 | You must include `` inside your html page to get auto refresh. 83 | 84 | **Example Configuration** 85 | 86 | ```yml 87 | testData: 88 | serviceUrl: http://ondemand:10000 89 | name: TRAEFIK_HACKATHON_whoami 90 | timeout: 1m 91 | waitui: false 92 | blockdelay: 1m 93 | loadingpage: /etc/traefik/plugins/traefik-ondemand-plugin/custompages/loading.html 94 | errorpage: /etc/traefik/plugins/traefik-ondemand-plugin/custompages/error.html 95 | ``` 96 | 97 | | Parameter | Type | Default | Required | Example | Description | 98 | | ------------- | --------------- | ------- | -------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | 99 | | `serviceUrl` | `string` | empty | yes | `http://ondemand:10000` | The docker container name, or the swarm service name | 100 | | `name` | `string` | empty | yes (except if `names` is set) | `TRAEFIK_HACKATHON_whoami` | The container/service/kubernetes resource to be stopped (docker ps docker service ls) | 101 | | `names` | `[]string` | [] | yes (except if `name` is set) | `[TRAEFIK_HACKATHON_whoami-1, TRAEFIK_HACKATHON_whoami-2]` | The containers/services to be stopped (docker ps docker service ls) | 102 | | `timeout` | `time.Duration` | `1m` | no | `1m30s` | The duration after which the container/service will be scaled down to 0 | 103 | | `waitui` | `bool` | `true` | no | `true` | Serves a self-refreshing html page when the service is scaled down to 0 | 104 | | `displayname` | `string` | `the middleware name` | no | `My App` | Serves a self-refreshing html page when the service is scaled down to 0 | 105 | | `blockdelay` | `time.Duration` | `1m` | no | `1m30s` | When `waitui` is `false`, wait for the service to be scaled up before `blockdelay` | 106 | | `loadingpage` | `string` | empty | no | `/etc/traefik/plugins/traefik-ondemand-plugin/custompages/loading.html` | The path in the traefik container for the **loading** page template | 107 | | `errorpage` | `string` | empty | no | `/etc/traefik/plugins/traefik-ondemand-plugin/custompages/error.html` | The path in the traefik container for the **error** page template | 108 | 109 | ### Traefik-Ondemand-Service 110 | 111 | The [traefik-ondemand-service](https://github.com/acouvreur/traefik-ondemand-service) must be used to bypass [Yaegi](https://github.com/traefik/yaegi) limitations. 112 | 113 | Yaegi is the interpreter used by Traefik to load plugin and run them at runtime. 114 | 115 | The docker library that interacts with the docker deamon uses `unsafe` which must be specified when instanciating Yaegi. Traefik doesn't, and probably never will by default. 116 | 117 | ## Examples 118 | 119 | - [Docker Classic](./examples/docker_classic/) 120 | - [Docker Swarm](./examples/docker_swarm/) 121 | - [Multiple Containers](./examples/multiple_containers/) 122 | - [Kubernetes](./examples/kubernetes/) 123 | 124 | ## Development 125 | 126 | `export TRAEFIK_PILOT_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` 127 | `docker stack deploy -c docker-compose.yml TRAEFIK_HACKATHON` 128 | 129 | ## Authors 130 | 131 | [Alexis Couvreur](https://www.linkedin.com/in/alexis-couvreur/) (left) 132 | [Alexandre Hiltcher](https://www.linkedin.com/in/alexandre-hiltcher/) (middle) 133 | [Matthias Schneider](https://www.linkedin.com/in/matthias-schneider-18831baa/) (right) 134 | 135 | ![Alexandre, Alexis and Matthias](./img/gophers-traefik.png) 136 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_compare.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type CompareType int 9 | 10 | const ( 11 | compareLess CompareType = iota - 1 12 | compareEqual 13 | compareGreater 14 | ) 15 | 16 | func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { 17 | switch kind { 18 | case reflect.Int: 19 | { 20 | intobj1 := obj1.(int) 21 | intobj2 := obj2.(int) 22 | if intobj1 > intobj2 { 23 | return compareGreater, true 24 | } 25 | if intobj1 == intobj2 { 26 | return compareEqual, true 27 | } 28 | if intobj1 < intobj2 { 29 | return compareLess, true 30 | } 31 | } 32 | case reflect.Int8: 33 | { 34 | int8obj1 := obj1.(int8) 35 | int8obj2 := obj2.(int8) 36 | if int8obj1 > int8obj2 { 37 | return compareGreater, true 38 | } 39 | if int8obj1 == int8obj2 { 40 | return compareEqual, true 41 | } 42 | if int8obj1 < int8obj2 { 43 | return compareLess, true 44 | } 45 | } 46 | case reflect.Int16: 47 | { 48 | int16obj1 := obj1.(int16) 49 | int16obj2 := obj2.(int16) 50 | if int16obj1 > int16obj2 { 51 | return compareGreater, true 52 | } 53 | if int16obj1 == int16obj2 { 54 | return compareEqual, true 55 | } 56 | if int16obj1 < int16obj2 { 57 | return compareLess, true 58 | } 59 | } 60 | case reflect.Int32: 61 | { 62 | int32obj1 := obj1.(int32) 63 | int32obj2 := obj2.(int32) 64 | if int32obj1 > int32obj2 { 65 | return compareGreater, true 66 | } 67 | if int32obj1 == int32obj2 { 68 | return compareEqual, true 69 | } 70 | if int32obj1 < int32obj2 { 71 | return compareLess, true 72 | } 73 | } 74 | case reflect.Int64: 75 | { 76 | int64obj1 := obj1.(int64) 77 | int64obj2 := obj2.(int64) 78 | if int64obj1 > int64obj2 { 79 | return compareGreater, true 80 | } 81 | if int64obj1 == int64obj2 { 82 | return compareEqual, true 83 | } 84 | if int64obj1 < int64obj2 { 85 | return compareLess, true 86 | } 87 | } 88 | case reflect.Uint: 89 | { 90 | uintobj1 := obj1.(uint) 91 | uintobj2 := obj2.(uint) 92 | if uintobj1 > uintobj2 { 93 | return compareGreater, true 94 | } 95 | if uintobj1 == uintobj2 { 96 | return compareEqual, true 97 | } 98 | if uintobj1 < uintobj2 { 99 | return compareLess, true 100 | } 101 | } 102 | case reflect.Uint8: 103 | { 104 | uint8obj1 := obj1.(uint8) 105 | uint8obj2 := obj2.(uint8) 106 | if uint8obj1 > uint8obj2 { 107 | return compareGreater, true 108 | } 109 | if uint8obj1 == uint8obj2 { 110 | return compareEqual, true 111 | } 112 | if uint8obj1 < uint8obj2 { 113 | return compareLess, true 114 | } 115 | } 116 | case reflect.Uint16: 117 | { 118 | uint16obj1 := obj1.(uint16) 119 | uint16obj2 := obj2.(uint16) 120 | if uint16obj1 > uint16obj2 { 121 | return compareGreater, true 122 | } 123 | if uint16obj1 == uint16obj2 { 124 | return compareEqual, true 125 | } 126 | if uint16obj1 < uint16obj2 { 127 | return compareLess, true 128 | } 129 | } 130 | case reflect.Uint32: 131 | { 132 | uint32obj1 := obj1.(uint32) 133 | uint32obj2 := obj2.(uint32) 134 | if uint32obj1 > uint32obj2 { 135 | return compareGreater, true 136 | } 137 | if uint32obj1 == uint32obj2 { 138 | return compareEqual, true 139 | } 140 | if uint32obj1 < uint32obj2 { 141 | return compareLess, true 142 | } 143 | } 144 | case reflect.Uint64: 145 | { 146 | uint64obj1 := obj1.(uint64) 147 | uint64obj2 := obj2.(uint64) 148 | if uint64obj1 > uint64obj2 { 149 | return compareGreater, true 150 | } 151 | if uint64obj1 == uint64obj2 { 152 | return compareEqual, true 153 | } 154 | if uint64obj1 < uint64obj2 { 155 | return compareLess, true 156 | } 157 | } 158 | case reflect.Float32: 159 | { 160 | float32obj1 := obj1.(float32) 161 | float32obj2 := obj2.(float32) 162 | if float32obj1 > float32obj2 { 163 | return compareGreater, true 164 | } 165 | if float32obj1 == float32obj2 { 166 | return compareEqual, true 167 | } 168 | if float32obj1 < float32obj2 { 169 | return compareLess, true 170 | } 171 | } 172 | case reflect.Float64: 173 | { 174 | float64obj1 := obj1.(float64) 175 | float64obj2 := obj2.(float64) 176 | if float64obj1 > float64obj2 { 177 | return compareGreater, true 178 | } 179 | if float64obj1 == float64obj2 { 180 | return compareEqual, true 181 | } 182 | if float64obj1 < float64obj2 { 183 | return compareLess, true 184 | } 185 | } 186 | case reflect.String: 187 | { 188 | stringobj1 := obj1.(string) 189 | stringobj2 := obj2.(string) 190 | if stringobj1 > stringobj2 { 191 | return compareGreater, true 192 | } 193 | if stringobj1 == stringobj2 { 194 | return compareEqual, true 195 | } 196 | if stringobj1 < stringobj2 { 197 | return compareLess, true 198 | } 199 | } 200 | } 201 | 202 | return compareEqual, false 203 | } 204 | 205 | // Greater asserts that the first element is greater than the second 206 | // 207 | // assert.Greater(t, 2, 1) 208 | // assert.Greater(t, float64(2), float64(1)) 209 | // assert.Greater(t, "b", "a") 210 | func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { 211 | return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs) 212 | } 213 | 214 | // GreaterOrEqual asserts that the first element is greater than or equal to the second 215 | // 216 | // assert.GreaterOrEqual(t, 2, 1) 217 | // assert.GreaterOrEqual(t, 2, 2) 218 | // assert.GreaterOrEqual(t, "b", "a") 219 | // assert.GreaterOrEqual(t, "b", "b") 220 | func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { 221 | return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs) 222 | } 223 | 224 | // Less asserts that the first element is less than the second 225 | // 226 | // assert.Less(t, 1, 2) 227 | // assert.Less(t, float64(1), float64(2)) 228 | // assert.Less(t, "a", "b") 229 | func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { 230 | return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs) 231 | } 232 | 233 | // LessOrEqual asserts that the first element is less than or equal to the second 234 | // 235 | // assert.LessOrEqual(t, 1, 2) 236 | // assert.LessOrEqual(t, 2, 2) 237 | // assert.LessOrEqual(t, "a", "b") 238 | // assert.LessOrEqual(t, "b", "b") 239 | func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { 240 | return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs) 241 | } 242 | 243 | func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { 244 | if h, ok := t.(tHelper); ok { 245 | h.Helper() 246 | } 247 | 248 | e1Kind := reflect.ValueOf(e1).Kind() 249 | e2Kind := reflect.ValueOf(e2).Kind() 250 | if e1Kind != e2Kind { 251 | return Fail(t, "Elements should be the same type", msgAndArgs...) 252 | } 253 | 254 | compareResult, isComparable := compare(e1, e2, e1Kind) 255 | if !isComparable { 256 | return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) 257 | } 258 | 259 | if !containsValue(allowedComparesResults, compareResult) { 260 | return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...) 261 | } 262 | 263 | return true 264 | } 265 | 266 | func containsValue(values []CompareType, value CompareType) bool { 267 | for _, v := range values { 268 | if v == value { 269 | return true 270 | } 271 | } 272 | 273 | return false 274 | } 275 | -------------------------------------------------------------------------------- /pkg/pages/loading.go: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "bytes" 5 | "path" 6 | 7 | "fmt" 8 | "html/template" 9 | "math" 10 | "time" 11 | ) 12 | 13 | var loadingPage = ` 14 | 15 | 16 | 17 | Ondemand - Loading 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | 220 | 221 | 222 | 223 |
224 | 226 |
227 | 228 |
229 |

{{ .Name }} is loading...

230 |
231 | 232 |

Your instance is loading, and will be 233 | ready shortly.

234 | 235 | 236 |
237 | Your instance will shutdown automatically after {{ .Timeout }} of 238 | inactivity. 239 |
240 |
241 | 242 | 246 | 247 | 248 | ` 249 | 250 | type LoadingData struct { 251 | Name string 252 | Timeout string 253 | } 254 | 255 | func GetLoadingPage(template_path string, name string, timeout time.Duration) string { 256 | var tpl *template.Template 257 | var err error 258 | if template_path != "" { 259 | tpl, err = template.New(path.Base(template_path)).ParseFiles(template_path) 260 | } else { 261 | tpl, err = template.New("loading").Parse(loadingPage) 262 | } 263 | if err != nil { 264 | return err.Error() 265 | } 266 | 267 | b := bytes.Buffer{} 268 | err = tpl.Execute(&b, LoadingData{ 269 | Name: name, 270 | Timeout: humanizeDuration(timeout), 271 | }) 272 | if err != nil { 273 | return err.Error() 274 | } 275 | 276 | return b.String() 277 | } 278 | 279 | // humanizeDuration humanizes time.Duration output to a meaningful value, 280 | // golang's default ``time.Duration`` output is badly formatted and unreadable. 281 | func humanizeDuration(duration time.Duration) string { 282 | if duration.Seconds() < 60.0 { 283 | return fmt.Sprintf("%d seconds", int64(duration.Seconds())) 284 | } 285 | if duration.Minutes() < 60.0 { 286 | remainingSeconds := math.Mod(duration.Seconds(), 60) 287 | if remainingSeconds > 0 { 288 | return fmt.Sprintf("%d minutes %d seconds", int64(duration.Minutes()), int64(remainingSeconds)) 289 | } 290 | return fmt.Sprintf("%d minutes", int64(duration.Minutes())) 291 | } 292 | if duration.Hours() < 24.0 { 293 | remainingMinutes := math.Mod(duration.Minutes(), 60) 294 | remainingSeconds := math.Mod(duration.Seconds(), 60) 295 | 296 | if remainingMinutes > 0 { 297 | if remainingSeconds > 0 { 298 | return fmt.Sprintf("%d hours %d minutes %d seconds", int64(duration.Hours()), int64(remainingMinutes), int64(remainingSeconds)) 299 | } 300 | return fmt.Sprintf("%d hours %d minutes", int64(duration.Hours()), int64(remainingMinutes)) 301 | } 302 | return fmt.Sprintf("%d hours", int64(duration.Hours())) 303 | } 304 | remainingHours := math.Mod(duration.Hours(), 24) 305 | remainingMinutes := math.Mod(duration.Minutes(), 60) 306 | remainingSeconds := math.Mod(duration.Seconds(), 60) 307 | return fmt.Sprintf("%d days %d hours %d minutes %d seconds", 308 | int64(duration.Hours()/24), int64(remainingHours), 309 | int64(remainingMinutes), int64(remainingSeconds)) 310 | } 311 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | Package spew implements a deep pretty printer for Go data structures to aid in 19 | debugging. 20 | 21 | A quick overview of the additional features spew provides over the built-in 22 | printing facilities for Go data types are as follows: 23 | 24 | * Pointers are dereferenced and followed 25 | * Circular data structures are detected and handled properly 26 | * Custom Stringer/error interfaces are optionally invoked, including 27 | on unexported types 28 | * Custom types which only implement the Stringer/error interfaces via 29 | a pointer receiver are optionally invoked when passing non-pointer 30 | variables 31 | * Byte arrays and slices are dumped like the hexdump -C command which 32 | includes offsets, byte values in hex, and ASCII output (only when using 33 | Dump style) 34 | 35 | There are two different approaches spew allows for dumping Go data structures: 36 | 37 | * Dump style which prints with newlines, customizable indentation, 38 | and additional debug information such as types and all pointer addresses 39 | used to indirect to the final value 40 | * A custom Formatter interface that integrates cleanly with the standard fmt 41 | package and replaces %v, %+v, %#v, and %#+v to provide inline printing 42 | similar to the default %v while providing the additional functionality 43 | outlined above and passing unsupported format verbs such as %x and %q 44 | along to fmt 45 | 46 | Quick Start 47 | 48 | This section demonstrates how to quickly get started with spew. See the 49 | sections below for further details on formatting and configuration options. 50 | 51 | To dump a variable with full newlines, indentation, type, and pointer 52 | information use Dump, Fdump, or Sdump: 53 | spew.Dump(myVar1, myVar2, ...) 54 | spew.Fdump(someWriter, myVar1, myVar2, ...) 55 | str := spew.Sdump(myVar1, myVar2, ...) 56 | 57 | Alternatively, if you would prefer to use format strings with a compacted inline 58 | printing style, use the convenience wrappers Printf, Fprintf, etc with 59 | %v (most compact), %+v (adds pointer addresses), %#v (adds types), or 60 | %#+v (adds types and pointer addresses): 61 | spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) 62 | spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 63 | spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) 64 | spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 65 | 66 | Configuration Options 67 | 68 | Configuration of spew is handled by fields in the ConfigState type. For 69 | convenience, all of the top-level functions use a global state available 70 | via the spew.Config global. 71 | 72 | It is also possible to create a ConfigState instance that provides methods 73 | equivalent to the top-level functions. This allows concurrent configuration 74 | options. See the ConfigState documentation for more details. 75 | 76 | The following configuration options are available: 77 | * Indent 78 | String to use for each indentation level for Dump functions. 79 | It is a single space by default. A popular alternative is "\t". 80 | 81 | * MaxDepth 82 | Maximum number of levels to descend into nested data structures. 83 | There is no limit by default. 84 | 85 | * DisableMethods 86 | Disables invocation of error and Stringer interface methods. 87 | Method invocation is enabled by default. 88 | 89 | * DisablePointerMethods 90 | Disables invocation of error and Stringer interface methods on types 91 | which only accept pointer receivers from non-pointer variables. 92 | Pointer method invocation is enabled by default. 93 | 94 | * DisablePointerAddresses 95 | DisablePointerAddresses specifies whether to disable the printing of 96 | pointer addresses. This is useful when diffing data structures in tests. 97 | 98 | * DisableCapacities 99 | DisableCapacities specifies whether to disable the printing of 100 | capacities for arrays, slices, maps and channels. This is useful when 101 | diffing data structures in tests. 102 | 103 | * ContinueOnMethod 104 | Enables recursion into types after invoking error and Stringer interface 105 | methods. Recursion after method invocation is disabled by default. 106 | 107 | * SortKeys 108 | Specifies map keys should be sorted before being printed. Use 109 | this to have a more deterministic, diffable output. Note that 110 | only native types (bool, int, uint, floats, uintptr and string) 111 | and types which implement error or Stringer interfaces are 112 | supported with other types sorted according to the 113 | reflect.Value.String() output which guarantees display 114 | stability. Natural map order is used by default. 115 | 116 | * SpewKeys 117 | Specifies that, as a last resort attempt, map keys should be 118 | spewed to strings and sorted by those strings. This is only 119 | considered if SortKeys is true. 120 | 121 | Dump Usage 122 | 123 | Simply call spew.Dump with a list of variables you want to dump: 124 | 125 | spew.Dump(myVar1, myVar2, ...) 126 | 127 | You may also call spew.Fdump if you would prefer to output to an arbitrary 128 | io.Writer. For example, to dump to standard error: 129 | 130 | spew.Fdump(os.Stderr, myVar1, myVar2, ...) 131 | 132 | A third option is to call spew.Sdump to get the formatted output as a string: 133 | 134 | str := spew.Sdump(myVar1, myVar2, ...) 135 | 136 | Sample Dump Output 137 | 138 | See the Dump example for details on the setup of the types and variables being 139 | shown here. 140 | 141 | (main.Foo) { 142 | unexportedField: (*main.Bar)(0xf84002e210)({ 143 | flag: (main.Flag) flagTwo, 144 | data: (uintptr) 145 | }), 146 | ExportedField: (map[interface {}]interface {}) (len=1) { 147 | (string) (len=3) "one": (bool) true 148 | } 149 | } 150 | 151 | Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C 152 | command as shown. 153 | ([]uint8) (len=32 cap=32) { 154 | 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | 155 | 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| 156 | 00000020 31 32 |12| 157 | } 158 | 159 | Custom Formatter 160 | 161 | Spew provides a custom formatter that implements the fmt.Formatter interface 162 | so that it integrates cleanly with standard fmt package printing functions. The 163 | formatter is useful for inline printing of smaller data types similar to the 164 | standard %v format specifier. 165 | 166 | The custom formatter only responds to the %v (most compact), %+v (adds pointer 167 | addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb 168 | combinations. Any other verbs such as %x and %q will be sent to the the 169 | standard fmt package for formatting. In addition, the custom formatter ignores 170 | the width and precision arguments (however they will still work on the format 171 | specifiers not handled by the custom formatter). 172 | 173 | Custom Formatter Usage 174 | 175 | The simplest way to make use of the spew custom formatter is to call one of the 176 | convenience functions such as spew.Printf, spew.Println, or spew.Printf. The 177 | functions have syntax you are most likely already familiar with: 178 | 179 | spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) 180 | spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 181 | spew.Println(myVar, myVar2) 182 | spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) 183 | spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 184 | 185 | See the Index for the full list convenience functions. 186 | 187 | Sample Formatter Output 188 | 189 | Double pointer to a uint8: 190 | %v: <**>5 191 | %+v: <**>(0xf8400420d0->0xf8400420c8)5 192 | %#v: (**uint8)5 193 | %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 194 | 195 | Pointer to circular struct with a uint8 field and a pointer to itself: 196 | %v: <*>{1 <*>} 197 | %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} 198 | %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} 199 | %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} 200 | 201 | See the Printf example for details on the setup of variables being shown 202 | here. 203 | 204 | Errors 205 | 206 | Since it is possible for custom Stringer/error interfaces to panic, spew 207 | detects them and handles them internally by printing the panic information 208 | inline with the output. Since spew is intended to provide deep pretty printing 209 | capabilities on structures, it intentionally does not return any errors. 210 | */ 211 | package spew 212 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/resolve.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011-2019 Canonical Ltd 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package yaml 17 | 18 | import ( 19 | "encoding/base64" 20 | "math" 21 | "regexp" 22 | "strconv" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | type resolveMapItem struct { 28 | value interface{} 29 | tag string 30 | } 31 | 32 | var resolveTable = make([]byte, 256) 33 | var resolveMap = make(map[string]resolveMapItem) 34 | 35 | func init() { 36 | t := resolveTable 37 | t[int('+')] = 'S' // Sign 38 | t[int('-')] = 'S' 39 | for _, c := range "0123456789" { 40 | t[int(c)] = 'D' // Digit 41 | } 42 | for _, c := range "yYnNtTfFoO~" { 43 | t[int(c)] = 'M' // In map 44 | } 45 | t[int('.')] = '.' // Float (potentially in map) 46 | 47 | var resolveMapList = []struct { 48 | v interface{} 49 | tag string 50 | l []string 51 | }{ 52 | {true, boolTag, []string{"true", "True", "TRUE"}}, 53 | {false, boolTag, []string{"false", "False", "FALSE"}}, 54 | {nil, nullTag, []string{"", "~", "null", "Null", "NULL"}}, 55 | {math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}}, 56 | {math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}}, 57 | {math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}}, 58 | {math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}}, 59 | {"<<", mergeTag, []string{"<<"}}, 60 | } 61 | 62 | m := resolveMap 63 | for _, item := range resolveMapList { 64 | for _, s := range item.l { 65 | m[s] = resolveMapItem{item.v, item.tag} 66 | } 67 | } 68 | } 69 | 70 | const ( 71 | nullTag = "!!null" 72 | boolTag = "!!bool" 73 | strTag = "!!str" 74 | intTag = "!!int" 75 | floatTag = "!!float" 76 | timestampTag = "!!timestamp" 77 | seqTag = "!!seq" 78 | mapTag = "!!map" 79 | binaryTag = "!!binary" 80 | mergeTag = "!!merge" 81 | ) 82 | 83 | var longTags = make(map[string]string) 84 | var shortTags = make(map[string]string) 85 | 86 | func init() { 87 | for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} { 88 | ltag := longTag(stag) 89 | longTags[stag] = ltag 90 | shortTags[ltag] = stag 91 | } 92 | } 93 | 94 | const longTagPrefix = "tag:yaml.org,2002:" 95 | 96 | func shortTag(tag string) string { 97 | if strings.HasPrefix(tag, longTagPrefix) { 98 | if stag, ok := shortTags[tag]; ok { 99 | return stag 100 | } 101 | return "!!" + tag[len(longTagPrefix):] 102 | } 103 | return tag 104 | } 105 | 106 | func longTag(tag string) string { 107 | if strings.HasPrefix(tag, "!!") { 108 | if ltag, ok := longTags[tag]; ok { 109 | return ltag 110 | } 111 | return longTagPrefix + tag[2:] 112 | } 113 | return tag 114 | } 115 | 116 | func resolvableTag(tag string) bool { 117 | switch tag { 118 | case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag: 119 | return true 120 | } 121 | return false 122 | } 123 | 124 | var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) 125 | 126 | func resolve(tag string, in string) (rtag string, out interface{}) { 127 | tag = shortTag(tag) 128 | if !resolvableTag(tag) { 129 | return tag, in 130 | } 131 | 132 | defer func() { 133 | switch tag { 134 | case "", rtag, strTag, binaryTag: 135 | return 136 | case floatTag: 137 | if rtag == intTag { 138 | switch v := out.(type) { 139 | case int64: 140 | rtag = floatTag 141 | out = float64(v) 142 | return 143 | case int: 144 | rtag = floatTag 145 | out = float64(v) 146 | return 147 | } 148 | } 149 | } 150 | failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) 151 | }() 152 | 153 | // Any data is accepted as a !!str or !!binary. 154 | // Otherwise, the prefix is enough of a hint about what it might be. 155 | hint := byte('N') 156 | if in != "" { 157 | hint = resolveTable[in[0]] 158 | } 159 | if hint != 0 && tag != strTag && tag != binaryTag { 160 | // Handle things we can lookup in a map. 161 | if item, ok := resolveMap[in]; ok { 162 | return item.tag, item.value 163 | } 164 | 165 | // Base 60 floats are a bad idea, were dropped in YAML 1.2, and 166 | // are purposefully unsupported here. They're still quoted on 167 | // the way out for compatibility with other parser, though. 168 | 169 | switch hint { 170 | case 'M': 171 | // We've already checked the map above. 172 | 173 | case '.': 174 | // Not in the map, so maybe a normal float. 175 | floatv, err := strconv.ParseFloat(in, 64) 176 | if err == nil { 177 | return floatTag, floatv 178 | } 179 | 180 | case 'D', 'S': 181 | // Int, float, or timestamp. 182 | // Only try values as a timestamp if the value is unquoted or there's an explicit 183 | // !!timestamp tag. 184 | if tag == "" || tag == timestampTag { 185 | t, ok := parseTimestamp(in) 186 | if ok { 187 | return timestampTag, t 188 | } 189 | } 190 | 191 | plain := strings.Replace(in, "_", "", -1) 192 | intv, err := strconv.ParseInt(plain, 0, 64) 193 | if err == nil { 194 | if intv == int64(int(intv)) { 195 | return intTag, int(intv) 196 | } else { 197 | return intTag, intv 198 | } 199 | } 200 | uintv, err := strconv.ParseUint(plain, 0, 64) 201 | if err == nil { 202 | return intTag, uintv 203 | } 204 | if yamlStyleFloat.MatchString(plain) { 205 | floatv, err := strconv.ParseFloat(plain, 64) 206 | if err == nil { 207 | return floatTag, floatv 208 | } 209 | } 210 | if strings.HasPrefix(plain, "0b") { 211 | intv, err := strconv.ParseInt(plain[2:], 2, 64) 212 | if err == nil { 213 | if intv == int64(int(intv)) { 214 | return intTag, int(intv) 215 | } else { 216 | return intTag, intv 217 | } 218 | } 219 | uintv, err := strconv.ParseUint(plain[2:], 2, 64) 220 | if err == nil { 221 | return intTag, uintv 222 | } 223 | } else if strings.HasPrefix(plain, "-0b") { 224 | intv, err := strconv.ParseInt("-"+plain[3:], 2, 64) 225 | if err == nil { 226 | if true || intv == int64(int(intv)) { 227 | return intTag, int(intv) 228 | } else { 229 | return intTag, intv 230 | } 231 | } 232 | } 233 | // Octals as introduced in version 1.2 of the spec. 234 | // Octals from the 1.1 spec, spelled as 0777, are still 235 | // decoded by default in v3 as well for compatibility. 236 | // May be dropped in v4 depending on how usage evolves. 237 | if strings.HasPrefix(plain, "0o") { 238 | intv, err := strconv.ParseInt(plain[2:], 8, 64) 239 | if err == nil { 240 | if intv == int64(int(intv)) { 241 | return intTag, int(intv) 242 | } else { 243 | return intTag, intv 244 | } 245 | } 246 | uintv, err := strconv.ParseUint(plain[2:], 8, 64) 247 | if err == nil { 248 | return intTag, uintv 249 | } 250 | } else if strings.HasPrefix(plain, "-0o") { 251 | intv, err := strconv.ParseInt("-"+plain[3:], 8, 64) 252 | if err == nil { 253 | if true || intv == int64(int(intv)) { 254 | return intTag, int(intv) 255 | } else { 256 | return intTag, intv 257 | } 258 | } 259 | } 260 | default: 261 | panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")") 262 | } 263 | } 264 | return strTag, in 265 | } 266 | 267 | // encodeBase64 encodes s as base64 that is broken up into multiple lines 268 | // as appropriate for the resulting length. 269 | func encodeBase64(s string) string { 270 | const lineLen = 70 271 | encLen := base64.StdEncoding.EncodedLen(len(s)) 272 | lines := encLen/lineLen + 1 273 | buf := make([]byte, encLen*2+lines) 274 | in := buf[0:encLen] 275 | out := buf[encLen:] 276 | base64.StdEncoding.Encode(in, []byte(s)) 277 | k := 0 278 | for i := 0; i < len(in); i += lineLen { 279 | j := i + lineLen 280 | if j > len(in) { 281 | j = len(in) 282 | } 283 | k += copy(out[k:], in[i:j]) 284 | if lines > 1 { 285 | out[k] = '\n' 286 | k++ 287 | } 288 | } 289 | return string(out[:k]) 290 | } 291 | 292 | // This is a subset of the formats allowed by the regular expression 293 | // defined at http://yaml.org/type/timestamp.html. 294 | var allowedTimestampFormats = []string{ 295 | "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. 296 | "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". 297 | "2006-1-2 15:4:5.999999999", // space separated with no time zone 298 | "2006-1-2", // date only 299 | // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" 300 | // from the set of examples. 301 | } 302 | 303 | // parseTimestamp parses s as a timestamp string and 304 | // returns the timestamp and reports whether it succeeded. 305 | // Timestamp formats are defined at http://yaml.org/type/timestamp.html 306 | func parseTimestamp(s string) (time.Time, bool) { 307 | // TODO write code to check all the formats supported by 308 | // http://yaml.org/type/timestamp.html instead of using time.Parse. 309 | 310 | // Quick check: all date formats start with YYYY-. 311 | i := 0 312 | for ; i < len(s); i++ { 313 | if c := s[i]; c < '0' || c > '9' { 314 | break 315 | } 316 | } 317 | if i != 4 || i == len(s) || s[i] != '-' { 318 | return time.Time{}, false 319 | } 320 | for _, format := range allowedTimestampFormats { 321 | if t, err := time.Parse(format, s); err == nil { 322 | return t, true 323 | } 324 | } 325 | return time.Time{}, false 326 | } 327 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spew 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io" 23 | "reflect" 24 | "sort" 25 | "strconv" 26 | ) 27 | 28 | // Some constants in the form of bytes to avoid string overhead. This mirrors 29 | // the technique used in the fmt package. 30 | var ( 31 | panicBytes = []byte("(PANIC=") 32 | plusBytes = []byte("+") 33 | iBytes = []byte("i") 34 | trueBytes = []byte("true") 35 | falseBytes = []byte("false") 36 | interfaceBytes = []byte("(interface {})") 37 | commaNewlineBytes = []byte(",\n") 38 | newlineBytes = []byte("\n") 39 | openBraceBytes = []byte("{") 40 | openBraceNewlineBytes = []byte("{\n") 41 | closeBraceBytes = []byte("}") 42 | asteriskBytes = []byte("*") 43 | colonBytes = []byte(":") 44 | colonSpaceBytes = []byte(": ") 45 | openParenBytes = []byte("(") 46 | closeParenBytes = []byte(")") 47 | spaceBytes = []byte(" ") 48 | pointerChainBytes = []byte("->") 49 | nilAngleBytes = []byte("") 50 | maxNewlineBytes = []byte("\n") 51 | maxShortBytes = []byte("") 52 | circularBytes = []byte("") 53 | circularShortBytes = []byte("") 54 | invalidAngleBytes = []byte("") 55 | openBracketBytes = []byte("[") 56 | closeBracketBytes = []byte("]") 57 | percentBytes = []byte("%") 58 | precisionBytes = []byte(".") 59 | openAngleBytes = []byte("<") 60 | closeAngleBytes = []byte(">") 61 | openMapBytes = []byte("map[") 62 | closeMapBytes = []byte("]") 63 | lenEqualsBytes = []byte("len=") 64 | capEqualsBytes = []byte("cap=") 65 | ) 66 | 67 | // hexDigits is used to map a decimal value to a hex digit. 68 | var hexDigits = "0123456789abcdef" 69 | 70 | // catchPanic handles any panics that might occur during the handleMethods 71 | // calls. 72 | func catchPanic(w io.Writer, v reflect.Value) { 73 | if err := recover(); err != nil { 74 | w.Write(panicBytes) 75 | fmt.Fprintf(w, "%v", err) 76 | w.Write(closeParenBytes) 77 | } 78 | } 79 | 80 | // handleMethods attempts to call the Error and String methods on the underlying 81 | // type the passed reflect.Value represents and outputes the result to Writer w. 82 | // 83 | // It handles panics in any called methods by catching and displaying the error 84 | // as the formatted value. 85 | func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { 86 | // We need an interface to check if the type implements the error or 87 | // Stringer interface. However, the reflect package won't give us an 88 | // interface on certain things like unexported struct fields in order 89 | // to enforce visibility rules. We use unsafe, when it's available, 90 | // to bypass these restrictions since this package does not mutate the 91 | // values. 92 | if !v.CanInterface() { 93 | if UnsafeDisabled { 94 | return false 95 | } 96 | 97 | v = unsafeReflectValue(v) 98 | } 99 | 100 | // Choose whether or not to do error and Stringer interface lookups against 101 | // the base type or a pointer to the base type depending on settings. 102 | // Technically calling one of these methods with a pointer receiver can 103 | // mutate the value, however, types which choose to satisify an error or 104 | // Stringer interface with a pointer receiver should not be mutating their 105 | // state inside these interface methods. 106 | if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { 107 | v = unsafeReflectValue(v) 108 | } 109 | if v.CanAddr() { 110 | v = v.Addr() 111 | } 112 | 113 | // Is it an error or Stringer? 114 | switch iface := v.Interface().(type) { 115 | case error: 116 | defer catchPanic(w, v) 117 | if cs.ContinueOnMethod { 118 | w.Write(openParenBytes) 119 | w.Write([]byte(iface.Error())) 120 | w.Write(closeParenBytes) 121 | w.Write(spaceBytes) 122 | return false 123 | } 124 | 125 | w.Write([]byte(iface.Error())) 126 | return true 127 | 128 | case fmt.Stringer: 129 | defer catchPanic(w, v) 130 | if cs.ContinueOnMethod { 131 | w.Write(openParenBytes) 132 | w.Write([]byte(iface.String())) 133 | w.Write(closeParenBytes) 134 | w.Write(spaceBytes) 135 | return false 136 | } 137 | w.Write([]byte(iface.String())) 138 | return true 139 | } 140 | return false 141 | } 142 | 143 | // printBool outputs a boolean value as true or false to Writer w. 144 | func printBool(w io.Writer, val bool) { 145 | if val { 146 | w.Write(trueBytes) 147 | } else { 148 | w.Write(falseBytes) 149 | } 150 | } 151 | 152 | // printInt outputs a signed integer value to Writer w. 153 | func printInt(w io.Writer, val int64, base int) { 154 | w.Write([]byte(strconv.FormatInt(val, base))) 155 | } 156 | 157 | // printUint outputs an unsigned integer value to Writer w. 158 | func printUint(w io.Writer, val uint64, base int) { 159 | w.Write([]byte(strconv.FormatUint(val, base))) 160 | } 161 | 162 | // printFloat outputs a floating point value using the specified precision, 163 | // which is expected to be 32 or 64bit, to Writer w. 164 | func printFloat(w io.Writer, val float64, precision int) { 165 | w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) 166 | } 167 | 168 | // printComplex outputs a complex value using the specified float precision 169 | // for the real and imaginary parts to Writer w. 170 | func printComplex(w io.Writer, c complex128, floatPrecision int) { 171 | r := real(c) 172 | w.Write(openParenBytes) 173 | w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) 174 | i := imag(c) 175 | if i >= 0 { 176 | w.Write(plusBytes) 177 | } 178 | w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) 179 | w.Write(iBytes) 180 | w.Write(closeParenBytes) 181 | } 182 | 183 | // printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' 184 | // prefix to Writer w. 185 | func printHexPtr(w io.Writer, p uintptr) { 186 | // Null pointer. 187 | num := uint64(p) 188 | if num == 0 { 189 | w.Write(nilAngleBytes) 190 | return 191 | } 192 | 193 | // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix 194 | buf := make([]byte, 18) 195 | 196 | // It's simpler to construct the hex string right to left. 197 | base := uint64(16) 198 | i := len(buf) - 1 199 | for num >= base { 200 | buf[i] = hexDigits[num%base] 201 | num /= base 202 | i-- 203 | } 204 | buf[i] = hexDigits[num] 205 | 206 | // Add '0x' prefix. 207 | i-- 208 | buf[i] = 'x' 209 | i-- 210 | buf[i] = '0' 211 | 212 | // Strip unused leading bytes. 213 | buf = buf[i:] 214 | w.Write(buf) 215 | } 216 | 217 | // valuesSorter implements sort.Interface to allow a slice of reflect.Value 218 | // elements to be sorted. 219 | type valuesSorter struct { 220 | values []reflect.Value 221 | strings []string // either nil or same len and values 222 | cs *ConfigState 223 | } 224 | 225 | // newValuesSorter initializes a valuesSorter instance, which holds a set of 226 | // surrogate keys on which the data should be sorted. It uses flags in 227 | // ConfigState to decide if and how to populate those surrogate keys. 228 | func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { 229 | vs := &valuesSorter{values: values, cs: cs} 230 | if canSortSimply(vs.values[0].Kind()) { 231 | return vs 232 | } 233 | if !cs.DisableMethods { 234 | vs.strings = make([]string, len(values)) 235 | for i := range vs.values { 236 | b := bytes.Buffer{} 237 | if !handleMethods(cs, &b, vs.values[i]) { 238 | vs.strings = nil 239 | break 240 | } 241 | vs.strings[i] = b.String() 242 | } 243 | } 244 | if vs.strings == nil && cs.SpewKeys { 245 | vs.strings = make([]string, len(values)) 246 | for i := range vs.values { 247 | vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) 248 | } 249 | } 250 | return vs 251 | } 252 | 253 | // canSortSimply tests whether a reflect.Kind is a primitive that can be sorted 254 | // directly, or whether it should be considered for sorting by surrogate keys 255 | // (if the ConfigState allows it). 256 | func canSortSimply(kind reflect.Kind) bool { 257 | // This switch parallels valueSortLess, except for the default case. 258 | switch kind { 259 | case reflect.Bool: 260 | return true 261 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 262 | return true 263 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 264 | return true 265 | case reflect.Float32, reflect.Float64: 266 | return true 267 | case reflect.String: 268 | return true 269 | case reflect.Uintptr: 270 | return true 271 | case reflect.Array: 272 | return true 273 | } 274 | return false 275 | } 276 | 277 | // Len returns the number of values in the slice. It is part of the 278 | // sort.Interface implementation. 279 | func (s *valuesSorter) Len() int { 280 | return len(s.values) 281 | } 282 | 283 | // Swap swaps the values at the passed indices. It is part of the 284 | // sort.Interface implementation. 285 | func (s *valuesSorter) Swap(i, j int) { 286 | s.values[i], s.values[j] = s.values[j], s.values[i] 287 | if s.strings != nil { 288 | s.strings[i], s.strings[j] = s.strings[j], s.strings[i] 289 | } 290 | } 291 | 292 | // valueSortLess returns whether the first value should sort before the second 293 | // value. It is used by valueSorter.Less as part of the sort.Interface 294 | // implementation. 295 | func valueSortLess(a, b reflect.Value) bool { 296 | switch a.Kind() { 297 | case reflect.Bool: 298 | return !a.Bool() && b.Bool() 299 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 300 | return a.Int() < b.Int() 301 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 302 | return a.Uint() < b.Uint() 303 | case reflect.Float32, reflect.Float64: 304 | return a.Float() < b.Float() 305 | case reflect.String: 306 | return a.String() < b.String() 307 | case reflect.Uintptr: 308 | return a.Uint() < b.Uint() 309 | case reflect.Array: 310 | // Compare the contents of both arrays. 311 | l := a.Len() 312 | for i := 0; i < l; i++ { 313 | av := a.Index(i) 314 | bv := b.Index(i) 315 | if av.Interface() == bv.Interface() { 316 | continue 317 | } 318 | return valueSortLess(av, bv) 319 | } 320 | } 321 | return a.String() < b.String() 322 | } 323 | 324 | // Less returns whether the value at index i should sort before the 325 | // value at index j. It is part of the sort.Interface implementation. 326 | func (s *valuesSorter) Less(i, j int) bool { 327 | if s.strings == nil { 328 | return valueSortLess(s.values[i], s.values[j]) 329 | } 330 | return s.strings[i] < s.strings[j] 331 | } 332 | 333 | // sortValues is a sort function that handles both native types and any type that 334 | // can be converted to error or Stringer. Other inputs are sorted according to 335 | // their Value.String() value to ensure display stability. 336 | func sortValues(values []reflect.Value, cs *ConfigState) { 337 | if len(values) == 0 { 338 | return 339 | } 340 | sort.Sort(newValuesSorter(values, cs)) 341 | } 342 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/format.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spew 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "reflect" 23 | "strconv" 24 | "strings" 25 | ) 26 | 27 | // supportedFlags is a list of all the character flags supported by fmt package. 28 | const supportedFlags = "0-+# " 29 | 30 | // formatState implements the fmt.Formatter interface and contains information 31 | // about the state of a formatting operation. The NewFormatter function can 32 | // be used to get a new Formatter which can be used directly as arguments 33 | // in standard fmt package printing calls. 34 | type formatState struct { 35 | value interface{} 36 | fs fmt.State 37 | depth int 38 | pointers map[uintptr]int 39 | ignoreNextType bool 40 | cs *ConfigState 41 | } 42 | 43 | // buildDefaultFormat recreates the original format string without precision 44 | // and width information to pass in to fmt.Sprintf in the case of an 45 | // unrecognized type. Unless new types are added to the language, this 46 | // function won't ever be called. 47 | func (f *formatState) buildDefaultFormat() (format string) { 48 | buf := bytes.NewBuffer(percentBytes) 49 | 50 | for _, flag := range supportedFlags { 51 | if f.fs.Flag(int(flag)) { 52 | buf.WriteRune(flag) 53 | } 54 | } 55 | 56 | buf.WriteRune('v') 57 | 58 | format = buf.String() 59 | return format 60 | } 61 | 62 | // constructOrigFormat recreates the original format string including precision 63 | // and width information to pass along to the standard fmt package. This allows 64 | // automatic deferral of all format strings this package doesn't support. 65 | func (f *formatState) constructOrigFormat(verb rune) (format string) { 66 | buf := bytes.NewBuffer(percentBytes) 67 | 68 | for _, flag := range supportedFlags { 69 | if f.fs.Flag(int(flag)) { 70 | buf.WriteRune(flag) 71 | } 72 | } 73 | 74 | if width, ok := f.fs.Width(); ok { 75 | buf.WriteString(strconv.Itoa(width)) 76 | } 77 | 78 | if precision, ok := f.fs.Precision(); ok { 79 | buf.Write(precisionBytes) 80 | buf.WriteString(strconv.Itoa(precision)) 81 | } 82 | 83 | buf.WriteRune(verb) 84 | 85 | format = buf.String() 86 | return format 87 | } 88 | 89 | // unpackValue returns values inside of non-nil interfaces when possible and 90 | // ensures that types for values which have been unpacked from an interface 91 | // are displayed when the show types flag is also set. 92 | // This is useful for data types like structs, arrays, slices, and maps which 93 | // can contain varying types packed inside an interface. 94 | func (f *formatState) unpackValue(v reflect.Value) reflect.Value { 95 | if v.Kind() == reflect.Interface { 96 | f.ignoreNextType = false 97 | if !v.IsNil() { 98 | v = v.Elem() 99 | } 100 | } 101 | return v 102 | } 103 | 104 | // formatPtr handles formatting of pointers by indirecting them as necessary. 105 | func (f *formatState) formatPtr(v reflect.Value) { 106 | // Display nil if top level pointer is nil. 107 | showTypes := f.fs.Flag('#') 108 | if v.IsNil() && (!showTypes || f.ignoreNextType) { 109 | f.fs.Write(nilAngleBytes) 110 | return 111 | } 112 | 113 | // Remove pointers at or below the current depth from map used to detect 114 | // circular refs. 115 | for k, depth := range f.pointers { 116 | if depth >= f.depth { 117 | delete(f.pointers, k) 118 | } 119 | } 120 | 121 | // Keep list of all dereferenced pointers to possibly show later. 122 | pointerChain := make([]uintptr, 0) 123 | 124 | // Figure out how many levels of indirection there are by derferencing 125 | // pointers and unpacking interfaces down the chain while detecting circular 126 | // references. 127 | nilFound := false 128 | cycleFound := false 129 | indirects := 0 130 | ve := v 131 | for ve.Kind() == reflect.Ptr { 132 | if ve.IsNil() { 133 | nilFound = true 134 | break 135 | } 136 | indirects++ 137 | addr := ve.Pointer() 138 | pointerChain = append(pointerChain, addr) 139 | if pd, ok := f.pointers[addr]; ok && pd < f.depth { 140 | cycleFound = true 141 | indirects-- 142 | break 143 | } 144 | f.pointers[addr] = f.depth 145 | 146 | ve = ve.Elem() 147 | if ve.Kind() == reflect.Interface { 148 | if ve.IsNil() { 149 | nilFound = true 150 | break 151 | } 152 | ve = ve.Elem() 153 | } 154 | } 155 | 156 | // Display type or indirection level depending on flags. 157 | if showTypes && !f.ignoreNextType { 158 | f.fs.Write(openParenBytes) 159 | f.fs.Write(bytes.Repeat(asteriskBytes, indirects)) 160 | f.fs.Write([]byte(ve.Type().String())) 161 | f.fs.Write(closeParenBytes) 162 | } else { 163 | if nilFound || cycleFound { 164 | indirects += strings.Count(ve.Type().String(), "*") 165 | } 166 | f.fs.Write(openAngleBytes) 167 | f.fs.Write([]byte(strings.Repeat("*", indirects))) 168 | f.fs.Write(closeAngleBytes) 169 | } 170 | 171 | // Display pointer information depending on flags. 172 | if f.fs.Flag('+') && (len(pointerChain) > 0) { 173 | f.fs.Write(openParenBytes) 174 | for i, addr := range pointerChain { 175 | if i > 0 { 176 | f.fs.Write(pointerChainBytes) 177 | } 178 | printHexPtr(f.fs, addr) 179 | } 180 | f.fs.Write(closeParenBytes) 181 | } 182 | 183 | // Display dereferenced value. 184 | switch { 185 | case nilFound == true: 186 | f.fs.Write(nilAngleBytes) 187 | 188 | case cycleFound == true: 189 | f.fs.Write(circularShortBytes) 190 | 191 | default: 192 | f.ignoreNextType = true 193 | f.format(ve) 194 | } 195 | } 196 | 197 | // format is the main workhorse for providing the Formatter interface. It 198 | // uses the passed reflect value to figure out what kind of object we are 199 | // dealing with and formats it appropriately. It is a recursive function, 200 | // however circular data structures are detected and handled properly. 201 | func (f *formatState) format(v reflect.Value) { 202 | // Handle invalid reflect values immediately. 203 | kind := v.Kind() 204 | if kind == reflect.Invalid { 205 | f.fs.Write(invalidAngleBytes) 206 | return 207 | } 208 | 209 | // Handle pointers specially. 210 | if kind == reflect.Ptr { 211 | f.formatPtr(v) 212 | return 213 | } 214 | 215 | // Print type information unless already handled elsewhere. 216 | if !f.ignoreNextType && f.fs.Flag('#') { 217 | f.fs.Write(openParenBytes) 218 | f.fs.Write([]byte(v.Type().String())) 219 | f.fs.Write(closeParenBytes) 220 | } 221 | f.ignoreNextType = false 222 | 223 | // Call Stringer/error interfaces if they exist and the handle methods 224 | // flag is enabled. 225 | if !f.cs.DisableMethods { 226 | if (kind != reflect.Invalid) && (kind != reflect.Interface) { 227 | if handled := handleMethods(f.cs, f.fs, v); handled { 228 | return 229 | } 230 | } 231 | } 232 | 233 | switch kind { 234 | case reflect.Invalid: 235 | // Do nothing. We should never get here since invalid has already 236 | // been handled above. 237 | 238 | case reflect.Bool: 239 | printBool(f.fs, v.Bool()) 240 | 241 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 242 | printInt(f.fs, v.Int(), 10) 243 | 244 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 245 | printUint(f.fs, v.Uint(), 10) 246 | 247 | case reflect.Float32: 248 | printFloat(f.fs, v.Float(), 32) 249 | 250 | case reflect.Float64: 251 | printFloat(f.fs, v.Float(), 64) 252 | 253 | case reflect.Complex64: 254 | printComplex(f.fs, v.Complex(), 32) 255 | 256 | case reflect.Complex128: 257 | printComplex(f.fs, v.Complex(), 64) 258 | 259 | case reflect.Slice: 260 | if v.IsNil() { 261 | f.fs.Write(nilAngleBytes) 262 | break 263 | } 264 | fallthrough 265 | 266 | case reflect.Array: 267 | f.fs.Write(openBracketBytes) 268 | f.depth++ 269 | if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { 270 | f.fs.Write(maxShortBytes) 271 | } else { 272 | numEntries := v.Len() 273 | for i := 0; i < numEntries; i++ { 274 | if i > 0 { 275 | f.fs.Write(spaceBytes) 276 | } 277 | f.ignoreNextType = true 278 | f.format(f.unpackValue(v.Index(i))) 279 | } 280 | } 281 | f.depth-- 282 | f.fs.Write(closeBracketBytes) 283 | 284 | case reflect.String: 285 | f.fs.Write([]byte(v.String())) 286 | 287 | case reflect.Interface: 288 | // The only time we should get here is for nil interfaces due to 289 | // unpackValue calls. 290 | if v.IsNil() { 291 | f.fs.Write(nilAngleBytes) 292 | } 293 | 294 | case reflect.Ptr: 295 | // Do nothing. We should never get here since pointers have already 296 | // been handled above. 297 | 298 | case reflect.Map: 299 | // nil maps should be indicated as different than empty maps 300 | if v.IsNil() { 301 | f.fs.Write(nilAngleBytes) 302 | break 303 | } 304 | 305 | f.fs.Write(openMapBytes) 306 | f.depth++ 307 | if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { 308 | f.fs.Write(maxShortBytes) 309 | } else { 310 | keys := v.MapKeys() 311 | if f.cs.SortKeys { 312 | sortValues(keys, f.cs) 313 | } 314 | for i, key := range keys { 315 | if i > 0 { 316 | f.fs.Write(spaceBytes) 317 | } 318 | f.ignoreNextType = true 319 | f.format(f.unpackValue(key)) 320 | f.fs.Write(colonBytes) 321 | f.ignoreNextType = true 322 | f.format(f.unpackValue(v.MapIndex(key))) 323 | } 324 | } 325 | f.depth-- 326 | f.fs.Write(closeMapBytes) 327 | 328 | case reflect.Struct: 329 | numFields := v.NumField() 330 | f.fs.Write(openBraceBytes) 331 | f.depth++ 332 | if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { 333 | f.fs.Write(maxShortBytes) 334 | } else { 335 | vt := v.Type() 336 | for i := 0; i < numFields; i++ { 337 | if i > 0 { 338 | f.fs.Write(spaceBytes) 339 | } 340 | vtf := vt.Field(i) 341 | if f.fs.Flag('+') || f.fs.Flag('#') { 342 | f.fs.Write([]byte(vtf.Name)) 343 | f.fs.Write(colonBytes) 344 | } 345 | f.format(f.unpackValue(v.Field(i))) 346 | } 347 | } 348 | f.depth-- 349 | f.fs.Write(closeBraceBytes) 350 | 351 | case reflect.Uintptr: 352 | printHexPtr(f.fs, uintptr(v.Uint())) 353 | 354 | case reflect.UnsafePointer, reflect.Chan, reflect.Func: 355 | printHexPtr(f.fs, v.Pointer()) 356 | 357 | // There were not any other types at the time this code was written, but 358 | // fall back to letting the default fmt package handle it if any get added. 359 | default: 360 | format := f.buildDefaultFormat() 361 | if v.CanInterface() { 362 | fmt.Fprintf(f.fs, format, v.Interface()) 363 | } else { 364 | fmt.Fprintf(f.fs, format, v.String()) 365 | } 366 | } 367 | } 368 | 369 | // Format satisfies the fmt.Formatter interface. See NewFormatter for usage 370 | // details. 371 | func (f *formatState) Format(fs fmt.State, verb rune) { 372 | f.fs = fs 373 | 374 | // Use standard formatting for verbs that are not v. 375 | if verb != 'v' { 376 | format := f.constructOrigFormat(verb) 377 | fmt.Fprintf(fs, format, f.value) 378 | return 379 | } 380 | 381 | if f.value == nil { 382 | if fs.Flag('#') { 383 | fs.Write(interfaceBytes) 384 | } 385 | fs.Write(nilAngleBytes) 386 | return 387 | } 388 | 389 | f.format(reflect.ValueOf(f.value)) 390 | } 391 | 392 | // newFormatter is a helper function to consolidate the logic from the various 393 | // public methods which take varying config states. 394 | func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter { 395 | fs := &formatState{value: v, cs: cs} 396 | fs.pointers = make(map[uintptr]int) 397 | return fs 398 | } 399 | 400 | /* 401 | NewFormatter returns a custom formatter that satisfies the fmt.Formatter 402 | interface. As a result, it integrates cleanly with standard fmt package 403 | printing functions. The formatter is useful for inline printing of smaller data 404 | types similar to the standard %v format specifier. 405 | 406 | The custom formatter only responds to the %v (most compact), %+v (adds pointer 407 | addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb 408 | combinations. Any other verbs such as %x and %q will be sent to the the 409 | standard fmt package for formatting. In addition, the custom formatter ignores 410 | the width and precision arguments (however they will still work on the format 411 | specifiers not handled by the custom formatter). 412 | 413 | Typically this function shouldn't be called directly. It is much easier to make 414 | use of the custom formatter by calling one of the convenience functions such as 415 | Printf, Println, or Fprintf. 416 | */ 417 | func NewFormatter(v interface{}) fmt.Formatter { 418 | return newFormatter(&Config, v) 419 | } 420 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spew 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io" 23 | "os" 24 | ) 25 | 26 | // ConfigState houses the configuration options used by spew to format and 27 | // display values. There is a global instance, Config, that is used to control 28 | // all top-level Formatter and Dump functionality. Each ConfigState instance 29 | // provides methods equivalent to the top-level functions. 30 | // 31 | // The zero value for ConfigState provides no indentation. You would typically 32 | // want to set it to a space or a tab. 33 | // 34 | // Alternatively, you can use NewDefaultConfig to get a ConfigState instance 35 | // with default settings. See the documentation of NewDefaultConfig for default 36 | // values. 37 | type ConfigState struct { 38 | // Indent specifies the string to use for each indentation level. The 39 | // global config instance that all top-level functions use set this to a 40 | // single space by default. If you would like more indentation, you might 41 | // set this to a tab with "\t" or perhaps two spaces with " ". 42 | Indent string 43 | 44 | // MaxDepth controls the maximum number of levels to descend into nested 45 | // data structures. The default, 0, means there is no limit. 46 | // 47 | // NOTE: Circular data structures are properly detected, so it is not 48 | // necessary to set this value unless you specifically want to limit deeply 49 | // nested data structures. 50 | MaxDepth int 51 | 52 | // DisableMethods specifies whether or not error and Stringer interfaces are 53 | // invoked for types that implement them. 54 | DisableMethods bool 55 | 56 | // DisablePointerMethods specifies whether or not to check for and invoke 57 | // error and Stringer interfaces on types which only accept a pointer 58 | // receiver when the current type is not a pointer. 59 | // 60 | // NOTE: This might be an unsafe action since calling one of these methods 61 | // with a pointer receiver could technically mutate the value, however, 62 | // in practice, types which choose to satisify an error or Stringer 63 | // interface with a pointer receiver should not be mutating their state 64 | // inside these interface methods. As a result, this option relies on 65 | // access to the unsafe package, so it will not have any effect when 66 | // running in environments without access to the unsafe package such as 67 | // Google App Engine or with the "safe" build tag specified. 68 | DisablePointerMethods bool 69 | 70 | // DisablePointerAddresses specifies whether to disable the printing of 71 | // pointer addresses. This is useful when diffing data structures in tests. 72 | DisablePointerAddresses bool 73 | 74 | // DisableCapacities specifies whether to disable the printing of capacities 75 | // for arrays, slices, maps and channels. This is useful when diffing 76 | // data structures in tests. 77 | DisableCapacities bool 78 | 79 | // ContinueOnMethod specifies whether or not recursion should continue once 80 | // a custom error or Stringer interface is invoked. The default, false, 81 | // means it will print the results of invoking the custom error or Stringer 82 | // interface and return immediately instead of continuing to recurse into 83 | // the internals of the data type. 84 | // 85 | // NOTE: This flag does not have any effect if method invocation is disabled 86 | // via the DisableMethods or DisablePointerMethods options. 87 | ContinueOnMethod bool 88 | 89 | // SortKeys specifies map keys should be sorted before being printed. Use 90 | // this to have a more deterministic, diffable output. Note that only 91 | // native types (bool, int, uint, floats, uintptr and string) and types 92 | // that support the error or Stringer interfaces (if methods are 93 | // enabled) are supported, with other types sorted according to the 94 | // reflect.Value.String() output which guarantees display stability. 95 | SortKeys bool 96 | 97 | // SpewKeys specifies that, as a last resort attempt, map keys should 98 | // be spewed to strings and sorted by those strings. This is only 99 | // considered if SortKeys is true. 100 | SpewKeys bool 101 | } 102 | 103 | // Config is the active configuration of the top-level functions. 104 | // The configuration can be changed by modifying the contents of spew.Config. 105 | var Config = ConfigState{Indent: " "} 106 | 107 | // Errorf is a wrapper for fmt.Errorf that treats each argument as if it were 108 | // passed with a Formatter interface returned by c.NewFormatter. It returns 109 | // the formatted string as a value that satisfies error. See NewFormatter 110 | // for formatting details. 111 | // 112 | // This function is shorthand for the following syntax: 113 | // 114 | // fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b)) 115 | func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) { 116 | return fmt.Errorf(format, c.convertArgs(a)...) 117 | } 118 | 119 | // Fprint is a wrapper for fmt.Fprint that treats each argument as if it were 120 | // passed with a Formatter interface returned by c.NewFormatter. It returns 121 | // the number of bytes written and any write error encountered. See 122 | // NewFormatter for formatting details. 123 | // 124 | // This function is shorthand for the following syntax: 125 | // 126 | // fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b)) 127 | func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) { 128 | return fmt.Fprint(w, c.convertArgs(a)...) 129 | } 130 | 131 | // Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were 132 | // passed with a Formatter interface returned by c.NewFormatter. It returns 133 | // the number of bytes written and any write error encountered. See 134 | // NewFormatter for formatting details. 135 | // 136 | // This function is shorthand for the following syntax: 137 | // 138 | // fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b)) 139 | func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { 140 | return fmt.Fprintf(w, format, c.convertArgs(a)...) 141 | } 142 | 143 | // Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it 144 | // passed with a Formatter interface returned by c.NewFormatter. See 145 | // NewFormatter for formatting details. 146 | // 147 | // This function is shorthand for the following syntax: 148 | // 149 | // fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b)) 150 | func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { 151 | return fmt.Fprintln(w, c.convertArgs(a)...) 152 | } 153 | 154 | // Print is a wrapper for fmt.Print that treats each argument as if it were 155 | // passed with a Formatter interface returned by c.NewFormatter. It returns 156 | // the number of bytes written and any write error encountered. See 157 | // NewFormatter for formatting details. 158 | // 159 | // This function is shorthand for the following syntax: 160 | // 161 | // fmt.Print(c.NewFormatter(a), c.NewFormatter(b)) 162 | func (c *ConfigState) Print(a ...interface{}) (n int, err error) { 163 | return fmt.Print(c.convertArgs(a)...) 164 | } 165 | 166 | // Printf is a wrapper for fmt.Printf that treats each argument as if it were 167 | // passed with a Formatter interface returned by c.NewFormatter. It returns 168 | // the number of bytes written and any write error encountered. See 169 | // NewFormatter for formatting details. 170 | // 171 | // This function is shorthand for the following syntax: 172 | // 173 | // fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b)) 174 | func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) { 175 | return fmt.Printf(format, c.convertArgs(a)...) 176 | } 177 | 178 | // Println is a wrapper for fmt.Println that treats each argument as if it were 179 | // passed with a Formatter interface returned by c.NewFormatter. It returns 180 | // the number of bytes written and any write error encountered. See 181 | // NewFormatter for formatting details. 182 | // 183 | // This function is shorthand for the following syntax: 184 | // 185 | // fmt.Println(c.NewFormatter(a), c.NewFormatter(b)) 186 | func (c *ConfigState) Println(a ...interface{}) (n int, err error) { 187 | return fmt.Println(c.convertArgs(a)...) 188 | } 189 | 190 | // Sprint is a wrapper for fmt.Sprint that treats each argument as if it were 191 | // passed with a Formatter interface returned by c.NewFormatter. It returns 192 | // the resulting string. See NewFormatter for formatting details. 193 | // 194 | // This function is shorthand for the following syntax: 195 | // 196 | // fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b)) 197 | func (c *ConfigState) Sprint(a ...interface{}) string { 198 | return fmt.Sprint(c.convertArgs(a)...) 199 | } 200 | 201 | // Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were 202 | // passed with a Formatter interface returned by c.NewFormatter. It returns 203 | // the resulting string. See NewFormatter for formatting details. 204 | // 205 | // This function is shorthand for the following syntax: 206 | // 207 | // fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b)) 208 | func (c *ConfigState) Sprintf(format string, a ...interface{}) string { 209 | return fmt.Sprintf(format, c.convertArgs(a)...) 210 | } 211 | 212 | // Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it 213 | // were passed with a Formatter interface returned by c.NewFormatter. It 214 | // returns the resulting string. See NewFormatter for formatting details. 215 | // 216 | // This function is shorthand for the following syntax: 217 | // 218 | // fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b)) 219 | func (c *ConfigState) Sprintln(a ...interface{}) string { 220 | return fmt.Sprintln(c.convertArgs(a)...) 221 | } 222 | 223 | /* 224 | NewFormatter returns a custom formatter that satisfies the fmt.Formatter 225 | interface. As a result, it integrates cleanly with standard fmt package 226 | printing functions. The formatter is useful for inline printing of smaller data 227 | types similar to the standard %v format specifier. 228 | 229 | The custom formatter only responds to the %v (most compact), %+v (adds pointer 230 | addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb 231 | combinations. Any other verbs such as %x and %q will be sent to the the 232 | standard fmt package for formatting. In addition, the custom formatter ignores 233 | the width and precision arguments (however they will still work on the format 234 | specifiers not handled by the custom formatter). 235 | 236 | Typically this function shouldn't be called directly. It is much easier to make 237 | use of the custom formatter by calling one of the convenience functions such as 238 | c.Printf, c.Println, or c.Printf. 239 | */ 240 | func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter { 241 | return newFormatter(c, v) 242 | } 243 | 244 | // Fdump formats and displays the passed arguments to io.Writer w. It formats 245 | // exactly the same as Dump. 246 | func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) { 247 | fdump(c, w, a...) 248 | } 249 | 250 | /* 251 | Dump displays the passed parameters to standard out with newlines, customizable 252 | indentation, and additional debug information such as complete types and all 253 | pointer addresses used to indirect to the final value. It provides the 254 | following features over the built-in printing facilities provided by the fmt 255 | package: 256 | 257 | * Pointers are dereferenced and followed 258 | * Circular data structures are detected and handled properly 259 | * Custom Stringer/error interfaces are optionally invoked, including 260 | on unexported types 261 | * Custom types which only implement the Stringer/error interfaces via 262 | a pointer receiver are optionally invoked when passing non-pointer 263 | variables 264 | * Byte arrays and slices are dumped like the hexdump -C command which 265 | includes offsets, byte values in hex, and ASCII output 266 | 267 | The configuration options are controlled by modifying the public members 268 | of c. See ConfigState for options documentation. 269 | 270 | See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to 271 | get the formatted result as a string. 272 | */ 273 | func (c *ConfigState) Dump(a ...interface{}) { 274 | fdump(c, os.Stdout, a...) 275 | } 276 | 277 | // Sdump returns a string with the passed arguments formatted exactly the same 278 | // as Dump. 279 | func (c *ConfigState) Sdump(a ...interface{}) string { 280 | var buf bytes.Buffer 281 | fdump(c, &buf, a...) 282 | return buf.String() 283 | } 284 | 285 | // convertArgs accepts a slice of arguments and returns a slice of the same 286 | // length with each argument converted to a spew Formatter interface using 287 | // the ConfigState associated with s. 288 | func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) { 289 | formatters = make([]interface{}, len(args)) 290 | for index, arg := range args { 291 | formatters[index] = newFormatter(c, arg) 292 | } 293 | return formatters 294 | } 295 | 296 | // NewDefaultConfig returns a ConfigState with the following default settings. 297 | // 298 | // Indent: " " 299 | // MaxDepth: 0 300 | // DisableMethods: false 301 | // DisablePointerMethods: false 302 | // ContinueOnMethod: false 303 | // SortKeys: false 304 | func NewDefaultConfig() *ConfigState { 305 | return &ConfigState{Indent: " "} 306 | } 307 | -------------------------------------------------------------------------------- /vendor/gopkg.in/yaml.v3/readerc.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2011-2019 Canonical Ltd 3 | // Copyright (c) 2006-2010 Kirill Simonov 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | // of the Software, and to permit persons to whom the Software is furnished to do 10 | // so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package yaml 24 | 25 | import ( 26 | "io" 27 | ) 28 | 29 | // Set the reader error and return 0. 30 | func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { 31 | parser.error = yaml_READER_ERROR 32 | parser.problem = problem 33 | parser.problem_offset = offset 34 | parser.problem_value = value 35 | return false 36 | } 37 | 38 | // Byte order marks. 39 | const ( 40 | bom_UTF8 = "\xef\xbb\xbf" 41 | bom_UTF16LE = "\xff\xfe" 42 | bom_UTF16BE = "\xfe\xff" 43 | ) 44 | 45 | // Determine the input stream encoding by checking the BOM symbol. If no BOM is 46 | // found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. 47 | func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { 48 | // Ensure that we had enough bytes in the raw buffer. 49 | for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { 50 | if !yaml_parser_update_raw_buffer(parser) { 51 | return false 52 | } 53 | } 54 | 55 | // Determine the encoding. 56 | buf := parser.raw_buffer 57 | pos := parser.raw_buffer_pos 58 | avail := len(buf) - pos 59 | if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { 60 | parser.encoding = yaml_UTF16LE_ENCODING 61 | parser.raw_buffer_pos += 2 62 | parser.offset += 2 63 | } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { 64 | parser.encoding = yaml_UTF16BE_ENCODING 65 | parser.raw_buffer_pos += 2 66 | parser.offset += 2 67 | } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { 68 | parser.encoding = yaml_UTF8_ENCODING 69 | parser.raw_buffer_pos += 3 70 | parser.offset += 3 71 | } else { 72 | parser.encoding = yaml_UTF8_ENCODING 73 | } 74 | return true 75 | } 76 | 77 | // Update the raw buffer. 78 | func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { 79 | size_read := 0 80 | 81 | // Return if the raw buffer is full. 82 | if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { 83 | return true 84 | } 85 | 86 | // Return on EOF. 87 | if parser.eof { 88 | return true 89 | } 90 | 91 | // Move the remaining bytes in the raw buffer to the beginning. 92 | if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { 93 | copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) 94 | } 95 | parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] 96 | parser.raw_buffer_pos = 0 97 | 98 | // Call the read handler to fill the buffer. 99 | size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) 100 | parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] 101 | if err == io.EOF { 102 | parser.eof = true 103 | } else if err != nil { 104 | return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) 105 | } 106 | return true 107 | } 108 | 109 | // Ensure that the buffer contains at least `length` characters. 110 | // Return true on success, false on failure. 111 | // 112 | // The length is supposed to be significantly less that the buffer size. 113 | func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { 114 | if parser.read_handler == nil { 115 | panic("read handler must be set") 116 | } 117 | 118 | // [Go] This function was changed to guarantee the requested length size at EOF. 119 | // The fact we need to do this is pretty awful, but the description above implies 120 | // for that to be the case, and there are tests 121 | 122 | // If the EOF flag is set and the raw buffer is empty, do nothing. 123 | if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { 124 | // [Go] ACTUALLY! Read the documentation of this function above. 125 | // This is just broken. To return true, we need to have the 126 | // given length in the buffer. Not doing that means every single 127 | // check that calls this function to make sure the buffer has a 128 | // given length is Go) panicking; or C) accessing invalid memory. 129 | //return true 130 | } 131 | 132 | // Return if the buffer contains enough characters. 133 | if parser.unread >= length { 134 | return true 135 | } 136 | 137 | // Determine the input encoding if it is not known yet. 138 | if parser.encoding == yaml_ANY_ENCODING { 139 | if !yaml_parser_determine_encoding(parser) { 140 | return false 141 | } 142 | } 143 | 144 | // Move the unread characters to the beginning of the buffer. 145 | buffer_len := len(parser.buffer) 146 | if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { 147 | copy(parser.buffer, parser.buffer[parser.buffer_pos:]) 148 | buffer_len -= parser.buffer_pos 149 | parser.buffer_pos = 0 150 | } else if parser.buffer_pos == buffer_len { 151 | buffer_len = 0 152 | parser.buffer_pos = 0 153 | } 154 | 155 | // Open the whole buffer for writing, and cut it before returning. 156 | parser.buffer = parser.buffer[:cap(parser.buffer)] 157 | 158 | // Fill the buffer until it has enough characters. 159 | first := true 160 | for parser.unread < length { 161 | 162 | // Fill the raw buffer if necessary. 163 | if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { 164 | if !yaml_parser_update_raw_buffer(parser) { 165 | parser.buffer = parser.buffer[:buffer_len] 166 | return false 167 | } 168 | } 169 | first = false 170 | 171 | // Decode the raw buffer. 172 | inner: 173 | for parser.raw_buffer_pos != len(parser.raw_buffer) { 174 | var value rune 175 | var width int 176 | 177 | raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos 178 | 179 | // Decode the next character. 180 | switch parser.encoding { 181 | case yaml_UTF8_ENCODING: 182 | // Decode a UTF-8 character. Check RFC 3629 183 | // (http://www.ietf.org/rfc/rfc3629.txt) for more details. 184 | // 185 | // The following table (taken from the RFC) is used for 186 | // decoding. 187 | // 188 | // Char. number range | UTF-8 octet sequence 189 | // (hexadecimal) | (binary) 190 | // --------------------+------------------------------------ 191 | // 0000 0000-0000 007F | 0xxxxxxx 192 | // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx 193 | // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 194 | // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 195 | // 196 | // Additionally, the characters in the range 0xD800-0xDFFF 197 | // are prohibited as they are reserved for use with UTF-16 198 | // surrogate pairs. 199 | 200 | // Determine the length of the UTF-8 sequence. 201 | octet := parser.raw_buffer[parser.raw_buffer_pos] 202 | switch { 203 | case octet&0x80 == 0x00: 204 | width = 1 205 | case octet&0xE0 == 0xC0: 206 | width = 2 207 | case octet&0xF0 == 0xE0: 208 | width = 3 209 | case octet&0xF8 == 0xF0: 210 | width = 4 211 | default: 212 | // The leading octet is invalid. 213 | return yaml_parser_set_reader_error(parser, 214 | "invalid leading UTF-8 octet", 215 | parser.offset, int(octet)) 216 | } 217 | 218 | // Check if the raw buffer contains an incomplete character. 219 | if width > raw_unread { 220 | if parser.eof { 221 | return yaml_parser_set_reader_error(parser, 222 | "incomplete UTF-8 octet sequence", 223 | parser.offset, -1) 224 | } 225 | break inner 226 | } 227 | 228 | // Decode the leading octet. 229 | switch { 230 | case octet&0x80 == 0x00: 231 | value = rune(octet & 0x7F) 232 | case octet&0xE0 == 0xC0: 233 | value = rune(octet & 0x1F) 234 | case octet&0xF0 == 0xE0: 235 | value = rune(octet & 0x0F) 236 | case octet&0xF8 == 0xF0: 237 | value = rune(octet & 0x07) 238 | default: 239 | value = 0 240 | } 241 | 242 | // Check and decode the trailing octets. 243 | for k := 1; k < width; k++ { 244 | octet = parser.raw_buffer[parser.raw_buffer_pos+k] 245 | 246 | // Check if the octet is valid. 247 | if (octet & 0xC0) != 0x80 { 248 | return yaml_parser_set_reader_error(parser, 249 | "invalid trailing UTF-8 octet", 250 | parser.offset+k, int(octet)) 251 | } 252 | 253 | // Decode the octet. 254 | value = (value << 6) + rune(octet&0x3F) 255 | } 256 | 257 | // Check the length of the sequence against the value. 258 | switch { 259 | case width == 1: 260 | case width == 2 && value >= 0x80: 261 | case width == 3 && value >= 0x800: 262 | case width == 4 && value >= 0x10000: 263 | default: 264 | return yaml_parser_set_reader_error(parser, 265 | "invalid length of a UTF-8 sequence", 266 | parser.offset, -1) 267 | } 268 | 269 | // Check the range of the value. 270 | if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { 271 | return yaml_parser_set_reader_error(parser, 272 | "invalid Unicode character", 273 | parser.offset, int(value)) 274 | } 275 | 276 | case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: 277 | var low, high int 278 | if parser.encoding == yaml_UTF16LE_ENCODING { 279 | low, high = 0, 1 280 | } else { 281 | low, high = 1, 0 282 | } 283 | 284 | // The UTF-16 encoding is not as simple as one might 285 | // naively think. Check RFC 2781 286 | // (http://www.ietf.org/rfc/rfc2781.txt). 287 | // 288 | // Normally, two subsequent bytes describe a Unicode 289 | // character. However a special technique (called a 290 | // surrogate pair) is used for specifying character 291 | // values larger than 0xFFFF. 292 | // 293 | // A surrogate pair consists of two pseudo-characters: 294 | // high surrogate area (0xD800-0xDBFF) 295 | // low surrogate area (0xDC00-0xDFFF) 296 | // 297 | // The following formulas are used for decoding 298 | // and encoding characters using surrogate pairs: 299 | // 300 | // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) 301 | // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) 302 | // W1 = 110110yyyyyyyyyy 303 | // W2 = 110111xxxxxxxxxx 304 | // 305 | // where U is the character value, W1 is the high surrogate 306 | // area, W2 is the low surrogate area. 307 | 308 | // Check for incomplete UTF-16 character. 309 | if raw_unread < 2 { 310 | if parser.eof { 311 | return yaml_parser_set_reader_error(parser, 312 | "incomplete UTF-16 character", 313 | parser.offset, -1) 314 | } 315 | break inner 316 | } 317 | 318 | // Get the character. 319 | value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + 320 | (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) 321 | 322 | // Check for unexpected low surrogate area. 323 | if value&0xFC00 == 0xDC00 { 324 | return yaml_parser_set_reader_error(parser, 325 | "unexpected low surrogate area", 326 | parser.offset, int(value)) 327 | } 328 | 329 | // Check for a high surrogate area. 330 | if value&0xFC00 == 0xD800 { 331 | width = 4 332 | 333 | // Check for incomplete surrogate pair. 334 | if raw_unread < 4 { 335 | if parser.eof { 336 | return yaml_parser_set_reader_error(parser, 337 | "incomplete UTF-16 surrogate pair", 338 | parser.offset, -1) 339 | } 340 | break inner 341 | } 342 | 343 | // Get the next character. 344 | value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + 345 | (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) 346 | 347 | // Check for a low surrogate area. 348 | if value2&0xFC00 != 0xDC00 { 349 | return yaml_parser_set_reader_error(parser, 350 | "expected low surrogate area", 351 | parser.offset+2, int(value2)) 352 | } 353 | 354 | // Generate the value of the surrogate pair. 355 | value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) 356 | } else { 357 | width = 2 358 | } 359 | 360 | default: 361 | panic("impossible") 362 | } 363 | 364 | // Check if the character is in the allowed range: 365 | // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) 366 | // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) 367 | // | [#x10000-#x10FFFF] (32 bit) 368 | switch { 369 | case value == 0x09: 370 | case value == 0x0A: 371 | case value == 0x0D: 372 | case value >= 0x20 && value <= 0x7E: 373 | case value == 0x85: 374 | case value >= 0xA0 && value <= 0xD7FF: 375 | case value >= 0xE000 && value <= 0xFFFD: 376 | case value >= 0x10000 && value <= 0x10FFFF: 377 | default: 378 | return yaml_parser_set_reader_error(parser, 379 | "control characters are not allowed", 380 | parser.offset, int(value)) 381 | } 382 | 383 | // Move the raw pointers. 384 | parser.raw_buffer_pos += width 385 | parser.offset += width 386 | 387 | // Finally put the character into the buffer. 388 | if value <= 0x7F { 389 | // 0000 0000-0000 007F . 0xxxxxxx 390 | parser.buffer[buffer_len+0] = byte(value) 391 | buffer_len += 1 392 | } else if value <= 0x7FF { 393 | // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx 394 | parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) 395 | parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) 396 | buffer_len += 2 397 | } else if value <= 0xFFFF { 398 | // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx 399 | parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) 400 | parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) 401 | parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) 402 | buffer_len += 3 403 | } else { 404 | // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 405 | parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) 406 | parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) 407 | parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) 408 | parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) 409 | buffer_len += 4 410 | } 411 | 412 | parser.unread++ 413 | } 414 | 415 | // On EOF, put NUL into the buffer and return. 416 | if parser.eof { 417 | parser.buffer[buffer_len] = 0 418 | buffer_len++ 419 | parser.unread++ 420 | break 421 | } 422 | } 423 | // [Go] Read the documentation of this function above. To return true, 424 | // we need to have the given length in the buffer. Not doing that means 425 | // every single check that calls this function to make sure the buffer 426 | // has a given length is Go) panicking; or C) accessing invalid memory. 427 | // This happens here due to the EOF above breaking early. 428 | for buffer_len < length { 429 | parser.buffer[buffer_len] = 0 430 | buffer_len++ 431 | } 432 | parser.buffer = parser.buffer[:buffer_len] 433 | return true 434 | } 435 | --------------------------------------------------------------------------------