mirror of
				https://github.com/ClusterCockpit/cc-metric-store.git
				synced 2025-10-31 00:55:06 +01:00 
			
		
		
		
	Merge pull request #20 from ClusterCockpit/Enable-Swagger-documentation-for-REST-api
Enable swagger documentation for rest api
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -7,7 +7,7 @@ jobs: | |||||||
|       - name: Install Go |       - name: Install Go | ||||||
|         uses: actions/setup-go@v4 |         uses: actions/setup-go@v4 | ||||||
|         with: |         with: | ||||||
|           go-version: 1.20.x |           go-version: 1.22.x | ||||||
|       - name: Checkout code |       - name: Checkout code | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v3 | ||||||
|       - name: Build, Vet & Test |       - name: Build, Vet & Test | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ GIT_HASH := $(shell git rev-parse --short HEAD || echo 'development') | |||||||
| CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S") | CURRENT_TIME = $(shell date +"%Y-%m-%d:T%H:%M:%S") | ||||||
| LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}' | LD_FLAGS = '-s -X main.date=${CURRENT_TIME} -X main.version=${VERSION} -X main.commit=${GIT_HASH}' | ||||||
|  |  | ||||||
| .PHONY: clean test tags $(TARGET) | .PHONY: clean test tags swagger $(TARGET) | ||||||
|  |  | ||||||
| .NOTPARALLEL: | .NOTPARALLEL: | ||||||
|  |  | ||||||
| @@ -12,6 +12,11 @@ $(TARGET): | |||||||
| 	$(info ===>  BUILD cc-metric-store) | 	$(info ===>  BUILD cc-metric-store) | ||||||
| 	@go build -ldflags=${LD_FLAGS} ./cmd/cc-metric-store | 	@go build -ldflags=${LD_FLAGS} ./cmd/cc-metric-store | ||||||
|  |  | ||||||
|  | swagger: | ||||||
|  | 	$(info ===>  GENERATE swagger) | ||||||
|  | 	@go run github.com/swaggo/swag/cmd/swag init -d ./internal/api,./internal/util -g api.go -o ./api | ||||||
|  | 	@mv ./api/docs.go ./internal/api/docs.go | ||||||
|  |  | ||||||
| clean: | clean: | ||||||
| 	$(info ===>  CLEAN) | 	$(info ===>  CLEAN) | ||||||
| 	@go clean | 	@go clean | ||||||
|   | |||||||
							
								
								
									
										392
									
								
								api/swagger.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								api/swagger.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,392 @@ | |||||||
|  | { | ||||||
|  |     "swagger": "2.0", | ||||||
|  |     "info": { | ||||||
|  |         "description": "API for cc-metric-store", | ||||||
|  |         "title": "cc-metric-store REST API", | ||||||
|  |         "contact": { | ||||||
|  |             "name": "ClusterCockpit Project", | ||||||
|  |             "url": "https://clustercockpit.org", | ||||||
|  |             "email": "support@clustercockpit.org" | ||||||
|  |         }, | ||||||
|  |         "license": { | ||||||
|  |             "name": "MIT License", | ||||||
|  |             "url": "https://opensource.org/licenses/MIT" | ||||||
|  |         }, | ||||||
|  |         "version": "1.0.0" | ||||||
|  |     }, | ||||||
|  |     "host": "localhost:8082", | ||||||
|  |     "basePath": "/api/", | ||||||
|  |     "paths": { | ||||||
|  |         "/debug/": { | ||||||
|  |             "post": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "description": "Write metrics to store", | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "tags": [ | ||||||
|  |                     "debug" | ||||||
|  |                 ], | ||||||
|  |                 "summary": "Debug endpoint", | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "description": "Selector", | ||||||
|  |                         "name": "selector", | ||||||
|  |                         "in": "query" | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "Debug dump", | ||||||
|  |                         "schema": { | ||||||
|  |                             "type": "string" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "/free/": { | ||||||
|  |             "get": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "tags": [ | ||||||
|  |                     "free" | ||||||
|  |                 ], | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "description": "up to timestamp", | ||||||
|  |                         "name": "to", | ||||||
|  |                         "in": "query" | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "ok", | ||||||
|  |                         "schema": { | ||||||
|  |                             "type": "string" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "/query/": { | ||||||
|  |             "get": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "description": "Query metrics.", | ||||||
|  |                 "consumes": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "tags": [ | ||||||
|  |                     "query" | ||||||
|  |                 ], | ||||||
|  |                 "summary": "Query metrics", | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "description": "API query payload object", | ||||||
|  |                         "name": "request", | ||||||
|  |                         "in": "body", | ||||||
|  |                         "required": true, | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ApiQueryRequest" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "API query response object", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ApiQueryResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "/write/": { | ||||||
|  |             "post": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "consumes": [ | ||||||
|  |                     "text/plain" | ||||||
|  |                 ], | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "description": "If the lines in the body do not have a cluster tag, use this value instead.", | ||||||
|  |                         "name": "cluster", | ||||||
|  |                         "in": "query" | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "ok", | ||||||
|  |                         "schema": { | ||||||
|  |                             "type": "string" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     "definitions": { | ||||||
|  |         "api.ApiMetricData": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "avg": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "data": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "number" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "error": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "from": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 }, | ||||||
|  |                 "max": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "min": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "to": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ApiQuery": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "aggreg": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 }, | ||||||
|  |                 "host": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "metric": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "scale-by": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "subtype": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "subtype-ids": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "type": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "type-ids": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ApiQueryRequest": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "cluster": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "for-all-nodes": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "from": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 }, | ||||||
|  |                 "queries": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "$ref": "#/definitions/api.ApiQuery" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "to": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 }, | ||||||
|  |                 "with-data": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 }, | ||||||
|  |                 "with-padding": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 }, | ||||||
|  |                 "with-stats": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ApiQueryResponse": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "queries": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "$ref": "#/definitions/api.ApiQuery" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "results": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "array", | ||||||
|  |                         "items": { | ||||||
|  |                             "$ref": "#/definitions/api.ApiMetricData" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ErrorResponse": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "error": { | ||||||
|  |                     "description": "Error Message", | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "status": { | ||||||
|  |                     "description": "Statustext of Errorcode", | ||||||
|  |                     "type": "string" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     "securityDefinitions": { | ||||||
|  |         "ApiKeyAuth": { | ||||||
|  |             "type": "apiKey", | ||||||
|  |             "name": "X-Auth-Token", | ||||||
|  |             "in": "header" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										253
									
								
								api/swagger.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								api/swagger.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,253 @@ | |||||||
|  | basePath: /api/ | ||||||
|  | definitions: | ||||||
|  |   api.ApiMetricData: | ||||||
|  |     properties: | ||||||
|  |       avg: | ||||||
|  |         type: number | ||||||
|  |       data: | ||||||
|  |         items: | ||||||
|  |           type: number | ||||||
|  |         type: array | ||||||
|  |       error: | ||||||
|  |         type: string | ||||||
|  |       from: | ||||||
|  |         type: integer | ||||||
|  |       max: | ||||||
|  |         type: number | ||||||
|  |       min: | ||||||
|  |         type: number | ||||||
|  |       to: | ||||||
|  |         type: integer | ||||||
|  |     type: object | ||||||
|  |   api.ApiQuery: | ||||||
|  |     properties: | ||||||
|  |       aggreg: | ||||||
|  |         type: boolean | ||||||
|  |       host: | ||||||
|  |         type: string | ||||||
|  |       metric: | ||||||
|  |         type: string | ||||||
|  |       scale-by: | ||||||
|  |         type: number | ||||||
|  |       subtype: | ||||||
|  |         type: string | ||||||
|  |       subtype-ids: | ||||||
|  |         items: | ||||||
|  |           type: string | ||||||
|  |         type: array | ||||||
|  |       type: | ||||||
|  |         type: string | ||||||
|  |       type-ids: | ||||||
|  |         items: | ||||||
|  |           type: string | ||||||
|  |         type: array | ||||||
|  |     type: object | ||||||
|  |   api.ApiQueryRequest: | ||||||
|  |     properties: | ||||||
|  |       cluster: | ||||||
|  |         type: string | ||||||
|  |       for-all-nodes: | ||||||
|  |         items: | ||||||
|  |           type: string | ||||||
|  |         type: array | ||||||
|  |       from: | ||||||
|  |         type: integer | ||||||
|  |       queries: | ||||||
|  |         items: | ||||||
|  |           $ref: '#/definitions/api.ApiQuery' | ||||||
|  |         type: array | ||||||
|  |       to: | ||||||
|  |         type: integer | ||||||
|  |       with-data: | ||||||
|  |         type: boolean | ||||||
|  |       with-padding: | ||||||
|  |         type: boolean | ||||||
|  |       with-stats: | ||||||
|  |         type: boolean | ||||||
|  |     type: object | ||||||
|  |   api.ApiQueryResponse: | ||||||
|  |     properties: | ||||||
|  |       queries: | ||||||
|  |         items: | ||||||
|  |           $ref: '#/definitions/api.ApiQuery' | ||||||
|  |         type: array | ||||||
|  |       results: | ||||||
|  |         items: | ||||||
|  |           items: | ||||||
|  |             $ref: '#/definitions/api.ApiMetricData' | ||||||
|  |           type: array | ||||||
|  |         type: array | ||||||
|  |     type: object | ||||||
|  |   api.ErrorResponse: | ||||||
|  |     properties: | ||||||
|  |       error: | ||||||
|  |         description: Error Message | ||||||
|  |         type: string | ||||||
|  |       status: | ||||||
|  |         description: Statustext of Errorcode | ||||||
|  |         type: string | ||||||
|  |     type: object | ||||||
|  | host: localhost:8082 | ||||||
|  | info: | ||||||
|  |   contact: | ||||||
|  |     email: support@clustercockpit.org | ||||||
|  |     name: ClusterCockpit Project | ||||||
|  |     url: https://clustercockpit.org | ||||||
|  |   description: API for cc-metric-store | ||||||
|  |   license: | ||||||
|  |     name: MIT License | ||||||
|  |     url: https://opensource.org/licenses/MIT | ||||||
|  |   title: cc-metric-store REST API | ||||||
|  |   version: 1.0.0 | ||||||
|  | paths: | ||||||
|  |   /debug/: | ||||||
|  |     post: | ||||||
|  |       description: Write metrics to store | ||||||
|  |       parameters: | ||||||
|  |       - description: Selector | ||||||
|  |         in: query | ||||||
|  |         name: selector | ||||||
|  |         type: string | ||||||
|  |       produces: | ||||||
|  |       - application/json | ||||||
|  |       responses: | ||||||
|  |         "200": | ||||||
|  |           description: Debug dump | ||||||
|  |           schema: | ||||||
|  |             type: string | ||||||
|  |         "400": | ||||||
|  |           description: Bad Request | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "401": | ||||||
|  |           description: Unauthorized | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "403": | ||||||
|  |           description: Forbidden | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "500": | ||||||
|  |           description: Internal Server Error | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |       security: | ||||||
|  |       - ApiKeyAuth: [] | ||||||
|  |       summary: Debug endpoint | ||||||
|  |       tags: | ||||||
|  |       - debug | ||||||
|  |   /free/: | ||||||
|  |     get: | ||||||
|  |       parameters: | ||||||
|  |       - description: up to timestamp | ||||||
|  |         in: query | ||||||
|  |         name: to | ||||||
|  |         type: string | ||||||
|  |       produces: | ||||||
|  |       - application/json | ||||||
|  |       responses: | ||||||
|  |         "200": | ||||||
|  |           description: ok | ||||||
|  |           schema: | ||||||
|  |             type: string | ||||||
|  |         "400": | ||||||
|  |           description: Bad Request | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "401": | ||||||
|  |           description: Unauthorized | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "403": | ||||||
|  |           description: Forbidden | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "500": | ||||||
|  |           description: Internal Server Error | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |       security: | ||||||
|  |       - ApiKeyAuth: [] | ||||||
|  |       tags: | ||||||
|  |       - free | ||||||
|  |   /query/: | ||||||
|  |     get: | ||||||
|  |       consumes: | ||||||
|  |       - application/json | ||||||
|  |       description: Query metrics. | ||||||
|  |       parameters: | ||||||
|  |       - description: API query payload object | ||||||
|  |         in: body | ||||||
|  |         name: request | ||||||
|  |         required: true | ||||||
|  |         schema: | ||||||
|  |           $ref: '#/definitions/api.ApiQueryRequest' | ||||||
|  |       produces: | ||||||
|  |       - application/json | ||||||
|  |       responses: | ||||||
|  |         "200": | ||||||
|  |           description: API query response object | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ApiQueryResponse' | ||||||
|  |         "400": | ||||||
|  |           description: Bad Request | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "401": | ||||||
|  |           description: Unauthorized | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "403": | ||||||
|  |           description: Forbidden | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "500": | ||||||
|  |           description: Internal Server Error | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |       security: | ||||||
|  |       - ApiKeyAuth: [] | ||||||
|  |       summary: Query metrics | ||||||
|  |       tags: | ||||||
|  |       - query | ||||||
|  |   /write/: | ||||||
|  |     post: | ||||||
|  |       consumes: | ||||||
|  |       - text/plain | ||||||
|  |       parameters: | ||||||
|  |       - description: If the lines in the body do not have a cluster tag, use this | ||||||
|  |           value instead. | ||||||
|  |         in: query | ||||||
|  |         name: cluster | ||||||
|  |         type: string | ||||||
|  |       produces: | ||||||
|  |       - application/json | ||||||
|  |       responses: | ||||||
|  |         "200": | ||||||
|  |           description: ok | ||||||
|  |           schema: | ||||||
|  |             type: string | ||||||
|  |         "400": | ||||||
|  |           description: Bad Request | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "401": | ||||||
|  |           description: Unauthorized | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "403": | ||||||
|  |           description: Forbidden | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |         "500": | ||||||
|  |           description: Internal Server Error | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/api.ErrorResponse' | ||||||
|  |       security: | ||||||
|  |       - ApiKeyAuth: [] | ||||||
|  | securityDefinitions: | ||||||
|  |   ApiKeyAuth: | ||||||
|  |     in: header | ||||||
|  |     name: X-Auth-Token | ||||||
|  |     type: apiKey | ||||||
|  | swagger: "2.0" | ||||||
| @@ -1,11 +1,17 @@ | |||||||
|  | // Copyright (C) NHR@FAU, University Erlangen-Nuremberg. | ||||||
|  | // All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"crypto/tls" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"log" | 	"log" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"runtime" | 	"runtime" | ||||||
| @@ -17,7 +23,9 @@ import ( | |||||||
| 	"github.com/ClusterCockpit/cc-metric-store/internal/api" | 	"github.com/ClusterCockpit/cc-metric-store/internal/api" | ||||||
| 	"github.com/ClusterCockpit/cc-metric-store/internal/config" | 	"github.com/ClusterCockpit/cc-metric-store/internal/config" | ||||||
| 	"github.com/ClusterCockpit/cc-metric-store/internal/memorystore" | 	"github.com/ClusterCockpit/cc-metric-store/internal/memorystore" | ||||||
|  | 	"github.com/ClusterCockpit/cc-metric-store/internal/runtimeEnv" | ||||||
| 	"github.com/google/gops/agent" | 	"github.com/google/gops/agent" | ||||||
|  | 	httpSwagger "github.com/swaggo/http-swagger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @@ -28,9 +36,10 @@ var ( | |||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
| 	var configFile string | 	var configFile string | ||||||
| 	var enableGopsAgent, flagVersion bool | 	var enableGopsAgent, flagVersion, flagDev bool | ||||||
| 	flag.StringVar(&configFile, "config", "./config.json", "configuration file") | 	flag.StringVar(&configFile, "config", "./config.json", "configuration file") | ||||||
| 	flag.BoolVar(&enableGopsAgent, "gops", false, "Listen via github.com/google/gops/agent") | 	flag.BoolVar(&enableGopsAgent, "gops", false, "Listen via github.com/google/gops/agent") | ||||||
|  | 	flag.BoolVar(&flagDev, "dev", false, "Enable development Swagger UI component") | ||||||
| 	flag.BoolVar(&flagVersion, "version", false, "Show version information and exit") | 	flag.BoolVar(&flagVersion, "version", false, "Show version information and exit") | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
|  |  | ||||||
| @@ -81,35 +90,71 @@ func main() { | |||||||
| 	ctx, shutdown := context.WithCancel(context.Background()) | 	ctx, shutdown := context.WithCancel(context.Background()) | ||||||
|  |  | ||||||
| 	var wg sync.WaitGroup | 	var wg sync.WaitGroup | ||||||
| 	sigs := make(chan os.Signal, 1) |  | ||||||
| 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) |  | ||||||
| 	go func() { |  | ||||||
| 		for { |  | ||||||
| 			sig := <-sigs |  | ||||||
| 			if sig == syscall.SIGUSR1 { |  | ||||||
| 				ms.DebugDump(bufio.NewWriter(os.Stdout), nil) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			log.Println("Shutting down...") |  | ||||||
| 			shutdown() |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	wg.Add(3) | 	wg.Add(3) | ||||||
|  |  | ||||||
| 	memorystore.Retention(&wg, ctx) | 	memorystore.Retention(&wg, ctx) | ||||||
| 	memorystore.Checkpointing(&wg, ctx) | 	memorystore.Checkpointing(&wg, ctx) | ||||||
| 	memorystore.Archiving(&wg, ctx) | 	memorystore.Archiving(&wg, ctx) | ||||||
|  |  | ||||||
| 	wg.Add(1) | 	r := http.NewServeMux() | ||||||
|  | 	api.MountRoutes(r) | ||||||
|  |  | ||||||
| 	go func() { | 	if flagDev { | ||||||
| 		err := api.StartApiServer(ctx, config.Keys.HttpConfig) | 		log.Print("Enable Swagger UI!") | ||||||
|  | 		r.HandleFunc("GET /swagger/", httpSwagger.Handler( | ||||||
|  | 			httpSwagger.URL("http://"+config.Keys.HttpConfig.Address+"/swagger/doc.json"))) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	server := &http.Server{ | ||||||
|  | 		Handler:      r, | ||||||
|  | 		Addr:         config.Keys.HttpConfig.Address, | ||||||
|  | 		WriteTimeout: 30 * time.Second, | ||||||
|  | 		ReadTimeout:  30 * time.Second, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Start http or https server | ||||||
|  | 	listener, err := net.Listen("tcp", config.Keys.HttpConfig.Address) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("starting http listener failed: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if config.Keys.HttpConfig.CertFile != "" && config.Keys.HttpConfig.KeyFile != "" { | ||||||
|  | 		cert, err := tls.LoadX509KeyPair(config.Keys.HttpConfig.CertFile, config.Keys.HttpConfig.KeyFile) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Fatal(err) | 			log.Fatalf("loading X509 keypair failed: %v", err) | ||||||
| 		} | 		} | ||||||
| 		wg.Done() | 		listener = tls.NewListener(listener, &tls.Config{ | ||||||
|  | 			Certificates: []tls.Certificate{cert}, | ||||||
|  | 			CipherSuites: []uint16{ | ||||||
|  | 				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | ||||||
|  | 				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||||||
|  | 			}, | ||||||
|  | 			MinVersion:               tls.VersionTLS12, | ||||||
|  | 			PreferServerCipherSuites: true, | ||||||
|  | 		}) | ||||||
|  | 		fmt.Printf("HTTPS server listening at %s...", config.Keys.HttpConfig.Address) | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Printf("HTTP server listening at %s...", config.Keys.HttpConfig.Address) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg.Add(1) | ||||||
|  | 	go func() { | ||||||
|  | 		defer wg.Done() | ||||||
|  | 		if err = server.Serve(listener); err != nil && err != http.ErrServerClosed { | ||||||
|  | 			log.Fatalf("starting server failed: %v", err) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	wg.Add(1) | ||||||
|  | 	sigs := make(chan os.Signal, 1) | ||||||
|  | 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | ||||||
|  | 	go func() { | ||||||
|  | 		defer wg.Done() | ||||||
|  | 		<-sigs | ||||||
|  | 		runtimeEnv.SystemdNotifiy(false, "Shutting down ...") | ||||||
|  | 		server.Shutdown(context.Background()) | ||||||
|  | 		shutdown() | ||||||
|  | 		memorystore.Shutdown() | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	if config.Keys.Nats != nil { | 	if config.Keys.Nats != nil { | ||||||
| @@ -128,6 +173,7 @@ func main() { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	runtimeEnv.SystemdNotifiy(true, "running") | ||||||
| 	wg.Wait() | 	wg.Wait() | ||||||
| 	memorystore.Shutdown() | 	log.Print("Graceful shutdown completed!") | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								config.json
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								config.json
									
									
									
									
									
								
							| @@ -1,27 +1,45 @@ | |||||||
| { | { | ||||||
|     "metrics": { |   "metrics": { | ||||||
|         "flops_any":  { "frequency": 15, "aggregation": "sum" }, |     "flops_any": { | ||||||
|         "flops_dp":   { "frequency": 15, "aggregation": "sum" }, |       "frequency": 15, | ||||||
|         "flops_sp":   { "frequency": 15, "aggregation": "sum" }, |       "aggregation": "sum" | ||||||
|         "mem_bw":     { "frequency": 15, "aggregation": "sum" }, |  | ||||||
|         "load_one":   { "frequency": 15, "aggregation": null  }, |  | ||||||
|         "load_five":  { "frequency": 15, "aggregation": null  } |  | ||||||
|     }, |     }, | ||||||
|     "checkpoints": { |     "flops_dp": { | ||||||
|         "interval": "12h", |       "frequency": 15, | ||||||
|         "directory": "./var/checkpoints", |       "aggregation": "sum" | ||||||
|         "restore": "48h" |  | ||||||
|     }, |     }, | ||||||
|     "archive": { |     "flops_sp": { | ||||||
|         "interval": "168h", |       "frequency": 15, | ||||||
|         "directory": "./var/archive" |       "aggregation": "sum" | ||||||
|     }, |     }, | ||||||
|     "http-api": { |     "mem_bw": { | ||||||
|         "address": "0.0.0.0:8081", |       "frequency": 15, | ||||||
|         "https-cert-file": null, |       "aggregation": "sum" | ||||||
|         "https-key-file": null |  | ||||||
|     }, |     }, | ||||||
|     "retention-in-memory": "48h", |     "load_one": { | ||||||
|     "nats": null, |       "frequency": 15, | ||||||
|     "jwt-public-key": "kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0=" |       "aggregation": null | ||||||
|  |     }, | ||||||
|  |     "load_five": { | ||||||
|  |       "frequency": 15, | ||||||
|  |       "aggregation": null | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "checkpoints": { | ||||||
|  |     "interval": "12h", | ||||||
|  |     "directory": "./var/checkpoints", | ||||||
|  |     "restore": "48h" | ||||||
|  |   }, | ||||||
|  |   "archive": { | ||||||
|  |     "interval": "168h", | ||||||
|  |     "directory": "./var/archive" | ||||||
|  |   }, | ||||||
|  |   "http-api": { | ||||||
|  |     "address": "127.0.0.1:8081", | ||||||
|  |     "https-cert-file": null, | ||||||
|  |     "https-key-file": null | ||||||
|  |   }, | ||||||
|  |   "retention-in-memory": "48h", | ||||||
|  |   "nats": null, | ||||||
|  |   "jwt-public-key": "kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0=" | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,19 +1,37 @@ | |||||||
| module github.com/ClusterCockpit/cc-metric-store | module github.com/ClusterCockpit/cc-metric-store | ||||||
|  |  | ||||||
| go 1.19 | go 1.22 | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/golang-jwt/jwt/v4 v4.5.0 | 	github.com/golang-jwt/jwt/v4 v4.5.0 | ||||||
| 	github.com/google/gops v0.3.28 | 	github.com/google/gops v0.3.28 | ||||||
| 	github.com/gorilla/mux v1.8.1 |  | ||||||
| 	github.com/influxdata/line-protocol/v2 v2.2.1 | 	github.com/influxdata/line-protocol/v2 v2.2.1 | ||||||
| 	github.com/nats-io/nats.go v1.33.1 | 	github.com/nats-io/nats.go v1.36.0 | ||||||
|  | 	github.com/swaggo/http-swagger v1.3.4 | ||||||
|  | 	github.com/swaggo/swag v1.16.3 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/klauspost/compress v1.17.7 // indirect | 	github.com/KyleBanks/depth v1.2.1 // indirect | ||||||
|  | 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | ||||||
|  | 	github.com/go-openapi/jsonpointer v0.21.0 // indirect | ||||||
|  | 	github.com/go-openapi/jsonreference v0.21.0 // indirect | ||||||
|  | 	github.com/go-openapi/spec v0.21.0 // indirect | ||||||
|  | 	github.com/go-openapi/swag v0.23.0 // indirect | ||||||
|  | 	github.com/josharian/intern v1.0.0 // indirect | ||||||
|  | 	github.com/klauspost/compress v1.17.9 // indirect | ||||||
|  | 	github.com/mailru/easyjson v0.7.7 // indirect | ||||||
| 	github.com/nats-io/nkeys v0.4.7 // indirect | 	github.com/nats-io/nkeys v0.4.7 // indirect | ||||||
| 	github.com/nats-io/nuid v1.0.1 // indirect | 	github.com/nats-io/nuid v1.0.1 // indirect | ||||||
| 	golang.org/x/crypto v0.21.0 // indirect | 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||||
| 	golang.org/x/sys v0.18.0 // indirect | 	github.com/swaggo/files v1.0.1 // indirect | ||||||
|  | 	github.com/urfave/cli/v2 v2.27.1 // indirect | ||||||
|  | 	github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect | ||||||
|  | 	golang.org/x/crypto v0.24.0 // indirect | ||||||
|  | 	golang.org/x/net v0.26.0 // indirect | ||||||
|  | 	golang.org/x/sys v0.21.0 // indirect | ||||||
|  | 	golang.org/x/text v0.16.0 // indirect | ||||||
|  | 	golang.org/x/tools v0.22.0 // indirect | ||||||
|  | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
|  | 	sigs.k8s.io/yaml v1.4.0 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										105
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,17 +1,31 @@ | |||||||
|  | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= | ||||||
|  | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= | ||||||
|  | github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= | ||||||
|  | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||||
|  | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
|  | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= | github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= | ||||||
| github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= | github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= | ||||||
| github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= | github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= | ||||||
| github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= | github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= | ||||||
|  | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= | ||||||
|  | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= | ||||||
|  | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= | ||||||
|  | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= | ||||||
|  | github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= | ||||||
|  | github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= | ||||||
|  | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= | ||||||
|  | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= | ||||||
| github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= | ||||||
| github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= | ||||||
| github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= |  | ||||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
|  | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
| github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark= | github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark= | ||||||
| github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c= | github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c= | ||||||
| github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= |  | ||||||
| github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= |  | ||||||
| github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98= | github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98= | ||||||
| github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig= | github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig= | ||||||
| github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo= | github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo= | ||||||
| @@ -19,27 +33,92 @@ github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod | |||||||
| github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY= | github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY= | ||||||
| github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE= | github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE= | ||||||
| github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM= | github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM= | ||||||
| github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||||||
| github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||||||
| github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= | ||||||
|  | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= | ||||||
| github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||||||
|  | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||||||
|  | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/nats-io/nats.go v1.33.1 h1:8TxLZZ/seeEfR97qV0/Bl939tpDnt2Z2fK3HkPypj70= | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= | ||||||
| github.com/nats-io/nats.go v1.33.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | ||||||
|  | github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU= | ||||||
|  | github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= | ||||||
| github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= | github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= | ||||||
| github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= | github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= | ||||||
| github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= | ||||||
| github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= | ||||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||||
| golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
| golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
| golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= | ||||||
| golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | ||||||
|  | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||||
|  | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||||
|  | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
|  | github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= | ||||||
|  | github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= | ||||||
|  | github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= | ||||||
|  | github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= | ||||||
|  | github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= | ||||||
|  | github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= | ||||||
|  | github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= | ||||||
|  | github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= | ||||||
|  | github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= | ||||||
|  | github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= | ||||||
|  | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
|  | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
|  | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||||
|  | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= | ||||||
|  | golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= | ||||||
|  | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||||
|  | golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= | ||||||
|  | golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||||
|  | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||||
|  | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||||
|  | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||||
|  | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= | ||||||
|  | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= | ||||||
|  | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= | ||||||
|  | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
|  | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= | ||||||
|  | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
|  | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
|  | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||||
|  | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||||||
|  | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
|  | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
|  | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||||
|  | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||||
|  | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= | ||||||
|  | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= | ||||||
|  | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
|  | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||||
|  | golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= | ||||||
|  | golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= | ||||||
|  | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
|  | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= | ||||||
|  | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= | ||||||
|   | |||||||
| @@ -1,11 +1,13 @@ | |||||||
|  | // Copyright (C) NHR@FAU, University Erlangen-Nuremberg. | ||||||
|  | // All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
| package api | package api | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" | 	"bufio" | ||||||
| 	"context" |  | ||||||
| 	"crypto/ed25519" |  | ||||||
| 	"encoding/base64" |  | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| @@ -13,25 +15,57 @@ import ( | |||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/ClusterCockpit/cc-metric-store/internal/config" |  | ||||||
| 	"github.com/ClusterCockpit/cc-metric-store/internal/memorystore" | 	"github.com/ClusterCockpit/cc-metric-store/internal/memorystore" | ||||||
| 	"github.com/ClusterCockpit/cc-metric-store/internal/util" | 	"github.com/ClusterCockpit/cc-metric-store/internal/util" | ||||||
| 	"github.com/gorilla/mux" |  | ||||||
| 	"github.com/influxdata/line-protocol/v2/lineprotocol" | 	"github.com/influxdata/line-protocol/v2/lineprotocol" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // @title                      cc-metric-store REST API | ||||||
|  | // @version                    1.0.0 | ||||||
|  | // @description                API for cc-metric-store | ||||||
|  |  | ||||||
|  | // @contact.name               ClusterCockpit Project | ||||||
|  | // @contact.url                https://clustercockpit.org | ||||||
|  | // @contact.email              support@clustercockpit.org | ||||||
|  |  | ||||||
|  | // @license.name               MIT License | ||||||
|  | // @license.url                https://opensource.org/licenses/MIT | ||||||
|  |  | ||||||
|  | // @host                       localhost:8082 | ||||||
|  | // @basePath                   /api/ | ||||||
|  |  | ||||||
|  | // @securityDefinitions.apikey ApiKeyAuth | ||||||
|  | // @in                         header | ||||||
|  | // @name                       X-Auth-Token | ||||||
|  |  | ||||||
|  | // ErrorResponse model | ||||||
|  | type ErrorResponse struct { | ||||||
|  | 	// Statustext of Errorcode | ||||||
|  | 	Status string `json:"status"` | ||||||
|  | 	Error  string `json:"error"` // Error Message | ||||||
|  | } | ||||||
|  |  | ||||||
| type ApiMetricData struct { | type ApiMetricData struct { | ||||||
| 	Error *string    `json:"error,omitempty"` | 	Error *string         `json:"error,omitempty"` | ||||||
| 	Data  util.FloatArray `json:"data,omitempty"` | 	Data  util.FloatArray `json:"data,omitempty"` | ||||||
| 	From  int64      `json:"from"` | 	From  int64           `json:"from"` | ||||||
| 	To    int64      `json:"to"` | 	To    int64           `json:"to"` | ||||||
| 	Avg   util.Float      `json:"avg"` | 	Avg   util.Float      `json:"avg"` | ||||||
| 	Min   util.Float      `json:"min"` | 	Min   util.Float      `json:"min"` | ||||||
| 	Max   util.Float      `json:"max"` | 	Max   util.Float      `json:"max"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func handleError(err error, statusCode int, rw http.ResponseWriter) { | ||||||
|  | 	// log.Warnf("REST ERROR : %s", err.Error()) | ||||||
|  | 	rw.Header().Add("Content-Type", "application/json") | ||||||
|  | 	rw.WriteHeader(statusCode) | ||||||
|  | 	json.NewEncoder(rw).Encode(ErrorResponse{ | ||||||
|  | 		Status: http.StatusText(statusCode), | ||||||
|  | 		Error:  err.Error(), | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
| // TODO: Optimize this, just like the stats endpoint! | // TODO: Optimize this, just like the stats endpoint! | ||||||
| func (data *ApiMetricData) AddStats() { | func (data *ApiMetricData) AddStats() { | ||||||
| 	n := 0 | 	n := 0 | ||||||
| @@ -89,16 +123,29 @@ func (data *ApiMetricData) PadDataWithNull(ms *memorystore.MemoryStore, from, to | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // handleFree godoc | ||||||
|  | // @summary | ||||||
|  | // @tags free | ||||||
|  | // @description | ||||||
|  | // @produce     json | ||||||
|  | // @param       to        query    string        false  "up to timestamp" | ||||||
|  | // @success     200            {string} string  "ok" | ||||||
|  | // @failure     400            {object} api.ErrorResponse       "Bad Request" | ||||||
|  | // @failure     401            {object} api.ErrorResponse       "Unauthorized" | ||||||
|  | // @failure     403            {object} api.ErrorResponse       "Forbidden" | ||||||
|  | // @failure     500            {object} api.ErrorResponse       "Internal Server Error" | ||||||
|  | // @security    ApiKeyAuth | ||||||
|  | // @router      /free/ [get] | ||||||
| func handleFree(rw http.ResponseWriter, r *http.Request) { | func handleFree(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	rawTo := r.URL.Query().Get("to") | 	rawTo := r.URL.Query().Get("to") | ||||||
| 	if rawTo == "" { | 	if rawTo == "" { | ||||||
| 		http.Error(rw, "'to' is a required query parameter", http.StatusBadRequest) | 		handleError(errors.New("'to' is a required query parameter"), http.StatusBadRequest, rw) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	to, err := strconv.ParseInt(rawTo, 10, 64) | 	to, err := strconv.ParseInt(rawTo, 10, 64) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		http.Error(rw, err.Error(), http.StatusBadRequest) | 		handleError(err, http.StatusInternalServerError, rw) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -109,11 +156,6 @@ func handleFree(rw http.ResponseWriter, r *http.Request) { | |||||||
| 	// 	freeUpTo = to | 	// 	freeUpTo = to | ||||||
| 	// } | 	// } | ||||||
|  |  | ||||||
| 	if r.Method != http.MethodPost { |  | ||||||
| 		http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	bodyDec := json.NewDecoder(r.Body) | 	bodyDec := json.NewDecoder(r.Body) | ||||||
| 	var selectors [][]string | 	var selectors [][]string | ||||||
| 	err = bodyDec.Decode(&selectors) | 	err = bodyDec.Decode(&selectors) | ||||||
| @@ -127,7 +169,7 @@ func handleFree(rw http.ResponseWriter, r *http.Request) { | |||||||
| 	for _, sel := range selectors { | 	for _, sel := range selectors { | ||||||
| 		bn, err := ms.Free(sel, to) | 		bn, err := ms.Free(sel, to) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			http.Error(rw, err.Error(), http.StatusInternalServerError) | 			handleError(err, http.StatusInternalServerError, rw) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -138,16 +180,26 @@ func handleFree(rw http.ResponseWriter, r *http.Request) { | |||||||
| 	fmt.Fprintf(rw, "buffers freed: %d\n", n) | 	fmt.Fprintf(rw, "buffers freed: %d\n", n) | ||||||
| } | } | ||||||
|  |  | ||||||
| func handleWrite(rw http.ResponseWriter, r *http.Request) { | // handleWrite godoc | ||||||
| 	if r.Method != http.MethodPost { | // @summary Receive metrics in line-protocol | ||||||
| 		http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed) | // @tags write | ||||||
| 		return | // @description Receives metrics in the influx line-protocol using [this format](https://github.com/ClusterCockpit/cc-specifications/blob/master/metrics/lineprotocol_alternative.md) | ||||||
| 	} |  | ||||||
|  |  | ||||||
|  | // @accept      plain | ||||||
|  | // @produce     json | ||||||
|  | // @param       cluster        query string false "If the lines in the body do not have a cluster tag, use this value instead." | ||||||
|  | // @success     200            {string} string  "ok" | ||||||
|  | // @failure     400            {object} api.ErrorResponse       "Bad Request" | ||||||
|  | // @failure     401            {object} api.ErrorResponse       "Unauthorized" | ||||||
|  | // @failure     403            {object} api.ErrorResponse       "Forbidden" | ||||||
|  | // @failure     500            {object} api.ErrorResponse       "Internal Server Error" | ||||||
|  | // @security    ApiKeyAuth | ||||||
|  | // @router      /write/ [post] | ||||||
|  | func handleWrite(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	bytes, err := io.ReadAll(r.Body) | 	bytes, err := io.ReadAll(r.Body) | ||||||
|  | 	rw.Header().Add("Content-Type", "application/json") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Printf("error while reading request body: %s", err.Error()) | 		handleError(err, http.StatusInternalServerError, rw) | ||||||
| 		http.Error(rw, err.Error(), http.StatusInternalServerError) |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -155,7 +207,7 @@ func handleWrite(rw http.ResponseWriter, r *http.Request) { | |||||||
| 	dec := lineprotocol.NewDecoderWithBytes(bytes) | 	dec := lineprotocol.NewDecoderWithBytes(bytes) | ||||||
| 	if err := decodeLine(dec, ms, r.URL.Query().Get("cluster")); err != nil { | 	if err := decodeLine(dec, ms, r.URL.Query().Get("cluster")); err != nil { | ||||||
| 		log.Printf("/api/write error: %s", err.Error()) | 		log.Printf("/api/write error: %s", err.Error()) | ||||||
| 		http.Error(rw, err.Error(), http.StatusBadRequest) | 		handleError(err, http.StatusBadRequest, rw) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	rw.WriteHeader(http.StatusOK) | 	rw.WriteHeader(http.StatusOK) | ||||||
| @@ -180,19 +232,33 @@ type ApiQueryResponse struct { | |||||||
| type ApiQuery struct { | type ApiQuery struct { | ||||||
| 	Type        *string    `json:"type,omitempty"` | 	Type        *string    `json:"type,omitempty"` | ||||||
| 	SubType     *string    `json:"subtype,omitempty"` | 	SubType     *string    `json:"subtype,omitempty"` | ||||||
| 	Metric      string   `json:"metric"` | 	Metric      string     `json:"metric"` | ||||||
| 	Hostname    string   `json:"host"` | 	Hostname    string     `json:"host"` | ||||||
| 	TypeIds     []string `json:"type-ids,omitempty"` | 	TypeIds     []string   `json:"type-ids,omitempty"` | ||||||
| 	SubTypeIds  []string `json:"subtype-ids,omitempty"` | 	SubTypeIds  []string   `json:"subtype-ids,omitempty"` | ||||||
| 	ScaleFactor util.Float `json:"scale-by,omitempty"` | 	ScaleFactor util.Float `json:"scale-by,omitempty"` | ||||||
| 	Aggregate   bool       `json:"aggreg"` | 	Aggregate   bool       `json:"aggreg"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // handleQuery godoc | ||||||
|  | // @summary    Query metrics | ||||||
|  | // @tags query | ||||||
|  | // @description Query metrics. | ||||||
|  | // @accept      json | ||||||
|  | // @produce     json | ||||||
|  | // @param       request body     api.ApiQueryRequest  true "API query payload object" | ||||||
|  | // @success     200            {object} api.ApiQueryResponse  "API query response object" | ||||||
|  | // @failure     400            {object} api.ErrorResponse       "Bad Request" | ||||||
|  | // @failure     401   		   {object} api.ErrorResponse       "Unauthorized" | ||||||
|  | // @failure     403            {object} api.ErrorResponse       "Forbidden" | ||||||
|  | // @failure     500            {object} api.ErrorResponse       "Internal Server Error" | ||||||
|  | // @security    ApiKeyAuth | ||||||
|  | // @router      /query/ [get] | ||||||
| func handleQuery(rw http.ResponseWriter, r *http.Request) { | func handleQuery(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	var err error | 	var err error | ||||||
| 	req := ApiQueryRequest{WithStats: true, WithData: true, WithPadding: true} | 	req := ApiQueryRequest{WithStats: true, WithData: true, WithPadding: true} | ||||||
| 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||||||
| 		http.Error(rw, err.Error(), http.StatusBadRequest) | 		handleError(err, http.StatusBadRequest, rw) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -305,64 +371,30 @@ func handleQuery(rw http.ResponseWriter, r *http.Request) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // handleDebug godoc | ||||||
|  | // @summary Debug endpoint | ||||||
|  | // @tags debug | ||||||
|  | // @description Write metrics to store | ||||||
|  | // @produce     json | ||||||
|  | // @param       selector        query    string            false "Selector" | ||||||
|  | // @success     200            {string} string  "Debug dump" | ||||||
|  | // @failure     400            {object} api.ErrorResponse       "Bad Request" | ||||||
|  | // @failure     401            {object} api.ErrorResponse       "Unauthorized" | ||||||
|  | // @failure     403            {object} api.ErrorResponse       "Forbidden" | ||||||
|  | // @failure     500            {object} api.ErrorResponse       "Internal Server Error" | ||||||
|  | // @security    ApiKeyAuth | ||||||
|  | // @router      /debug/ [post] | ||||||
| func handleDebug(rw http.ResponseWriter, r *http.Request) { | func handleDebug(rw http.ResponseWriter, r *http.Request) { | ||||||
| 	raw := r.URL.Query().Get("selector") | 	raw := r.URL.Query().Get("selector") | ||||||
|  | 	rw.Header().Add("Content-Type", "application/json") | ||||||
| 	selector := []string{} | 	selector := []string{} | ||||||
| 	if len(raw) != 0 { | 	if len(raw) != 0 { | ||||||
| 		selector = strings.Split(raw, ":") | 		selector = strings.Split(raw, ":") | ||||||
| 		} | 	} | ||||||
|  |  | ||||||
| 	ms := memorystore.GetMemoryStore() | 	ms := memorystore.GetMemoryStore() | ||||||
| 	if err := ms.DebugDump(bufio.NewWriter(rw), selector); err != nil { | 	if err := ms.DebugDump(bufio.NewWriter(rw), selector); err != nil { | ||||||
| 		rw.WriteHeader(http.StatusBadRequest) | 		handleError(err, http.StatusBadRequest, rw) | ||||||
| 		rw.Write([]byte(err.Error())) | 		return | ||||||
| 		} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func StartApiServer(ctx context.Context, httpConfig *config.HttpConfig) error { |  | ||||||
| 	r := mux.NewRouter() |  | ||||||
|  |  | ||||||
| 	r.HandleFunc("/api/free", handleFree) |  | ||||||
| 	r.HandleFunc("/api/write", handleWrite) |  | ||||||
| 	r.HandleFunc("/api/query", handleQuery) |  | ||||||
| 	r.HandleFunc("/api/debug", handleDebug) |  | ||||||
|  |  | ||||||
| 	server := &http.Server{ |  | ||||||
| 		Handler:      r, |  | ||||||
| 		Addr:         httpConfig.Address, |  | ||||||
| 		WriteTimeout: 30 * time.Second, |  | ||||||
| 		ReadTimeout:  30 * time.Second, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(config.Keys.JwtPublicKey) > 0 { |  | ||||||
| 		buf, err := base64.StdEncoding.DecodeString(config.Keys.JwtPublicKey) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		publicKey := ed25519.PublicKey(buf) |  | ||||||
| 		server.Handler = authentication(server.Handler, publicKey) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	go func() { |  | ||||||
| 		if httpConfig.CertFile != "" && httpConfig.KeyFile != "" { |  | ||||||
| 			log.Printf("API https endpoint listening on '%s'\n", httpConfig.Address) |  | ||||||
| 			err := server.ListenAndServeTLS(httpConfig.CertFile, httpConfig.KeyFile) |  | ||||||
| 			if err != nil && err != http.ErrServerClosed { |  | ||||||
| 				log.Println(err) |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			log.Printf("API http endpoint listening on '%s'\n", httpConfig.Address) |  | ||||||
| 			err := server.ListenAndServe() |  | ||||||
| 			if err != nil && err != http.ErrServerClosed { |  | ||||||
| 				log.Println(err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		<-ctx.Done() |  | ||||||
| 		err := server.Shutdown(context.Background()) |  | ||||||
| 		log.Println("API server shut down") |  | ||||||
| 		return err |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| 	"github.com/golang-jwt/jwt/v4" | 	"github.com/golang-jwt/jwt/v4" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func authentication(next http.Handler, publicKey ed25519.PublicKey) http.Handler { | func authHandler(next http.Handler, publicKey ed25519.PublicKey) http.Handler { | ||||||
| 	cacheLock := sync.RWMutex{} | 	cacheLock := sync.RWMutex{} | ||||||
| 	cache := map[string]*jwt.Token{} | 	cache := map[string]*jwt.Token{} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										416
									
								
								internal/api/docs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								internal/api/docs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,416 @@ | |||||||
|  | // Package api Code generated by swaggo/swag. DO NOT EDIT | ||||||
|  | package api | ||||||
|  |  | ||||||
|  | import "github.com/swaggo/swag" | ||||||
|  |  | ||||||
|  | const docTemplate = `{ | ||||||
|  |     "schemes": {{ marshal .Schemes }}, | ||||||
|  |     "swagger": "2.0", | ||||||
|  |     "info": { | ||||||
|  |         "description": "{{escape .Description}}", | ||||||
|  |         "title": "{{.Title}}", | ||||||
|  |         "contact": { | ||||||
|  |             "name": "ClusterCockpit Project", | ||||||
|  |             "url": "https://clustercockpit.org", | ||||||
|  |             "email": "support@clustercockpit.org" | ||||||
|  |         }, | ||||||
|  |         "license": { | ||||||
|  |             "name": "MIT License", | ||||||
|  |             "url": "https://opensource.org/licenses/MIT" | ||||||
|  |         }, | ||||||
|  |         "version": "{{.Version}}" | ||||||
|  |     }, | ||||||
|  |     "host": "{{.Host}}", | ||||||
|  |     "basePath": "{{.BasePath}}", | ||||||
|  |     "paths": { | ||||||
|  |         "/debug/": { | ||||||
|  |             "post": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "description": "Write metrics to store", | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "tags": [ | ||||||
|  |                     "debug" | ||||||
|  |                 ], | ||||||
|  |                 "summary": "Debug endpoint", | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "description": "Selector", | ||||||
|  |                         "name": "selector", | ||||||
|  |                         "in": "query" | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "Debug dump", | ||||||
|  |                         "schema": { | ||||||
|  |                             "type": "string" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "/free/": { | ||||||
|  |             "get": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "tags": [ | ||||||
|  |                     "free" | ||||||
|  |                 ], | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "description": "up to timestamp", | ||||||
|  |                         "name": "to", | ||||||
|  |                         "in": "query" | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "ok", | ||||||
|  |                         "schema": { | ||||||
|  |                             "type": "string" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "/query/": { | ||||||
|  |             "get": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "description": "Query metrics.", | ||||||
|  |                 "consumes": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "tags": [ | ||||||
|  |                     "query" | ||||||
|  |                 ], | ||||||
|  |                 "summary": "Query metrics", | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "description": "API query payload object", | ||||||
|  |                         "name": "request", | ||||||
|  |                         "in": "body", | ||||||
|  |                         "required": true, | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ApiQueryRequest" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "API query response object", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ApiQueryResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "/write/": { | ||||||
|  |             "post": { | ||||||
|  |                 "security": [ | ||||||
|  |                     { | ||||||
|  |                         "ApiKeyAuth": [] | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "consumes": [ | ||||||
|  |                     "text/plain" | ||||||
|  |                 ], | ||||||
|  |                 "produces": [ | ||||||
|  |                     "application/json" | ||||||
|  |                 ], | ||||||
|  |                 "parameters": [ | ||||||
|  |                     { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "description": "If the lines in the body do not have a cluster tag, use this value instead.", | ||||||
|  |                         "name": "cluster", | ||||||
|  |                         "in": "query" | ||||||
|  |                     } | ||||||
|  |                 ], | ||||||
|  |                 "responses": { | ||||||
|  |                     "200": { | ||||||
|  |                         "description": "ok", | ||||||
|  |                         "schema": { | ||||||
|  |                             "type": "string" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "400": { | ||||||
|  |                         "description": "Bad Request", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "401": { | ||||||
|  |                         "description": "Unauthorized", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "403": { | ||||||
|  |                         "description": "Forbidden", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "500": { | ||||||
|  |                         "description": "Internal Server Error", | ||||||
|  |                         "schema": { | ||||||
|  |                             "$ref": "#/definitions/api.ErrorResponse" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     "definitions": { | ||||||
|  |         "api.ApiMetricData": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "avg": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "data": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "number" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "error": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "from": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 }, | ||||||
|  |                 "max": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "min": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "to": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ApiQuery": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "aggreg": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 }, | ||||||
|  |                 "host": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "metric": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "scale-by": { | ||||||
|  |                     "type": "number" | ||||||
|  |                 }, | ||||||
|  |                 "subtype": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "subtype-ids": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "type": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "type-ids": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ApiQueryRequest": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "cluster": { | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "for-all-nodes": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "from": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 }, | ||||||
|  |                 "queries": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "$ref": "#/definitions/api.ApiQuery" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "to": { | ||||||
|  |                     "type": "integer" | ||||||
|  |                 }, | ||||||
|  |                 "with-data": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 }, | ||||||
|  |                 "with-padding": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 }, | ||||||
|  |                 "with-stats": { | ||||||
|  |                     "type": "boolean" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ApiQueryResponse": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "queries": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "$ref": "#/definitions/api.ApiQuery" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "results": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "array", | ||||||
|  |                         "items": { | ||||||
|  |                             "$ref": "#/definitions/api.ApiMetricData" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "api.ErrorResponse": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "error": { | ||||||
|  |                     "description": "Error Message", | ||||||
|  |                     "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "status": { | ||||||
|  |                     "description": "Statustext of Errorcode", | ||||||
|  |                     "type": "string" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     "securityDefinitions": { | ||||||
|  |         "ApiKeyAuth": { | ||||||
|  |             "type": "apiKey", | ||||||
|  |             "name": "X-Auth-Token", | ||||||
|  |             "in": "header" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }` | ||||||
|  |  | ||||||
|  | // SwaggerInfo holds exported Swagger Info so clients can modify it | ||||||
|  | var SwaggerInfo = &swag.Spec{ | ||||||
|  | 	Version:          "1.0.0", | ||||||
|  | 	Host:             "localhost:8082", | ||||||
|  | 	BasePath:         "/api/", | ||||||
|  | 	Schemes:          []string{}, | ||||||
|  | 	Title:            "cc-metric-store REST API", | ||||||
|  | 	Description:      "API for cc-metric-store", | ||||||
|  | 	InfoInstanceName: "swagger", | ||||||
|  | 	SwaggerTemplate:  docTemplate, | ||||||
|  | 	LeftDelim:        "{{", | ||||||
|  | 	RightDelim:       "}}", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								internal/api/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								internal/api/server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | // Copyright (C) NHR@FAU, University Erlangen-Nuremberg. | ||||||
|  | // All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  | package api | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/ed25519" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"github.com/ClusterCockpit/cc-metric-store/internal/config" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func MountRoutes(r *http.ServeMux) { | ||||||
|  | 	if len(config.Keys.JwtPublicKey) > 0 { | ||||||
|  | 		buf, err := base64.StdEncoding.DecodeString(config.Keys.JwtPublicKey) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("starting server failed: %v", err) | ||||||
|  | 		} | ||||||
|  | 		publicKey := ed25519.PublicKey(buf) | ||||||
|  | 		r.Handle("POST /api/free/", authHandler(http.HandlerFunc(handleFree), publicKey)) | ||||||
|  | 		r.Handle("POST /api/write/", authHandler(http.HandlerFunc(handleWrite), publicKey)) | ||||||
|  | 		r.Handle("GET /api/query/", authHandler(http.HandlerFunc(handleQuery), publicKey)) | ||||||
|  | 		r.Handle("GET /api/debug/", authHandler(http.HandlerFunc(handleDebug), publicKey)) | ||||||
|  | 	} else { | ||||||
|  | 		r.HandleFunc("POST /api/free/", handleFree) | ||||||
|  | 		r.HandleFunc("POST /api/write/", handleWrite) | ||||||
|  | 		r.HandleFunc("GET /api/query/", handleQuery) | ||||||
|  | 		r.HandleFunc("GET /api/debug/", handleDebug) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										140
									
								
								internal/runtimeEnv/setup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								internal/runtimeEnv/setup.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | // Copyright (C) NHR@FAU, University Erlangen-Nuremberg. | ||||||
|  | // All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  | package runtimeEnv | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"os/user" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"syscall" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Very simple and limited .env file reader. | ||||||
|  | // All variable definitions found are directly | ||||||
|  | // added to the processes environment. | ||||||
|  | func LoadEnv(file string) error { | ||||||
|  | 	f, err := os.Open(file) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// log.Error("Error while opening .env file") | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	defer f.Close() | ||||||
|  | 	s := bufio.NewScanner(bufio.NewReader(f)) | ||||||
|  | 	for s.Scan() { | ||||||
|  | 		line := s.Text() | ||||||
|  | 		if strings.HasPrefix(line, "#") || len(line) == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if strings.Contains(line, "#") { | ||||||
|  | 			return errors.New("'#' are only supported at the start of a line") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		line = strings.TrimPrefix(line, "export ") | ||||||
|  | 		parts := strings.SplitN(line, "=", 2) | ||||||
|  | 		if len(parts) != 2 { | ||||||
|  | 			return fmt.Errorf("RUNTIME/SETUP > unsupported line: %#v", line) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		key := strings.TrimSpace(parts[0]) | ||||||
|  | 		val := strings.TrimSpace(parts[1]) | ||||||
|  | 		if strings.HasPrefix(val, "\"") { | ||||||
|  | 			if !strings.HasSuffix(val, "\"") { | ||||||
|  | 				return fmt.Errorf("RUNTIME/SETUP > unsupported line: %#v", line) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			runes := []rune(val[1 : len(val)-1]) | ||||||
|  | 			sb := strings.Builder{} | ||||||
|  | 			for i := 0; i < len(runes); i++ { | ||||||
|  | 				if runes[i] == '\\' { | ||||||
|  | 					i++ | ||||||
|  | 					switch runes[i] { | ||||||
|  | 					case 'n': | ||||||
|  | 						sb.WriteRune('\n') | ||||||
|  | 					case 'r': | ||||||
|  | 						sb.WriteRune('\r') | ||||||
|  | 					case 't': | ||||||
|  | 						sb.WriteRune('\t') | ||||||
|  | 					case '"': | ||||||
|  | 						sb.WriteRune('"') | ||||||
|  | 					default: | ||||||
|  | 						return fmt.Errorf("RUNTIME/SETUP > unsupported escape sequence in quoted string: backslash %#v", runes[i]) | ||||||
|  | 					} | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				sb.WriteRune(runes[i]) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			val = sb.String() | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		os.Setenv(key, val) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s.Err() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Changes the processes user and group to that | ||||||
|  | // specified in the config.json. The go runtime | ||||||
|  | // takes care of all threads (and not only the calling one) | ||||||
|  | // executing the underlying systemcall. | ||||||
|  | func DropPrivileges(username string, group string) error { | ||||||
|  | 	if group != "" { | ||||||
|  | 		g, err := user.LookupGroup(group) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// log.Warn("Error while looking up group") | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		gid, _ := strconv.Atoi(g.Gid) | ||||||
|  | 		if err := syscall.Setgid(gid); err != nil { | ||||||
|  | 			// log.Warn("Error while setting gid") | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if username != "" { | ||||||
|  | 		u, err := user.Lookup(username) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// log.Warn("Error while looking up user") | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		uid, _ := strconv.Atoi(u.Uid) | ||||||
|  | 		if err := syscall.Setuid(uid); err != nil { | ||||||
|  | 			// log.Warn("Error while setting uid") | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // If started via systemd, inform systemd that we are running: | ||||||
|  | // https://www.freedesktop.org/software/systemd/man/sd_notify.html | ||||||
|  | func SystemdNotifiy(ready bool, status string) { | ||||||
|  | 	if os.Getenv("NOTIFY_SOCKET") == "" { | ||||||
|  | 		// Not started using systemd | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	args := []string{fmt.Sprintf("--pid=%d", os.Getpid())} | ||||||
|  | 	if ready { | ||||||
|  | 		args = append(args, "--ready") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if status != "" { | ||||||
|  | 		args = append(args, fmt.Sprintf("--status=%s", status)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd := exec.Command("systemd-notify", args...) | ||||||
|  | 	cmd.Run() // errors ignored on purpose, there is not much to do anyways. | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user