diff --git a/Makefile b/Makefile index 3d87489f9..29381af20 100644 --- a/Makefile +++ b/Makefile @@ -34,9 +34,13 @@ test-container: curl -sH "Authorization: Bearer $${TOKEN}" localhost:9898/token/validate | grep test push-container: + docker tag $(DOCKER_IMAGE_NAME):$(VERSION) $(DOCKER_IMAGE_NAME):latest docker push $(DOCKER_IMAGE_NAME):$(VERSION) + docker push $(DOCKER_IMAGE_NAME):latest docker tag $(DOCKER_IMAGE_NAME):$(VERSION) quay.io/$(DOCKER_IMAGE_NAME):$(VERSION) + docker tag $(DOCKER_IMAGE_NAME):$(VERSION) quay.io/$(DOCKER_IMAGE_NAME):latest docker push quay.io/$(DOCKER_IMAGE_NAME):$(VERSION) + docker push quay.io/$(DOCKER_IMAGE_NAME):latest version-set: @next="$(TAG)" && \ @@ -51,3 +55,7 @@ version-set: release: git tag $(VERSION) git push origin $(VERSION) + +swagger: + GO111MODULE=on go get github.com/swaggo/swag/cmd/swag + cd pkg/api && $$(go env GOPATH)/bin/swag init -g server.go \ No newline at end of file diff --git a/README.md b/README.md index 5e9987ceb..811a5604e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Specifications: * Structured logging with zap * 12-factor app with viper * Fault injection (random errors and latency) +* Swagger docs * Helm and Kustomize installers * End-to-End testing with Kubernetes Kind and Helm @@ -41,11 +42,14 @@ Web API: * `GET /store/{hash}` returns the content of the file /data/hash if exists * `GET /ws/echo` echos content via websockets `podcli ws ws://localhost:9898/ws/echo` * `GET /chunked/{seconds}` uses `transfer-encoding` type `chunked` to give a partial response and then waits for the specified period +* `GET /swagger.json` returns the API Swagger docs, used for Linkerd service profiling and Gloo routes discovery Web UI: ![podinfo-ui](https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/screens/podinfo-ui.png) +To access the Swagger UI open `/swagger/index.html` in a browser. + ### Guides * [Automated canary deployments with Flagger and Istio](https://medium.com/google-cloud/automated-canary-deployments-with-flagger-and-istio-ac747827f9d1) @@ -79,3 +83,9 @@ Kustomize: ```bash kubectl apply -k github.com/stefanprodan/podinfo//kustomize ``` + +Docker: + +```bash +docker run -dp 9898:9898 stefanprodan/podinfo +``` \ No newline at end of file diff --git a/charts/podinfo/Chart.yaml b/charts/podinfo/Chart.yaml index 02f58df26..eeb66f82b 100644 --- a/charts/podinfo/Chart.yaml +++ b/charts/podinfo/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v1 -version: 2.0.2 -appVersion: 2.0.2 +version: 2.1.0 +appVersion: 2.1.0 name: podinfo engine: gotpl description: Podinfo Helm chart for Kubernetes diff --git a/charts/podinfo/values.yaml b/charts/podinfo/values.yaml index 343559353..a05bf1c9c 100644 --- a/charts/podinfo/values.yaml +++ b/charts/podinfo/values.yaml @@ -12,7 +12,7 @@ faults: image: repository: quay.io/stefanprodan/podinfo - tag: 2.0.2 + tag: 2.1.0 pullPolicy: IfNotPresent service: diff --git a/go.mod b/go.mod index a600a0f5c..b210b184e 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.12 require ( github.com/BurntSushi/toml v0.3.1 // indirect + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/aws/aws-sdk-go v1.15.63 // indirect github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/chzyer/logex v1.1.10 // indirect github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fatih/color v1.7.0 github.com/fsnotify/fsnotify v1.4.7 @@ -31,7 +31,6 @@ require ( github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 // indirect github.com/pelletier/go-toml v1.2.0 // indirect - github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v0.8.0 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect @@ -42,12 +41,12 @@ require ( github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 // indirect github.com/spf13/pflag v1.0.2 github.com/spf13/viper v1.1.0 - github.com/stretchr/testify v1.3.0 // indirect + github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 // indirect + github.com/swaggo/http-swagger v0.0.0-20190614090009-c2865af9083e + github.com/swaggo/swag v1.6.2 github.com/ulikunitz/xz v0.5.4 // indirect go.uber.org/atomic v1.3.2 // indirect go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.9.1 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect - golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect - gopkg.in/yaml.v2 v2.2.1 // indirect ) diff --git a/go.sum b/go.sum index 599ce160f..0945564ee 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,13 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/aws/aws-sdk-go v1.15.63 h1:rPr7eEm/FSK23DoDKhbd9oLMYGT7JU9pkyfBUVOHXUo= github.com/aws/aws-sdk-go v1.15.63/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= @@ -21,6 +29,17 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= +github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= +github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= @@ -43,6 +62,8 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Ao github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= @@ -82,10 +103,19 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.1.0 h1:V7OZpY8i3C1x/pDmU0zNNlfVoDz112fSYvtWMjjS3f4= github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= +github.com/swaggo/http-swagger v0.0.0-20190614090009-c2865af9083e h1:m5sYJ43teIUlESuKRFQRRm7kqi6ExiYwVKfoXNuRgHU= +github.com/swaggo/http-swagger v0.0.0-20190614090009-c2865af9083e/go.mod h1:eycbshptIv+tqTMlLEaGC2noPNcetbrcYEelLafrIDI= +github.com/swaggo/swag v1.6.2 h1:WQMAtT/FmMBb7g0rAuHDhG3vvdtHKJ3WZ+Ssb0p4Y6E= +github.com/swaggo/swag v1.6.2/go.mod h1:YyZstMc22WYm6GEDx/CYWxq+faBbjQ5EqwQcrjREDBo= github.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU= github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= @@ -93,6 +123,8 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= @@ -101,6 +133,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468 h1:fTfk6GjmihJbK0mSUFgPPgYpsdmApQ86Mcd4GuKax9U= +golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= diff --git a/kustomize/deployment.yaml b/kustomize/deployment.yaml index 580c1a6b6..7af0e37ce 100644 --- a/kustomize/deployment.yaml +++ b/kustomize/deployment.yaml @@ -24,7 +24,7 @@ spec: spec: containers: - name: podinfod - image: quay.io/stefanprodan/podinfo:2.0.2 + image: quay.io/stefanprodan/podinfo:2.1.0 imagePullPolicy: IfNotPresent ports: - containerPort: 9898 diff --git a/pkg/api/chunked.go b/pkg/api/chunked.go index fdaf1e563..4d14d2954 100644 --- a/pkg/api/chunked.go +++ b/pkg/api/chunked.go @@ -9,6 +9,14 @@ import ( "github.com/gorilla/mux" ) +// Chunked godoc +// @Summary Chunked transfer encoding +// @Description uses transfer-encoding type chunked to give a partial response and then waits for the specified period +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /chunked/{seconds} [get] +// @Success 200 {object} api.MapResponse func (s *Server) chunkedHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/pkg/api/delay.go b/pkg/api/delay.go index 1a7269e2e..c85d70582 100644 --- a/pkg/api/delay.go +++ b/pkg/api/delay.go @@ -8,6 +8,14 @@ import ( "time" ) +// Delay godoc +// @Summary Delay +// @Description waits for the specified period +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /delay/{seconds} [get] +// @Success 200 {object} api.MapResponse func (s *Server) delayHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/pkg/api/docs/docs.go b/pkg/api/docs/docs.go new file mode 100644 index 000000000..457f423f2 --- /dev/null +++ b/pkg/api/docs/docs.go @@ -0,0 +1,594 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag at +// 2019-08-07 15:52:23.918713 +0300 EEST m=+0.023438272 + +package docs + +import ( + "bytes" + "encoding/json" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "Go microservice template for Kubernetes.", + "title": "Podinfo API", + "contact": { + "name": "Source Code", + "url": "https://github.com/stefanprodan/podinfo" + }, + "license": { + "name": "MIT License", + "url": "https://github.com/stefanprodan/podinfo/blob/master/LICENSE" + }, + "version": "2.0" + }, + "host": "localhost:9898", + "basePath": "/", + "paths": { + "/": { + "get": { + "description": "renders podinfo UI", + "produces": [ + "text/html" + ], + "tags": [ + "HTTP API" + ], + "summary": "Index", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/echo": { + "post": { + "description": "forwards the call to the backend service and echos the posted content", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Echo", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/api/info": { + "get": { + "description": "returns the runtime information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Runtime information", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.RuntimeResponse" + } + } + } + } + }, + "/chunked/{seconds}": { + "get": { + "description": "uses transfer-encoding type chunked to give a partial response and then waits for the specified period", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Chunked transfer encoding", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/delay/{seconds}": { + "get": { + "description": "waits for the specified period", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Delay", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/env": { + "get": { + "description": "returns the environment variables as a JSON array", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Environment", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.ArrayResponse" + } + } + } + } + }, + "/headers": { + "get": { + "description": "returns a JSON array with the request HTTP headers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Headers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.ArrayResponse" + } + } + } + } + }, + "/healthz": { + "get": { + "description": "used by Kubernetes liveness probe", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Liveness check", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/metrics": { + "get": { + "description": "returns HTTP requests duration and Go runtime metrics", + "produces": [ + "text/plain" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Prometheus metrics", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/panic": { + "get": { + "description": "crashes the process with exit code 255", + "tags": [ + "HTTP API" + ], + "summary": "Panic" + } + }, + "/readyz": { + "get": { + "description": "used by Kubernetes readiness probe", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Readiness check", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/readyz/disable": { + "post": { + "description": "signals the Kubernetes LB to stop sending requests to this instance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Disable ready state", + "responses": { + "202": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/readyz/enable": { + "post": { + "description": "signals the Kubernetes LB that this instance is ready to receive traffic", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Enable ready state", + "responses": { + "202": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/status/{code}": { + "get": { + "description": "sets the response status code to the specified code", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Status code", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/store": { + "post": { + "description": "writes the posted content to disk at /data/hash and returns the SHA1 hash of the content", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Upload file", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/store/{hash}": { + "get": { + "description": "returns the content of the file /data/hash if exists", + "consumes": [ + "application/json" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "HTTP API" + ], + "summary": "Download file", + "responses": { + "200": { + "description": "file", + "schema": { + "type": "string" + } + } + } + } + }, + "/token": { + "post": { + "description": "issues a JWT token valid for one minute", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Generate JWT token", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.TokenResponse" + } + } + } + } + }, + "/token/validate": { + "post": { + "description": "validates the JWT token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Validate JWT token", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.TokenValidationResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + } + } + } + }, + "/version": { + "get": { + "description": "returns podinfo version and git commit hash", + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Version", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/ws/echo": { + "post": { + "description": "echos content via websockets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Echo over websockets", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + } + }, + "definitions": { + "api.ArrayResponse": { + "type": "array", + "items": {} + }, + "api.MapResponse": { + "type": "object", + "additionalProperties": {} + }, + "api.RuntimeResponse": { + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "goarch": { + "type": "string" + }, + "goos": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "message": { + "type": "string" + }, + "num_cpu": { + "type": "string" + }, + "num_goroutine": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "runtime": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "api.TokenResponse": { + "type": "object", + "properties": { + "expires_at": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, + "api.TokenValidationResponse": { + "type": "object", + "properties": { + "expires_at": { + "type": "string" + }, + "token_name": { + "type": "string" + } + } + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{Schemes: []string{"http", "https"}} + +type s struct{} + +func (s *s) ReadDoc() string { + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, SwaggerInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/pkg/api/docs/swagger.json b/pkg/api/docs/swagger.json new file mode 100644 index 000000000..56f23ed92 --- /dev/null +++ b/pkg/api/docs/swagger.json @@ -0,0 +1,542 @@ +{ + "swagger": "2.0", + "info": { + "description": "Go microservice template for Kubernetes.", + "title": "Podinfo API", + "contact": { + "name": "Source Code", + "url": "https://github.com/stefanprodan/podinfo" + }, + "license": { + "name": "MIT License", + "url": "https://github.com/stefanprodan/podinfo/blob/master/LICENSE" + }, + "version": "2.0" + }, + "host": "localhost:9898", + "basePath": "/", + "paths": { + "/": { + "get": { + "description": "renders podinfo UI", + "produces": [ + "text/html" + ], + "tags": [ + "HTTP API" + ], + "summary": "Index", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/echo": { + "post": { + "description": "forwards the call to the backend service and echos the posted content", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Echo", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/api/info": { + "get": { + "description": "returns the runtime information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Runtime information", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.RuntimeResponse" + } + } + } + } + }, + "/chunked/{seconds}": { + "get": { + "description": "uses transfer-encoding type chunked to give a partial response and then waits for the specified period", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Chunked transfer encoding", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/delay/{seconds}": { + "get": { + "description": "waits for the specified period", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Delay", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/env": { + "get": { + "description": "returns the environment variables as a JSON array", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Environment", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.ArrayResponse" + } + } + } + } + }, + "/headers": { + "get": { + "description": "returns a JSON array with the request HTTP headers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Headers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.ArrayResponse" + } + } + } + } + }, + "/healthz": { + "get": { + "description": "used by Kubernetes liveness probe", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Liveness check", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/metrics": { + "get": { + "description": "returns HTTP requests duration and Go runtime metrics", + "produces": [ + "text/plain" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Prometheus metrics", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/panic": { + "get": { + "description": "crashes the process with exit code 255", + "tags": [ + "HTTP API" + ], + "summary": "Panic" + } + }, + "/readyz": { + "get": { + "description": "used by Kubernetes readiness probe", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Readiness check", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/readyz/disable": { + "post": { + "description": "signals the Kubernetes LB to stop sending requests to this instance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Disable ready state", + "responses": { + "202": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/readyz/enable": { + "post": { + "description": "signals the Kubernetes LB that this instance is ready to receive traffic", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Kubernetes" + ], + "summary": "Enable ready state", + "responses": { + "202": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/status/{code}": { + "get": { + "description": "sets the response status code to the specified code", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Status code", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/store": { + "post": { + "description": "writes the posted content to disk at /data/hash and returns the SHA1 hash of the content", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Upload file", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/store/{hash}": { + "get": { + "description": "returns the content of the file /data/hash if exists", + "consumes": [ + "application/json" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "HTTP API" + ], + "summary": "Download file", + "responses": { + "200": { + "description": "file", + "schema": { + "type": "string" + } + } + } + } + }, + "/token": { + "post": { + "description": "issues a JWT token valid for one minute", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Generate JWT token", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.TokenResponse" + } + } + } + } + }, + "/token/validate": { + "post": { + "description": "validates the JWT token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Validate JWT token", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.TokenValidationResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + } + } + } + }, + "/version": { + "get": { + "description": "returns podinfo version and git commit hash", + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Version", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + }, + "/ws/echo": { + "post": { + "description": "echos content via websockets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Echo over websockets", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "object", + "$ref": "#/definitions/api.MapResponse" + } + } + } + } + } + }, + "definitions": { + "api.ArrayResponse": { + "type": "array", + "items": {} + }, + "api.MapResponse": { + "type": "object", + "additionalProperties": {} + }, + "api.RuntimeResponse": { + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "goarch": { + "type": "string" + }, + "goos": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "message": { + "type": "string" + }, + "num_cpu": { + "type": "string" + }, + "num_goroutine": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "runtime": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "api.TokenResponse": { + "type": "object", + "properties": { + "expires_at": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, + "api.TokenValidationResponse": { + "type": "object", + "properties": { + "expires_at": { + "type": "string" + }, + "token_name": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/pkg/api/docs/swagger.yaml b/pkg/api/docs/swagger.yaml new file mode 100644 index 000000000..68cd5a931 --- /dev/null +++ b/pkg/api/docs/swagger.yaml @@ -0,0 +1,362 @@ +basePath: / +definitions: + api.ArrayResponse: + items: {} + type: array + api.MapResponse: + additionalProperties: {} + type: object + api.RuntimeResponse: + properties: + color: + type: string + goarch: + type: string + goos: + type: string + hostname: + type: string + message: + type: string + num_cpu: + type: string + num_goroutine: + type: string + revision: + type: string + runtime: + type: string + version: + type: string + type: object + api.TokenResponse: + properties: + expires_at: + type: string + token: + type: string + type: object + api.TokenValidationResponse: + properties: + expires_at: + type: string + token_name: + type: string + type: object +host: localhost:9898 +info: + contact: + name: Source Code + url: https://github.com/stefanprodan/podinfo + description: Go microservice template for Kubernetes. + license: + name: MIT License + url: https://github.com/stefanprodan/podinfo/blob/master/LICENSE + title: Podinfo API + version: "2.0" +paths: + /: + get: + description: renders podinfo UI + produces: + - text/html + responses: + "200": + description: OK + schema: + type: string + summary: Index + tags: + - HTTP API + /api/echo: + post: + consumes: + - application/json + description: forwards the call to the backend service and echos the posted content + produces: + - application/json + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/api.MapResponse' + type: object + summary: Echo + tags: + - HTTP API + /api/info: + get: + consumes: + - application/json + description: returns the runtime information + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.RuntimeResponse' + type: object + summary: Runtime information + tags: + - HTTP API + /chunked/{seconds}: + get: + consumes: + - application/json + description: uses transfer-encoding type chunked to give a partial response + and then waits for the specified period + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.MapResponse' + type: object + summary: Chunked transfer encoding + tags: + - HTTP API + /delay/{seconds}: + get: + consumes: + - application/json + description: waits for the specified period + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.MapResponse' + type: object + summary: Delay + tags: + - HTTP API + /env: + get: + consumes: + - application/json + description: returns the environment variables as a JSON array + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.ArrayResponse' + type: object + summary: Environment + tags: + - HTTP API + /headers: + get: + consumes: + - application/json + description: returns a JSON array with the request HTTP headers + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.ArrayResponse' + type: object + summary: Headers + tags: + - HTTP API + /healthz: + get: + consumes: + - application/json + description: used by Kubernetes liveness probe + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: Liveness check + tags: + - Kubernetes + /metrics: + get: + description: returns HTTP requests duration and Go runtime metrics + produces: + - text/plain + responses: + "200": + description: OK + schema: + type: string + summary: Prometheus metrics + tags: + - Kubernetes + /panic: + get: + description: crashes the process with exit code 255 + summary: Panic + tags: + - HTTP API + /readyz: + get: + consumes: + - application/json + description: used by Kubernetes readiness probe + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: Readiness check + tags: + - Kubernetes + /readyz/disable: + post: + consumes: + - application/json + description: signals the Kubernetes LB to stop sending requests to this instance + produces: + - application/json + responses: + "202": + description: OK + schema: + type: string + summary: Disable ready state + tags: + - Kubernetes + /readyz/enable: + post: + consumes: + - application/json + description: signals the Kubernetes LB that this instance is ready to receive + traffic + produces: + - application/json + responses: + "202": + description: OK + schema: + type: string + summary: Enable ready state + tags: + - Kubernetes + /status/{code}: + get: + consumes: + - application/json + description: sets the response status code to the specified code + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.MapResponse' + type: object + summary: Status code + tags: + - HTTP API + /store: + post: + consumes: + - application/json + description: writes the posted content to disk at /data/hash and returns the + SHA1 hash of the content + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.MapResponse' + type: object + summary: Upload file + tags: + - HTTP API + /store/{hash}: + get: + consumes: + - application/json + description: returns the content of the file /data/hash if exists + produces: + - text/plain + responses: + "200": + description: file + schema: + type: string + summary: Download file + tags: + - HTTP API + /token: + post: + consumes: + - application/json + description: issues a JWT token valid for one minute + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.TokenResponse' + type: object + summary: Generate JWT token + tags: + - HTTP API + /token/validate: + post: + consumes: + - application/json + description: validates the JWT token + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.TokenValidationResponse' + type: object + "401": + description: Unauthorized + schema: + type: string + summary: Validate JWT token + tags: + - HTTP API + /version: + get: + description: returns podinfo version and git commit hash + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.MapResponse' + type: object + summary: Version + tags: + - HTTP API + /ws/echo: + post: + consumes: + - application/json + description: echos content via websockets + produces: + - application/json + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/api.MapResponse' + type: object + summary: Echo over websockets + tags: + - HTTP API +swagger: "2.0" diff --git a/pkg/api/echo.go b/pkg/api/echo.go index b3e07dd0f..0eb269434 100644 --- a/pkg/api/echo.go +++ b/pkg/api/echo.go @@ -10,6 +10,14 @@ import ( "go.uber.org/zap" ) +// Echo godoc +// @Summary Echo +// @Description forwards the call to the backend service and echos the posted content +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /api/echo [post] +// @Success 202 {object} api.MapResponse func (s *Server) echoHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { diff --git a/pkg/api/echows.go b/pkg/api/echows.go index 7302c0e6f..bcf693c3f 100644 --- a/pkg/api/echows.go +++ b/pkg/api/echows.go @@ -11,6 +11,14 @@ import ( var wsCon = websocket.Upgrader{} +// EchoWS godoc +// @Summary Echo over websockets +// @Description echos content via websockets +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /ws/echo [post] +// @Success 202 {object} api.MapResponse // Test: go run ./cmd/podcli/* ws localhost:9898/ws/echo func (s *Server) echoWsHandler(w http.ResponseWriter, r *http.Request) { c, err := wsCon.Upgrade(w, r, nil) diff --git a/pkg/api/env.go b/pkg/api/env.go index 95384ff51..47363ffb4 100644 --- a/pkg/api/env.go +++ b/pkg/api/env.go @@ -6,6 +6,14 @@ import ( "os" ) +// Env godoc +// @Summary Environment +// @Description returns the environment variables as a JSON array +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /env [get] +// @Success 200 {object} api.ArrayResponse func (s *Server) envHandler(w http.ResponseWriter, r *http.Request) { s.JSONResponse(w, r, os.Environ()) } diff --git a/pkg/api/headers.go b/pkg/api/headers.go index 95d73c0d3..e1b0f9791 100644 --- a/pkg/api/headers.go +++ b/pkg/api/headers.go @@ -4,6 +4,14 @@ import ( "net/http" ) +// Headers godoc +// @Summary Headers +// @Description returns a JSON array with the request HTTP headers +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /headers [get] +// @Success 200 {object} api.ArrayResponse func (s *Server) echoHeadersHandler(w http.ResponseWriter, r *http.Request) { s.JSONResponse(w, r, r.Header) } diff --git a/pkg/api/health.go b/pkg/api/health.go index 8ba578d94..61742ec5c 100644 --- a/pkg/api/health.go +++ b/pkg/api/health.go @@ -5,6 +5,14 @@ import ( "sync/atomic" ) +// Healthz godoc +// @Summary Liveness check +// @Description used by Kubernetes liveness probe +// @Tags Kubernetes +// @Accept json +// @Produce json +// @Router /healthz [get] +// @Success 200 {string} string "OK" func (s *Server) healthzHandler(w http.ResponseWriter, r *http.Request) { if atomic.LoadInt32(&healthy) == 1 { s.JSONResponse(w, r, map[string]string{"status": "OK"}) @@ -13,6 +21,14 @@ func (s *Server) healthzHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) } +// Readyz godoc +// @Summary Readiness check +// @Description used by Kubernetes readiness probe +// @Tags Kubernetes +// @Accept json +// @Produce json +// @Router /readyz [get] +// @Success 200 {string} string "OK" func (s *Server) readyzHandler(w http.ResponseWriter, r *http.Request) { if atomic.LoadInt32(&ready) == 1 { s.JSONResponse(w, r, map[string]string{"status": "OK"}) @@ -21,11 +37,27 @@ func (s *Server) readyzHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) } +// EnableReady godoc +// @Summary Enable ready state +// @Description signals the Kubernetes LB that this instance is ready to receive traffic +// @Tags Kubernetes +// @Accept json +// @Produce json +// @Router /readyz/enable [post] +// @Success 202 {string} string "OK" func (s *Server) enableReadyHandler(w http.ResponseWriter, r *http.Request) { atomic.StoreInt32(&ready, 1) w.WriteHeader(http.StatusAccepted) } +// DisableReady godoc +// @Summary Disable ready state +// @Description signals the Kubernetes LB to stop sending requests to this instance +// @Tags Kubernetes +// @Accept json +// @Produce json +// @Router /readyz/disable [post] +// @Success 202 {string} string "OK" func (s *Server) disableReadyHandler(w http.ResponseWriter, r *http.Request) { atomic.StoreInt32(&ready, 0) w.WriteHeader(http.StatusAccepted) diff --git a/pkg/api/index.go b/pkg/api/index.go index ebff73851..f7837ceba 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -6,6 +6,13 @@ import ( "path" ) +// Index godoc +// @Summary Index +// @Description renders podinfo UI +// @Tags HTTP API +// @Produce html +// @Router / [get] +// @Success 200 {string} string "OK" func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) { tmpl, err := template.New("vue.html").ParseFiles(path.Join(s.config.UIPath, "vue.html")) if err != nil { diff --git a/pkg/api/info.go b/pkg/api/info.go index 3e3fdfa33..90512cf11 100644 --- a/pkg/api/info.go +++ b/pkg/api/info.go @@ -9,19 +9,16 @@ import ( "github.com/stefanprodan/podinfo/pkg/version" ) +// Info godoc +// @Summary Runtime information +// @Description returns the runtime information +// @Tags HTTP API +// @Accept json +// @Produce json +// @Success 200 {object} api.RuntimeResponse +// @Router /api/info [get] func (s *Server) infoHandler(w http.ResponseWriter, r *http.Request) { - data := struct { - Hostname string `json:"hostname"` - Version string `json:"version"` - Revision string `json:"revision"` - Color string `json:"color"` - Message string `json:"message"` - GOOS string `json:"goos"` - GOARCH string `json:"goarch"` - Runtime string `json:"runtime"` - NumGoroutine string `json:"num_goroutine"` - NumCPU string `json:"num_cpu"` - }{ + data := RuntimeResponse{ Hostname: s.config.Hostname, Version: version.VERSION, Revision: version.REVISION, @@ -36,3 +33,16 @@ func (s *Server) infoHandler(w http.ResponseWriter, r *http.Request) { s.JSONResponse(w, r, data) } + +type RuntimeResponse struct { + Hostname string `json:"hostname"` + Version string `json:"version"` + Revision string `json:"revision"` + Color string `json:"color"` + Message string `json:"message"` + GOOS string `json:"goos"` + GOARCH string `json:"goarch"` + Runtime string `json:"runtime"` + NumGoroutine string `json:"num_goroutine"` + NumCPU string `json:"num_cpu"` +} diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 3158b5e88..c45c86fe6 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -47,6 +47,13 @@ func NewPrometheusMiddleware() *PrometheusMiddleware { } } +// Metrics godoc +// @Summary Prometheus metrics +// @Description returns HTTP requests duration and Go runtime metrics +// @Tags Kubernetes +// @Produce plain +// @Router /metrics [get] +// @Success 200 {string} string "OK" func (p *PrometheusMiddleware) Handler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { begin := time.Now() diff --git a/pkg/api/panic.go b/pkg/api/panic.go index 46f15089e..624daeed4 100644 --- a/pkg/api/panic.go +++ b/pkg/api/panic.go @@ -4,6 +4,11 @@ import ( "net/http" ) +// Panic godoc +// @Summary Panic +// @Description crashes the process with exit code 255 +// @Tags HTTP API +// @Router /panic [get] func (s *Server) panicHandler(w http.ResponseWriter, r *http.Request) { s.logger.Panic("Panic command received") } diff --git a/pkg/api/server.go b/pkg/api/server.go index a23cbea61..9367391ec 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -3,6 +3,7 @@ package api import ( "context" "fmt" + "github.com/swaggo/swag" "net/http" _ "net/http/pprof" "os" @@ -13,10 +14,26 @@ import ( "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/viper" + _ "github.com/stefanprodan/podinfo/pkg/api/docs" "github.com/stefanprodan/podinfo/pkg/fscache" + "github.com/swaggo/http-swagger" "go.uber.org/zap" ) +// @title Podinfo API +// @version 2.0 +// @description Go microservice template for Kubernetes. + +// @contact.name Source Code +// @contact.url https://github.com/stefanprodan/podinfo + +// @license.name MIT License +// @license.url https://github.com/stefanprodan/podinfo/blob/master/LICENSE + +// @host localhost:9898 +// @BasePath / +// @schemes http https + var ( healthy int32 ready int32 @@ -83,6 +100,19 @@ func (s *Server) registerHandlers() { s.router.HandleFunc("/ws/echo", s.echoWsHandler) s.router.HandleFunc("/chunked", s.chunkedHandler) s.router.HandleFunc("/chunked/{wait:[0-9]+}", s.chunkedHandler) + s.router.PathPrefix("/swagger/").Handler(httpSwagger.Handler( + httpSwagger.URL("/swagger/doc.json"), + )) + s.router.PathPrefix("/swagger/").Handler(httpSwagger.Handler( + httpSwagger.URL("/swagger/doc.json"), + )) + s.router.HandleFunc("/swagger.json", func(w http.ResponseWriter, r *http.Request) { + doc, err := swag.ReadDoc() + if err != nil { + s.logger.Error("swagger error", zap.Error(err), zap.String("path", "/swagger.json")) + } + w.Write([]byte(doc)) + }) } func (s *Server) registerMiddlewares() { @@ -206,3 +236,6 @@ func (s *Server) printRoutes() { return nil }) } + +type ArrayResponse []string +type MapResponse map[string]string diff --git a/pkg/api/status.go b/pkg/api/status.go index 23e69e1bd..1a397539d 100644 --- a/pkg/api/status.go +++ b/pkg/api/status.go @@ -7,6 +7,14 @@ import ( "strconv" ) +// Status godoc +// @Summary Status code +// @Description sets the response status code to the specified code +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /status/{code} [get] +// @Success 200 {object} api.MapResponse func (s *Server) statusHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/pkg/api/store.go b/pkg/api/store.go index f6d3f2627..57887259a 100644 --- a/pkg/api/store.go +++ b/pkg/api/store.go @@ -11,6 +11,14 @@ import ( "go.uber.org/zap" ) +// Store godoc +// @Summary Upload file +// @Description writes the posted content to disk at /data/hash and returns the SHA1 hash of the content +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /store [post] +// @Success 200 {object} api.MapResponse func (s *Server) storeWriteHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) @@ -29,6 +37,14 @@ func (s *Server) storeWriteHandler(w http.ResponseWriter, r *http.Request) { s.JSONResponseCode(w, r, map[string]string{"hash": hash}, http.StatusAccepted) } +// Store godoc +// @Summary Download file +// @Description returns the content of the file /data/hash if exists +// @Tags HTTP API +// @Accept json +// @Produce plain +// @Router /store/{hash} [get] +// @Success 200 {string} string "file" func (s *Server) storeReadHandler(w http.ResponseWriter, r *http.Request) { hash := mux.Vars(r)["hash"] content, err := ioutil.ReadFile(path.Join(s.config.DataPath, hash)) diff --git a/pkg/api/token.go b/pkg/api/token.go index d17131062..73bff87b9 100644 --- a/pkg/api/token.go +++ b/pkg/api/token.go @@ -17,6 +17,14 @@ type jwtCustomClaims struct { jwt.StandardClaims } +// Token godoc +// @Summary Generate JWT token +// @Description issues a JWT token valid for one minute +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /token [post] +// @Success 200 {object} api.TokenResponse func (s *Server) tokenGenerateHandler(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { @@ -46,10 +54,7 @@ func (s *Server) tokenGenerateHandler(w http.ResponseWriter, r *http.Request) { return } - var result = struct { - Token string `json:"token"` - ExpiresAt time.Time `json:"expires_at"` - }{ + var result = TokenResponse{ Token: t, ExpiresAt: time.Unix(claims.StandardClaims.ExpiresAt, 0), } @@ -57,6 +62,15 @@ func (s *Server) tokenGenerateHandler(w http.ResponseWriter, r *http.Request) { s.JSONResponse(w, r, result) } +// TokenValidate godoc +// @Summary Validate JWT token +// @Description validates the JWT token +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /token/validate [post] +// @Success 200 {object} api.TokenValidationResponse +// @Failure 401 {string} string "Unauthorized" // Get: JWT=$(curl -s -d 'test' localhost:9898/token | jq -r .token) // Post: curl -H "Authorization: Bearer ${JWT}" localhost:9898/token/validate func (s *Server) tokenValidateHandler(w http.ResponseWriter, r *http.Request) { @@ -87,10 +101,7 @@ func (s *Server) tokenValidateHandler(w http.ResponseWriter, r *http.Request) { if claims.StandardClaims.Issuer != "podinfo" { s.ErrorResponse(w, r, "invalid issuer", http.StatusUnauthorized) } else { - var result = struct { - TokenName string `json:"token_name"` - ExpiresAt time.Time `json:"expires_at"` - }{ + var result = TokenValidationResponse{ TokenName: claims.Name, ExpiresAt: time.Unix(claims.StandardClaims.ExpiresAt, 0), } @@ -100,3 +111,13 @@ func (s *Server) tokenValidateHandler(w http.ResponseWriter, r *http.Request) { s.ErrorResponse(w, r, "Invalid authorization token", http.StatusUnauthorized) } } + +type TokenResponse struct { + Token string `json:"token"` + ExpiresAt time.Time `json:"expires_at"` +} + +type TokenValidationResponse struct { + TokenName string `json:"token_name"` + ExpiresAt time.Time `json:"expires_at"` +} diff --git a/pkg/api/version.go b/pkg/api/version.go index 7384c5761..037691e74 100644 --- a/pkg/api/version.go +++ b/pkg/api/version.go @@ -6,6 +6,13 @@ import ( "github.com/stefanprodan/podinfo/pkg/version" ) +// Version godoc +// @Summary Version +// @Description returns podinfo version and git commit hash +// @Tags HTTP API +// @Produce json +// @Router /version [get] +// @Success 200 {object} api.MapResponse func (s *Server) versionHandler(w http.ResponseWriter, r *http.Request) { result := map[string]string{ "version": version.VERSION, diff --git a/pkg/version/version.go b/pkg/version/version.go index 3ff7884b4..09afc275d 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,4 +1,4 @@ package version -var VERSION = "2.0.2" +var VERSION = "2.1.0" var REVISION = "unknown" diff --git a/ui/vue.html b/ui/vue.html index 3b1642471..0a423790f 100644 --- a/ui/vue.html +++ b/ui/vue.html @@ -59,6 +59,7 @@

${ info.message }

Powered by podinfo version ${ info.version } revision ${ info.revision } + Swagger docs