diff --git a/Dockerfile b/Dockerfile
index 0fe35bc..32a823c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,5 +6,8 @@ RUN wget https://github.com/gohugoio/hugo/releases/download/v0.45.1/hugo_0.45.1_
&& ln -s /tmp/public/ /usr/share/nginx/html \
&& cd /tmp \
&& hugo \
- && cp ./nginx/default.conf /etc/nginx/conf.d/default.conf
+ && cp ./nginx/default.conf /etc/nginx/conf.d/default.conf \
+ && apk update \
+ && apk add nodejs npm \
+ && npm install -g asyncapi-generator
CMD ["/bin/sh", "/tmp/nginx/start.sh"]
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 76ab1ea..97fe833 100644
--- a/Makefile
+++ b/Makefile
@@ -51,13 +51,13 @@ build-all: clean-all build-site build-server build-docker ## Performs clean-all
#--- Run targets ---
run-server: ## Builds the in the server directory and runs it with default settings
cd server && go generate && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ../dist/server *.go
- MODE=LOCAL HUGODIR=$(CURRDIR)/webapp HUGOSTORE=$(CURRDIR)/webapp/content/apis SWAGGERSTORE=$(CURRDIR)/webapp/static/swaggerdocs EXTERNALIP=$(EXTIP) ./dist/server
+ MODE=LOCAL ASYNCDOCSTORE=$(CURRDIR)/webapp/static/asyncapidocs HUGODIR=$(CURRDIR)/webapp HUGOSTORE=$(CURRDIR)/webapp/content/apis SWAGGERSTORE=$(CURRDIR)/webapp/static/swaggerdocs EXTERNALIP=$(EXTIP) ./dist/server
run-docker: ## Runs a docker container with default settings
docker run -it --rm -p 80:80 -v $(HOME)/.kube:/root/.kube -v $(HOME)/.minikube:/home/$(USER)/.minikube -e MODE=LOCAL -e HUGODIR="/tmp" -e EXTERNALIP=$(EXTIP) -e HUGOCMD="sh -c \"cd /tmp && hugo\"" --name=apiscout $(DOCKERREPO)/apiscout:latest
run-hugo: ## Runs the embedded Hugo server on port 1313
- cd webapp && hugo server -D --disableFastRender
+ cd webapp && hugo server --renderToDisk
run-docs: ## Runs the embedded Hugo server on port 1313 for the documentation
cd docs && hugo server -D --disableFastRender --themesDir ../webapp/themes
diff --git a/README.md b/README.md
index e046397..664acc7 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ apiscout looks for two annotations to be able to index a service:
* `apiscout/index: 'true'` This annotation ensures that apiscout indexes the service
* `apiscout/swaggerUrl: '/swaggerspec'` This is the URL from where apiscout will read the OpenAPI document
+* `apiscout/asyncApiUrl: '/asyncapispec'` This is the URL from where apiscout will read the AsyncAPI document
## Environment variables for the docker container
diff --git a/apiscout.yml b/apiscout.yml
index 4f11fc8..6dc3fc5 100644
--- a/apiscout.yml
+++ b/apiscout.yml
@@ -31,7 +31,7 @@ spec:
spec:
containers:
- name: apiscout
- image:
+ image:
env:
- name: MODE
value: "KUBE"
diff --git a/samples/invoiceservice-go/Dockerfile b/samples/invoiceservice-go/Dockerfile
index a28e4b8..769084f 100644
--- a/samples/invoiceservice-go/Dockerfile
+++ b/samples/invoiceservice-go/Dockerfile
@@ -4,5 +4,6 @@ ENV HTTPPORT=8080 \
PAYMENTSERVICE=bla
ADD invoiceservice-go .
ADD swagger.json .
+ADD asyncapi.yaml .
EXPOSE 8080
CMD ./invoiceservice-go
\ No newline at end of file
diff --git a/samples/invoiceservice-go/Makefile b/samples/invoiceservice-go/Makefile
index 0736031..443ab4a 100644
--- a/samples/invoiceservice-go/Makefile
+++ b/samples/invoiceservice-go/Makefile
@@ -27,6 +27,7 @@ build-app:
build-docker:
cp Dockerfile dist/Dockerfile
cp swagger.json dist/swagger.json
+ cp asyncapi.yaml dist/asyncapi.yaml
eval $$(minikube docker-env) ;\
cd dist && docker build -t $(DOCKERHUBUSER)/invoiceservice-go:$(DOCKERTAG) .
diff --git a/samples/invoiceservice-go/README.md b/samples/invoiceservice-go/README.md
index 35f88e0..b6b4358 100644
--- a/samples/invoiceservice-go/README.md
+++ b/samples/invoiceservice-go/README.md
@@ -8,8 +8,9 @@ This sample Flogo application is used to demonstrate some key Flogo constructs,
├── Dockerfile <-- A Dockerfile to build a container based on an Alpine base image
├── main.go <-- The Go source code for the app
├── Makefile <-- A Makefile to help build and deploy the app
-├── payment-go-svc.yml <-- The Kubernetes deployment file
+├── invoice-go-svc.yml <-- The Kubernetes deployment file
├── README.md <-- This file
+├── asyncapi.yaml <-- The AsyncAPI specification (async spec example)
└── swagger.json <-- The OpenAPI specification for the app
```
@@ -32,6 +33,7 @@ To build and deploy the app to Kubernetes, run the make targets for _deps_, _bui
After starting the app, it will register with two endpoints:
* **/api/invoices/:id**: Get the invoice details for the invoice ID.
* **/swagger**: Get the OpenAPI specification for this app
+* **/asyncapispec**: Get the AsyncAPI specification for this app
## API Scout
-As you deploy the app to Kubernetes, after a few seconds the API will be found by API Scout and indexed. The lines 36 to 38 in [invoice-go-svc.yml](./invoice-go-svc.yml) are the annotations that make sure the API is found.
\ No newline at end of file
+As you deploy the app to Kubernetes, after a few seconds the API will be found by API Scout and indexed. The lines 34 to 36 in [invoice-go-svc.yml](./invoice-go-svc.yml) are the annotations that make sure the API is found.
\ No newline at end of file
diff --git a/samples/invoiceservice-go/asyncapi.yaml b/samples/invoiceservice-go/asyncapi.yaml
new file mode 100644
index 0000000..d84de30
--- /dev/null
+++ b/samples/invoiceservice-go/asyncapi.yaml
@@ -0,0 +1,161 @@
+asyncapi: "1.2.0"
+info:
+ title: invoiceservice
+ version: "1.0.0"
+ x-logo: https://avatars0.githubusercontent.com/u/16401334?v=4&s=200
+ description: |
+ This is a simple example of an _AsyncAPI_ document.
+ termsOfService: https://api.company.com/terms
+baseTopic: 'hitch'
+
+servers:
+ - url: api.company.com:{port}/{app-id}
+ description: Allows you to connect using the MQTT protocol.
+ scheme: mqtt
+ variables:
+ app-id:
+ default: demo
+ description: You can find your `app-id` in our control panel, under the auth tab.
+ port:
+ enum:
+ - '5676'
+ - '5677'
+ default: '5676'
+ - url: api.company.com:{port}/{app-id}
+ description: Allows you to connect using the AMQP protocol.
+ scheme: amqp
+ variables:
+ app-id:
+ default: demo
+ description: You can find your `app-id` in our control panel, under the auth tab.
+ port:
+ enum:
+ - '5676'
+ - '5677'
+ default: '5676'
+
+topics:
+ accounts.1.0.action.user.signup:
+ publish:
+ $ref: "#/components/messages/userSignUp"
+ accounts.1.0.event.user.signup:
+ subscribe:
+ $ref: "#/components/messages/userSignedUp"
+
+components:
+ messages:
+ userSignUp:
+ deprecated: true
+ summary: Action to sign a user up.
+ description: |
+ Multiline description of what this action does. **It allows Markdown.**
+ tags:
+ - name: user
+ - name: signup
+ headers:
+ type: object
+ properties:
+ qos:
+ $ref: "#/components/schemas/MQTTQoSHeader"
+ retainFlag:
+ $ref: "#/components/schemas/MQTTRetainHeader"
+ payload:
+ type: object
+ properties:
+ user:
+ $ref: "#/components/schemas/userCreate"
+ signup:
+ $ref: "#/components/schemas/signup"
+
+
+ userSignedUp:
+ payload:
+ type: object
+ properties:
+ test:
+ type: array
+ items:
+ type: object
+ properties:
+ key1:
+ type: string
+ key2:
+ type: integer
+ user:
+ $ref: "#/components/schemas/user"
+ signup:
+ $ref: "#/components/schemas/signup"
+ schemas:
+ id:
+ title: id
+ description: Resource identifier
+ type: string
+ username:
+ title: username
+ description: User handle
+ type: string
+ datetime:
+ title: datetime
+ description: Date and Time of the message
+ type: string
+ format: date-time
+ MQTTQoSHeader:
+ title: qos
+ description: Quality of Service
+ type: integer
+ format: int32
+ default: 1
+ enum:
+ - 0
+ - 2
+ MQTTRetainHeader:
+ title: retainFlag
+ description: |
+ This flag determines if the message will be saved by the broker for the specified
+ topic as last known good value. New clients that subscribe to that topic will receive
+ the last retained message on that topic instantly after subscribing. More on retained messages
+ and best practices in one of the next posts.
+ type: boolean
+ default: false
+ user:
+ type: object
+ required:
+ - id
+ - username
+ properties:
+ id:
+ description: User Id
+ $ref: "#/components/schemas/id"
+ full_name:
+ description: User full name
+ type: string
+ username:
+ $ref: "#/components/schemas/username"
+ userCreate:
+ type: object
+ required:
+ - username
+ properties:
+ full_name:
+ description: User full name
+ type: string
+ username:
+ $ref: "#/components/schemas/username"
+
+ signup:
+ type: object
+ required:
+ - method
+ - datetime
+ properties:
+ method:
+ description: Signup method
+ type: string
+ enum:
+ - email
+ - facebook
+ - twitter
+ - github
+ - google
+ datetime:
+ $ref: "#/components/schemas/datetime"
\ No newline at end of file
diff --git a/samples/invoiceservice-go/invoice-go-svc.yml b/samples/invoiceservice-go/invoice-go-svc.yml
index afec4f1..4ccb18a 100644
--- a/samples/invoiceservice-go/invoice-go-svc.yml
+++ b/samples/invoiceservice-go/invoice-go-svc.yml
@@ -32,6 +32,7 @@ metadata:
namespace: default
annotations:
apiscout/index: 'true'
+ apiscout/asyncApiUrl: '/asyncapispec'
apiscout/swaggerUrl: '/swaggerspec'
spec:
ports:
diff --git a/samples/invoiceservice-go/main.go b/samples/invoiceservice-go/main.go
index 89ff222..60bb6fd 100644
--- a/samples/invoiceservice-go/main.go
+++ b/samples/invoiceservice-go/main.go
@@ -15,6 +15,7 @@ import (
"github.com/TIBCOSoftware/flogo-lib/engine"
"github.com/TIBCOSoftware/flogo-lib/flogo"
"github.com/TIBCOSoftware/flogo-lib/logger"
+ "github.com/ghodss/yaml"
"github.com/retgits/flogo-components/activity/randomnumber"
)
@@ -49,6 +50,7 @@ func appBuilder() *flogo.App {
trg := app.NewTrigger(&rt.RestTrigger{}, map[string]interface{}{"port": port})
trg.NewFuncHandler(map[string]interface{}{"method": "GET", "path": "/api/invoices/:id"}, handler)
trg.NewFuncHandler(map[string]interface{}{"method": "GET", "path": "/swaggerspec"}, SwaggerSpec)
+ trg.NewFuncHandler(map[string]interface{}{"method": "GET", "path": "/asyncapispec"}, YamlSpec)
return app
}
@@ -133,3 +135,28 @@ func getEnvKey(key string, fallback string) string {
}
return fallback
}
+
+// YamlSpec is the function that gets executedto retrieve the AsynSpec
+func YamlSpec(ctx context.Context, inputs map[string]*data.Attribute) (map[string]*data.Attribute, error) {
+ // The return message is a map[string]*data.Attribute which we'll have to construct
+ response := make(map[string]interface{})
+ ret := make(map[string]*data.Attribute)
+
+ fileData, err := ioutil.ReadFile("asyncapi.yaml")
+ if err != nil {
+ ret["code"], _ = data.NewAttribute("code", data.TypeInteger, 500)
+ response["msg"] = err.Error()
+ } else {
+ ret["code"], _ = data.NewAttribute("code", data.TypeInteger, 200)
+ var data map[string]interface{}
+ if err := yaml.Unmarshal(fileData, &data); err != nil {
+ panic(err)
+ }
+ response = data
+ }
+
+ ret["data"], _ = data.NewAttribute("data", data.TypeAny, response)
+
+ return ret, nil
+
+}
diff --git a/samples/streetlightsapp-go/.gitignore b/samples/streetlightsapp-go/.gitignore
new file mode 100644
index 0000000..7773828
--- /dev/null
+++ b/samples/streetlightsapp-go/.gitignore
@@ -0,0 +1 @@
+dist/
\ No newline at end of file
diff --git a/samples/streetlightsapp-go/Dockerfile b/samples/streetlightsapp-go/Dockerfile
new file mode 100644
index 0000000..9612151
--- /dev/null
+++ b/samples/streetlightsapp-go/Dockerfile
@@ -0,0 +1,7 @@
+FROM alpine:latest
+RUN apk update && apk add ca-certificates
+ENV HTTPPORT=8090
+ADD streetlightsapp-go .
+ADD streetlightsapi.yaml .
+EXPOSE 8090
+CMD ./streetlightsapp-go
\ No newline at end of file
diff --git a/samples/streetlightsapp-go/Makefile b/samples/streetlightsapp-go/Makefile
new file mode 100644
index 0000000..6e294ed
--- /dev/null
+++ b/samples/streetlightsapp-go/Makefile
@@ -0,0 +1,39 @@
+.PHONY: deps
+
+#--- Variables ---
+DOCKERHUBUSER=naresh
+DOCKERTAG=latest
+IPADDR=xxx.xxx.xxx.xxx
+
+#--- Get the dependencies ---
+deps:
+ go get -u ./...
+
+#--- Clean up the dist folder ---
+clean:
+ rm -rf dist
+
+#--- Clean up the Kubernetes deployment ---
+clean-kube:
+ kubectl delete deployment streetlights-go-svc
+ kubectl delete svc streetlights-go-svc
+
+#--- Build the Flogo app ---
+build-app:
+ go generate
+ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o dist/streetlightsapp-go
+
+#--- Build the docker container ---
+build-docker:
+ cp Dockerfile dist/Dockerfile
+ cp streetlightsapi.yaml dist/streetlightsapi.yaml
+ eval $$(minikube docker-env) ;\
+ cd dist && docker build -t $(DOCKERHUBUSER)/streetlightsapp-go:$(DOCKERTAG) .
+
+#--- Run the container ---
+run-docker:
+ docker run --rm -it -p 9999:8090 $(DOCKERHUBUSER)/streetlightsapp-go:$(DOCKERTAG)
+
+#--- Run the app on Kubernetes ---
+run-kube:
+ kubectl apply -f streetlights-go-svc.yml
\ No newline at end of file
diff --git a/samples/streetlightsapp-go/README.md b/samples/streetlightsapp-go/README.md
new file mode 100644
index 0000000..0ae6eac
--- /dev/null
+++ b/samples/streetlightsapp-go/README.md
@@ -0,0 +1,36 @@
+# Streetlights Service
+
+This sample application is used to demonstrate AsyncAPI app and is set to be indexed by API Scout.
+
+## Files
+```bash
+.
+├── Dockerfile <-- A Dockerfile to build a container based on an Alpine base image
+├── main.go <-- The Go source code for the app
+├── Makefile <-- A Makefile to help build and deploy the app
+├── streetlights-go-svc.yml <-- The Kubernetes deployment file
+├── README.md <-- This file
+└── streetlightsapi.yaml <-- The AsyncAPI specification for the app
+```
+
+## Make targets
+The [Makefile](./Makefile) has a few targets:
+* **deps**: Get all the Go dependencies for the app
+* **clean**: Remove the `dist` folder for a new deployment
+* **clean-kube**: Remove all the deployed artifacts from Kubernetes
+* **build-app**: Build an executable (and store it in the dist folder)
+* **build-docker**: Build a Docker container from the contents of the `dist` folder
+* **run-docker**: Run the Docker image with default settings
+* **run-kube**: Deploy the app to Kubernetes
+
+_For more detailed information on the commands that are executed you can check out the [Makefile](./Makefile)_
+
+## Build and deploy the app
+To build and deploy the app to Kubernetes, run the make targets for _deps_, _build-app_, _build-docker_ and _run-kube_
+
+## API
+After starting the app, it will register with below endpoint:
+* **/asyncapispec**: Get the AsyncAPI specification for this app
+
+## API Scout
+As you deploy the app to Kubernetes, after a few seconds the API will be found by API Scout and indexed. The lines 34 to 35 in [streetlights-go-svc.yml](./streetlights-go-svc.yml) are the annotations that make sure the API is found.
\ No newline at end of file
diff --git a/samples/streetlightsapp-go/main.go b/samples/streetlightsapp-go/main.go
new file mode 100644
index 0000000..aea5350
--- /dev/null
+++ b/samples/streetlightsapp-go/main.go
@@ -0,0 +1,84 @@
+//go:generate go run $GOPATH/src/github.com/TIBCOSoftware/flogo-lib/flogo/gen/gen.go $GOPATH
+package main
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "strconv"
+
+ rt "github.com/TIBCOSoftware/flogo-contrib/trigger/rest"
+ "github.com/TIBCOSoftware/flogo-lib/core/data"
+ "github.com/TIBCOSoftware/flogo-lib/engine"
+ "github.com/TIBCOSoftware/flogo-lib/flogo"
+ "github.com/TIBCOSoftware/flogo-lib/logger"
+ "github.com/ghodss/yaml"
+)
+
+var (
+ httpport = getEnvKey("HTTPPORT", "8090")
+)
+
+func main() {
+ // Create a new Flogo app
+ app := appBuilder()
+
+ e, err := flogo.NewEngine(app)
+
+ if err != nil {
+ logger.Error(err)
+ return
+ }
+
+ engine.RunEngine(e)
+}
+
+func appBuilder() *flogo.App {
+ app := flogo.NewApp()
+
+ // Convert the HTTPPort to an integer
+ port, err := strconv.Atoi(httpport)
+ if err != nil {
+ logger.Error(err)
+ }
+
+ // Register the HTTP trigger
+ trg := app.NewTrigger(&rt.RestTrigger{}, map[string]interface{}{"port": port})
+ trg.NewFuncHandler(map[string]interface{}{"method": "GET", "path": "/asyncapispec"}, YamlSpec)
+
+ return app
+}
+
+// getEnvKey tries to get the specified key from the OS environment and returns either the
+// value or the fallback that was provided
+func getEnvKey(key string, fallback string) string {
+ if value, ok := os.LookupEnv(key); ok {
+ return value
+ }
+ return fallback
+}
+
+// YamlSpec is the function that gets executedto retrieve the SwaggerSpec
+func YamlSpec(ctx context.Context, inputs map[string]*data.Attribute) (map[string]*data.Attribute, error) {
+ // The return message is a map[string]*data.Attribute which we'll have to construct
+ response := make(map[string]interface{})
+ ret := make(map[string]*data.Attribute)
+
+ fileData, err := ioutil.ReadFile("streetlightsapi.yaml")
+ if err != nil {
+ ret["code"], _ = data.NewAttribute("code", data.TypeInteger, 500)
+ response["msg"] = err.Error()
+ } else {
+ ret["code"], _ = data.NewAttribute("code", data.TypeInteger, 200)
+ var data map[string]interface{}
+ if err := yaml.Unmarshal(fileData, &data); err != nil {
+ panic(err)
+ }
+ response = data
+ }
+
+ ret["data"], _ = data.NewAttribute("data", data.TypeAny, response)
+
+ return ret, nil
+
+}
diff --git a/samples/streetlightsapp-go/streetlights-go-svc.yml b/samples/streetlightsapp-go/streetlights-go-svc.yml
new file mode 100644
index 0000000..32fe4e1
--- /dev/null
+++ b/samples/streetlightsapp-go/streetlights-go-svc.yml
@@ -0,0 +1,43 @@
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ labels:
+ run: streetlights-go-svc
+ name: streetlights-go-svc
+ namespace: default
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ run: streetlights-go-svc
+ template:
+ metadata:
+ labels:
+ run: streetlights-go-svc
+ spec:
+ containers:
+ - name: streetlights-go-svc
+ image: naresh/streetlightsapp-go:latest
+ imagePullPolicy: Never
+ ports:
+ - containerPort: 8090
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ run: streetlights-go-svc
+ name: streetlights-go-svc
+ namespace: default
+ annotations:
+ apiscout/index: 'true'
+ apiscout/asyncApiUrl: '/asyncapispec'
+spec:
+ ports:
+ - port: 80
+ protocol: TCP
+ targetPort: 8090
+ selector:
+ run: streetlights-go-svc
+ type: LoadBalancer
diff --git a/samples/streetlightsapp-go/streetlightsapi.yaml b/samples/streetlightsapp-go/streetlightsapi.yaml
new file mode 100644
index 0000000..76fe811
--- /dev/null
+++ b/samples/streetlightsapp-go/streetlightsapi.yaml
@@ -0,0 +1,108 @@
+asyncapi: '1.1.0'
+info:
+ title: Streetlights API
+ version: '1.0.0'
+ description: |
+ The Smartylighting Streetlights API allows you to remotely manage the city lights.
+ ### Check out its awesome features:
+ * Turn a specific streetlight on/off 🌃
+ * Dim a specific streetlight 😎
+ * Receive real-time information about environmental lighting conditions 📈
+ license:
+ name: Apache 2.0
+ url: https://www.apache.org/licenses/LICENSE-2.0
+baseTopic: smartylighting.streetlights.1.0
+
+servers:
+ - url: api.streetlights.smartylighting.com:{port}
+ scheme: mqtt
+ description: Test broker
+ variables:
+ port:
+ description: Secure connection (TLS) is available through port 8883.
+ default: '1883'
+ enum:
+ - '1883'
+ - '8883'
+
+security:
+ - apiKey: []
+
+topics:
+ event.{streetlightId}.lighting.measured:
+ parameters:
+ - name: streetlightId
+ description: The ID of the streetlight.
+ schema:
+ type: string
+ publish:
+ $ref: '#/components/messages/lightMeasured'
+
+ action.{streetlightId}.turn.on:
+ subscribe:
+ $ref: '#/components/messages/turnOnOff'
+
+ action.{streetlightId}.turn.off:
+ subscribe:
+ $ref: '#/components/messages/turnOnOff'
+
+ action.{streetlightId}.dim:
+ subscribe:
+ $ref: '#/components/messages/dimLight'
+
+components:
+ messages:
+ lightMeasured:
+ summary: Inform about environmental lighting conditions for a particular streetlight.
+ payload:
+ $ref: "#/components/schemas/lightMeasuredPayload"
+ turnOnOff:
+ summary: Command a particular streetlight to turn the lights on or off.
+ payload:
+ $ref: "#/components/schemas/turnOnOffPayload"
+ dimLight:
+ summary: Command a particular streetlight to dim the lights.
+ payload:
+ $ref: "#/components/schemas/dimLightPayload"
+
+ schemas:
+ lightMeasuredPayload:
+ type: object
+ properties:
+ lumens:
+ type: integer
+ minimum: 0
+ description: Light intensity measured in lumens.
+ sentAt:
+ $ref: "#/components/schemas/sentAt"
+ turnOnOffPayload:
+ type: object
+ properties:
+ command:
+ type: string
+ enum:
+ - on
+ - off
+ description: Whether to turn on or off the light.
+ sentAt:
+ $ref: "#/components/schemas/sentAt"
+ dimLightPayload:
+ type: object
+ properties:
+ percentage:
+ type: integer
+ description: Percentage to which the light should be dimmed to.
+ minimum: 0
+ maximum: 100
+ sentAt:
+ $ref: "#/components/schemas/sentAt"
+ sentAt:
+ type: string
+ format: date-time
+ description: Date and time when the message was sent.
+
+ securitySchemes:
+ apiKey:
+ type: apiKey
+ in: user
+ description: Provide your API key as the user and leave the password empty.
\ No newline at end of file
diff --git a/server/main.go b/server/main.go
index c50ac2e..f9b24ee 100644
--- a/server/main.go
+++ b/server/main.go
@@ -35,6 +35,8 @@ var (
externalIP = util.GetEnvKey("EXTERNALIP", "")
// The base directory for Hugo
hugoDir = util.GetEnvKey("HUGODIR", "")
+ // The localtion where to store async api docs
+ asyncDocStore = util.GetEnvKey("ASYNCDOCSTORE", "/tmp/static/asyncapidocs")
)
// main is the main entrypoint to start APIScout
@@ -55,7 +57,7 @@ func main() {
log.Printf("------------------------------------------------------------\n")
// Create a new APIScout server instance
- srv, err := server.New(swaggerStore, hugoStore, runMode, externalIP, hugoDir)
+ srv, err := server.New(swaggerStore, hugoStore, runMode, externalIP, hugoDir, asyncDocStore)
if err != nil {
panic(err.Error())
}
diff --git a/server/server/server.go b/server/server/server.go
index 8d423cd..0f45a03 100644
--- a/server/server/server.go
+++ b/server/server/server.go
@@ -26,18 +26,21 @@ type Server struct {
ExternalIP string
// The base directory for Hugo
HugoDir string
+ // The localtion where to store async api docs
+ AsyncDocStore string
}
// New creates a new instance of the Server
-func New(swaggerStore string, hugoStore string, runMode string, externalIP string, hugoDir string) (*Server, error) {
+func New(swaggerStore string, hugoStore string, runMode string, externalIP string, hugoDir string, asyncDocStore string) (*Server, error) {
// Return a new struct
return &Server{
- ServiceMap: make(map[string]string),
- SwaggerStore: swaggerStore,
- HugoStore: hugoStore,
- RunMode: runMode,
- ExternalIP: externalIP,
- HugoDir: hugoDir,
+ ServiceMap: make(map[string]string),
+ SwaggerStore: swaggerStore,
+ HugoStore: hugoStore,
+ RunMode: runMode,
+ ExternalIP: externalIP,
+ HugoDir: hugoDir,
+ AsyncDocStore: asyncDocStore,
}, nil
}
diff --git a/server/server/service.go b/server/server/service.go
index b08fb27..52efed1 100644
--- a/server/server/service.go
+++ b/server/server/service.go
@@ -9,7 +9,7 @@ import (
"strings"
"github.com/TIBCOSoftware/apiscout/server/util"
- "k8s.io/api/core/v1"
+ v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/watch"
)
@@ -18,6 +18,8 @@ const (
annotation = "apiscout/index"
// The annotation for apiscout to get the OpenAPI doc from
swaggerURL = "apiscout/swaggerUrl"
+ // The annotation for apiscout to get the AsyncAPI doc from
+ asyncAPIURL = "apiscout/asyncApiUrl"
)
// handleService takes the Kubernetes service object and the EventType as input to determine what
@@ -91,13 +93,43 @@ func add(service *v1.Service, srv *Server) error {
port = service.Spec.Ports[0].Port
}
- apidoc, err := util.GetAPIDoc(fmt.Sprintf("http://%s:%d%s", ip, port, service.Annotations[swaggerURL]))
+ apis := getApis(service)
+
+ // create service name folder
+ serviceFold := filepath.Join(srv.HugoStore, strings.Replace(strings.ToLower(service.Name), " ", "-", -1))
+ err := util.CreateFolder(serviceFold)
if err != nil {
- log.Printf("Error while retrieving API document from %s: %s", fmt.Sprintf("http://%s:%d%s", ip, port, service.Annotations[swaggerURL]), err.Error())
+ log.Fatalln("Error while creating service name folder ", err)
return err
}
- util.WriteSwaggerToDisk(service.Name, apidoc, fmt.Sprintf("%s:%d", ip, port), srv.SwaggerStore, srv.HugoStore)
+ for apiType, docURL := range apis {
+
+ apidoc, err := util.GetAPIDoc(fmt.Sprintf("http://%s:%d%s", ip, port, docURL))
+
+ if err != nil {
+ log.Printf("Error while retrieving API document from %s: %s", fmt.Sprintf("http://%s:%d%s", ip, port, docURL), err.Error())
+ return err
+ }
+
+ if strings.Compare(apiType, "OPENAPI") == 0 {
+ err = util.WriteSwaggerToDisk(service.Name, apidoc, fmt.Sprintf("%s:%d", ip, port), srv.SwaggerStore, srv.HugoStore)
+ } else {
+ err = util.GenerateMarkdownFile(srv.AsyncDocStore, srv.HugoStore, apidoc, service.Name)
+ }
+
+ if err != nil {
+ log.Fatalf(" Service %s addition failed for doc type %s ", service.Name, apiType)
+ return err
+ }
+ }
+
+ // create service markup file
+ err = util.WriteServiceMarkdownFile(serviceFold, strings.Replace(strings.ToLower(service.Name), " ", "-", -1))
+ if err != nil {
+ log.Fatal("Error while creating service markup file ", err)
+ return err
+ }
srv.ServiceMap[service.Name] = "DONE"
log.Printf("Service %s has been added to API Scout\n", service.Name)
@@ -110,23 +142,55 @@ func add(service *v1.Service, srv *Server) error {
func remove(service *v1.Service, srv *Server) error {
log.Printf("Attempting to delete %s\n", service.Name)
- // Remove JSON file
- filename := filepath.Join(srv.SwaggerStore, fmt.Sprintf("%s.json", strings.Replace(strings.ToLower(service.Name), " ", "-", -1)))
- err := os.Remove(filename)
- if err != nil {
- return err
- }
+ var docPath string
+
+ apis := getApis(service)
+
+ for apiType := range apis {
+
+ if strings.Compare(strings.ToUpper(apiType), "ASYNCAPI") == 0 {
+ docPath = srv.AsyncDocStore
+ } else {
+ docPath = srv.SwaggerStore
+ }
+
+ // Remove JSON file
+ filename := filepath.Join(docPath, fmt.Sprintf("%s.json", strings.Replace(strings.ToLower(service.Name), " ", "-", -1)))
+ err := os.Remove(filename)
+ if err != nil {
+ return err
+ }
+
+ // Remove Markdown file
+ filename = filepath.Join(srv.HugoStore, strings.Replace(strings.ToLower(service.Name), " ", "-", -1), fmt.Sprintf("%s-%s.md", strings.Replace(strings.ToLower(service.Name), " ", "-", -1), strings.ToLower(apiType)))
+ err = os.Remove(filename)
+ if err != nil {
+ return err
+ }
- // Remove Markdown file
- filename = filepath.Join(srv.HugoStore, fmt.Sprintf("%s.md", strings.Replace(strings.ToLower(service.Name), " ", "-", -1)))
- err = os.Remove(filename)
- if err != nil {
- return err
}
+ // Remove service markup file and folder
+ os.RemoveAll(filepath.Join(srv.HugoStore, strings.Replace(strings.ToLower(service.Name), " ", "-", -1)))
+ os.Remove(filepath.Join(srv.HugoStore, strings.Replace(strings.ToLower(service.Name), " ", "-", -1)))
+
// Remove service from service map
delete(srv.ServiceMap, service.Name)
log.Printf("Service %s has been removed from API Scout\n", service.Name)
return nil
}
+
+func getApis(service *v1.Service) map[string]string {
+ multiAPIMap := make(map[string]string)
+
+ if len(service.Annotations[asyncAPIURL]) != 0 {
+ multiAPIMap["ASYNCAPI"] = service.Annotations[asyncAPIURL]
+ }
+
+ if len(service.Annotations[swaggerURL]) != 0 {
+ multiAPIMap["OPENAPI"] = service.Annotations[swaggerURL]
+ }
+
+ return multiAPIMap
+}
diff --git a/server/server/service_test.go b/server/server/service_test.go
index 0549778..c121183 100644
--- a/server/server/service_test.go
+++ b/server/server/service_test.go
@@ -8,9 +8,8 @@ import (
"strings"
"testing"
+ v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/watch"
-
- "k8s.io/api/core/v1"
)
const swaggerJSONPayload = `{
@@ -163,7 +162,7 @@ func TestHandleService(t *testing.T) {
os.MkdirAll(tempPath, 0777)
- srv, err := New(tempPath, tempPath, runMode, externalIP, tempPath)
+ srv, err := New(tempPath, tempPath, runMode, externalIP, tempPath, "", "")
if err != nil {
panic(err.Error())
}
diff --git a/server/util/apidoc.go b/server/util/apidoc.go
index ef87e1f..525bb9d 100644
--- a/server/util/apidoc.go
+++ b/server/util/apidoc.go
@@ -16,8 +16,9 @@ import (
// A template for the Markdown file for Hugo
const markdown = `---
-title: {{.title}}
+title: {{.title}}-openapi
weight: 1000
+#post: "openapi"
---
{{.json}}`
@@ -113,30 +114,13 @@ func WriteSwaggerToDisk(name string, apidoc string, svchost string, swaggerStore
s := buf.String()
// Determine where to save the file
- filename = filepath.Join(hugoStore, fmt.Sprintf("%s.md", strings.Replace(strings.ToLower(name), " ", "-", -1)))
+ filename = filepath.Join(hugoStore, strings.Replace(strings.ToLower(name), " ", "-", -1), fmt.Sprintf("%s-openapi.md", strings.Replace(strings.ToLower(name), " ", "-", -1)))
log.Printf("Preparing to write %s to disk", filename)
os.Remove(filename)
- // Create a file on disk
- file, err = os.Create(filename)
- if err != nil {
- log.Printf("error while creating file: %s", err.Error())
- return fmt.Errorf("error while creating file: %s", err.Error())
- }
- defer file.Close()
-
- // Open the file to write
- file, err = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
- if err != nil {
- log.Printf("error while opening file: %s", err.Error())
- return fmt.Errorf("error while opening file: %s", err.Error())
- }
-
- // Write the Markdown doc to disk
- _, err = file.Write([]byte(s))
+ err = createFileWithContent(filename, s)
if err != nil {
- log.Printf("error while writing Markdown to disk: %s", err.Error())
- return fmt.Errorf("error while writing Markdown to disk: %s", err.Error())
+ return err
}
return nil
diff --git a/server/util/asyncapi.go b/server/util/asyncapi.go
new file mode 100644
index 0000000..913688e
--- /dev/null
+++ b/server/util/asyncapi.go
@@ -0,0 +1,93 @@
+package util
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "text/template"
+)
+
+// A template for the Markdown file for Hugo
+const asyncMarkdown = `---
+title: {{.title}}-asyncapi
+weight: 1000
+#post: "asyncapi"
+---
+
+{{.content}}`
+
+// GenerateMarkdownFile generates markdown file for asyncapi docs
+func GenerateMarkdownFile(srcFile, destFile, apiDoc, serviceName string) error {
+
+ log.Print("Writing file from received data")
+
+ asyncFile := filepath.Join(srcFile, fmt.Sprintf("%s.json", strings.Replace(strings.ToLower(serviceName), " ", "-", -1)))
+ log.Printf("Preparing to write %s to disk", asyncFile)
+ os.Remove(asyncFile)
+
+ err := createFileWithContent(asyncFile, apiDoc)
+ if err != nil {
+ return err
+ }
+
+ log.Print("Generating AsyncApi markdown file")
+
+ cmd := exec.Command("ag", "-o", destFile, asyncFile, "markdown")
+ output, err := cmd.CombinedOutput()
+ log.Print(string(output))
+
+ if err != nil {
+ return err
+ }
+
+ // read markdown file
+ data, err := ioutil.ReadFile(filepath.Join(destFile, "asyncapi.md"))
+ if err != nil {
+ return nil
+ }
+ os.Remove(filepath.Join(destFile, "asyncapi.md"))
+
+ // Render the Markdown file based on the template
+ t := template.Must(template.New("top").Parse(asyncMarkdown))
+ buf := &bytes.Buffer{}
+
+ // Unmarshal the string into a proper document
+ var docContent map[string]interface{}
+ if err := json.Unmarshal([]byte(apiDoc), &docContent); err != nil {
+ log.Printf("error while unmarshaling apidoc: %s", err.Error())
+ return fmt.Errorf("error while unmarshaling apidoc: %s", err.Error())
+ }
+
+ var title string
+ if val, ok := docContent["info"].(map[string]interface{})["title"]; ok {
+ title = val.(string)
+ }
+
+ dataMap := make(map[string]interface{})
+ dataMap["title"] = title
+ dataMap["content"] = string(data)
+
+ if err := t.Execute(buf, dataMap); err != nil {
+ log.Printf("error while rendering Markdown file: %s", err.Error())
+ return fmt.Errorf("error while rendering Markdown file: %s", err.Error())
+ }
+ s := buf.String()
+
+ // Determine where to save the file
+ mdFilename := filepath.Join(destFile, strings.Replace(strings.ToLower(serviceName), " ", "-", -1), fmt.Sprintf("%s-asyncapi.md", strings.Replace(strings.ToLower(serviceName), " ", "-", -1)))
+ log.Printf("Preparing to write %s to disk", mdFilename)
+ os.Remove(mdFilename)
+
+ err = createFileWithContent(mdFilename, s)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/server/util/hugo.go b/server/util/hugo.go
index 212fe45..4d99d93 100644
--- a/server/util/hugo.go
+++ b/server/util/hugo.go
@@ -3,13 +3,15 @@ package util
import (
"log"
+ "os"
"os/exec"
+ "path/filepath"
)
// GenerateDocs is a wrapper around the Hugo binary in the container and instructs the binary to generate the site
func GenerateDocs(hugoDir string) error {
log.Print("Regeneratig Hugo content")
-
+ os.RemoveAll(filepath.Join(hugoDir, "public"))
cmd := exec.Command("sh", "-c", "hugo")
cmd.Dir = hugoDir
output, err := cmd.CombinedOutput()
diff --git a/server/util/os.go b/server/util/os.go
index 98495a5..eb02427 100644
--- a/server/util/os.go
+++ b/server/util/os.go
@@ -1,7 +1,11 @@
// Package util implements utility methods
package util
-import "os"
+import (
+ "fmt"
+ "log"
+ "os"
+)
// GetEnvKey tries to get the specified key from the OS environment and returns either the
// value or the fallback that was provided
@@ -19,3 +23,42 @@ func HomeDir() string {
}
return os.Getenv("USERPROFILE") // windows
}
+
+func createFileWithContent(filename, content string) error {
+
+ // Create a file on disk
+ file, err := os.Create(filename)
+ if err != nil {
+ log.Printf("error while creating file: %s", err.Error())
+ return fmt.Errorf("error while creating file: %s", err.Error())
+ }
+ defer file.Close()
+
+ // Open the file to write
+ file, err = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ log.Printf("error while opening file: %s", err.Error())
+ return fmt.Errorf("error while opening file: %s", err.Error())
+ }
+
+ // Write the Markdown doc to disk
+ _, err = file.Write([]byte(content))
+ if err != nil {
+ log.Printf("error while writing Markdown to disk: %s", err.Error())
+ return fmt.Errorf("error while writing Markdown to disk: %s", err.Error())
+ }
+
+ return nil
+}
+
+// CreateFolder creates folder for given path
+func CreateFolder(path string) error {
+ _, err := os.Stat(path)
+ if err != nil {
+ err = os.MkdirAll(path, 0777)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/server/util/servicedoc.go b/server/util/servicedoc.go
new file mode 100644
index 0000000..3a0e228
--- /dev/null
+++ b/server/util/servicedoc.go
@@ -0,0 +1,48 @@
+package util
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "text/template"
+)
+
+const serviceMarkdown = `---
+title: {{.title}}
+weight: 5
+---
+`
+
+// WriteServiceMarkdownFile writes service index file for given servicename
+func WriteServiceMarkdownFile(path, serviceName string) error {
+
+ fileName := filepath.Join(path, "_index.md")
+ log.Printf("Preparing to write %s to disk", fileName)
+
+ os.Remove(fileName)
+
+ dataMap := make(map[string]interface{})
+ dataMap["title"] = serviceName
+
+ // Render the Markdown file based on the template
+ t := template.Must(template.New("top").Parse(serviceMarkdown))
+ buf := &bytes.Buffer{}
+
+ if err := t.Execute(buf, dataMap); err != nil {
+ log.Printf("error while rendering Markdown file: %s", err.Error())
+ return fmt.Errorf("error while rendering Markdown file: %s", err.Error())
+ }
+ s := buf.String()
+
+ s = s + "\n {{% children %}} "
+
+ // creating file
+ err := createFileWithContent(fileName, s)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/webapp/layouts/shortcodes/oas3.html b/webapp/layouts/shortcodes/oas3.html
index e0faeb4..976a857 100644
--- a/webapp/layouts/shortcodes/oas3.html
+++ b/webapp/layouts/shortcodes/oas3.html
@@ -41,6 +41,7 @@
// Build a system
const ui = SwaggerUIBundle({
url: "{{ .Get "url" }}",
+ validatorUrl : false,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [