From d9ea241073ae4fc354fc5cd64ae4eeafb0cd6c6a Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Fri, 10 May 2019 21:59:43 +0200 Subject: [PATCH 01/15] add get /features and /labels in openapi doc --- openapi.json | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 openapi.json diff --git a/openapi.json b/openapi.json new file mode 100644 index 00000000..90a76103 --- /dev/null +++ b/openapi.json @@ -0,0 +1,156 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "FoundaMl" , + "description": "", + "termsOfService": "", + "contact": { + "name": "", + "url": "", + "email": "sauray.antoine@gmail.com" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "0.0.1" + }, + "servers": [ + { + "url": "http://{platform}.www.foundaml.org", + "description": "Test server", + "variables": { + "platform": { + "default": "dev", + "description": "this value is assigned by the service provider" + }, + "port": { + "enum": [ + "80", + "443" + ], + "default": "80" + }, + "basePath": { + "default": "" + } + } + } + ], + "paths": { + + "/features": { + "get": { + "description": "", + "responses": { + "200": { + "description": "", + "content": { + "application.json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FeaturesConfiguration" + } + } + } + } + } + } + } + }, + + "/labels": { + "get": { + "description": "", + "responses": { + "200": { + "description": "", + "content": { + "application.json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LabelsConfiguration" + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "FeaturesConfiguration": { + "type": "object", + "required": [ + "id", + "data" + ], + "properties": { + "id": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "type", + "description" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + }, + "LabelsConfiguration": { + "type": "object", + "required": [ + "id", + "data" + ], + "properties": { + "id": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "type", + "description" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + } + } + } +} From caba55fe341635c915bdc73cf328fb388167fe10 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Fri, 10 May 2019 22:02:05 +0200 Subject: [PATCH 02/15] add openapi yaml version --- openapi.yaml | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 00000000..f3d7c880 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,100 @@ +openapi: 3.0.0 +info: + title: FoundaMl + description: '' + termsOfService: '' + contact: + name: '' + url: '' + email: sauray.antoine@gmail.com + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' + version: 0.0.1 +servers: + - url: 'http://{platform}.www.foundaml.org' + description: Test server + variables: + platform: + default: dev + description: this value is assigned by the service provider + port: + enum: + - '80' + - '443' + default: '80' + basePath: + default: '' +paths: + /features: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + type: array + items: + $ref: '#/components/schemas/FeaturesConfiguration' + /labels: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + type: array + items: + $ref: '#/components/schemas/LabelsConfiguration' +components: + schemas: + FeaturesConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string + LabelsConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string From 66bb0c6ec9e89ef48516e811c3e1022bf491140b Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sat, 11 May 2019 14:33:31 +0200 Subject: [PATCH 03/15] update openapi and k8 --- k8foundaml.yaml | 29 +++++++++- openapi.yaml | 141 ++++++++++++++++++++++------------------------- openapi.yaml.old | 101 +++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 76 deletions(-) create mode 100644 openapi.yaml.old diff --git a/k8foundaml.yaml b/k8foundaml.yaml index b1daa25b..211fac6d 100644 --- a/k8foundaml.yaml +++ b/k8foundaml.yaml @@ -13,6 +13,10 @@ spec: labels: app: foundaml-server spec: + volumes: + - name: service-account-creds + secret: + secretName: service-account-creds containers: - name: foundaml-server image: foundaml/server:0.0.1 @@ -35,6 +39,27 @@ spec: value: "postgres" - name: POSTGRESQL_PASSWORD value: "postgres" + # esp proxy + - name: esp + image: gcr.io/endpoints-release/endpoints-runtime:1 + args: [ + "--http_port=8081", + "--backend=127.0.0.1:8080", + "--service=foundaml-service", + "--rollout_strategy=managed", + "--service_account_key=/etc/nginx/creds/service-account-creds.json" + ] + ports: + - containerPort: 8081 + volumeMounts: + - mountPath: /etc/nginx/creds + name: service-account-creds + readOnly: true + resources: + requests: + memory: "50Mi" + cpu: "50m" + # esp proxy --- kind: Service apiVersion: v1 @@ -46,5 +71,7 @@ spec: app: foundaml-server ports: - protocol: TCP - port: 8080 + port: 80 + targetPort: 8081 + name: http diff --git a/openapi.yaml b/openapi.yaml index f3d7c880..4954388e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.0 +swagger: "2.0" info: title: FoundaMl description: '' @@ -11,90 +11,81 @@ info: name: Apache 2.0 url: 'https://www.apache.org/licenses/LICENSE-2.0.html' version: 0.0.1 -servers: - - url: 'http://{platform}.www.foundaml.org' - description: Test server - variables: - platform: - default: dev - description: this value is assigned by the service provider - port: - enum: - - '80' - - '443' - default: '80' - basePath: - default: '' +host: "echo-api.endpoints.foundaml.cloud.goog" +securityDefinitions: + api_key: + type: apiKey + name: key + in: query paths: /features: get: + operationId: getFeaturesConfiguration + security: + - api_key: [] description: '' responses: '200': description: '' - content: - application.json: - schema: - type: array - items: - $ref: '#/components/schemas/FeaturesConfiguration' + schema: + type: array + $ref: '#/definitions/FeaturesConfiguration' /labels: get: + operationId: getLabelsConfiguration + security: + - api_key: [] description: '' responses: '200': description: '' - content: - application.json: - schema: - type: array - items: - $ref: '#/components/schemas/LabelsConfiguration' -components: - schemas: - FeaturesConfiguration: - type: object - required: - - id - - data - properties: - id: - type: string - data: - type: array - items: - type: object - required: - - name - - type - - description - properties: - name: - type: string - type: - type: string - description: - type: string - LabelsConfiguration: - type: object - required: - - id - - data - properties: - id: - type: string - data: - type: array - items: - type: object - required: - - name - - type - - description - properties: - name: - type: string - type: - type: string - description: - type: string + schema: + type: array + $ref: '#/definitions/LabelsConfiguration' +definitions: + FeaturesConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string + LabelsConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string diff --git a/openapi.yaml.old b/openapi.yaml.old new file mode 100644 index 00000000..13820276 --- /dev/null +++ b/openapi.yaml.old @@ -0,0 +1,101 @@ +openapi: "2.0.0" +info: + title: FoundaMl + description: '' + termsOfService: '' + contact: + name: '' + url: '' + email: sauray.antoine@gmail.com + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' + version: 0.0.1 +host: "echo-api.endpoints.foundaml.cloud.goog" +servers: + - url: 'http://{platform}.www.foundaml.org' + description: Test server + variables: + platform: + default: dev + description: this value is assigned by the service provider + port: + enum: + - '80' + - '443' + default: '80' + basePath: + default: '' +paths: + /features: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + type: array + items: + $ref: '#/components/schemas/FeaturesConfiguration' + /labels: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + type: array + items: + $ref: '#/components/schemas/LabelsConfiguration' +components: + schemas: + FeaturesConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string + LabelsConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string From 80c6556e2fb9f96a75246c18ce3d1c3961626c5f Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sat, 11 May 2019 17:38:42 +0200 Subject: [PATCH 04/15] start documenting project --- openapi.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 4954388e..95f8c531 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -43,6 +43,11 @@ paths: type: array $ref: '#/definitions/LabelsConfiguration' definitions: + Project: + type: object + required: + - id + FeaturesConfiguration: type: object required: From 200cd7199c6ebcff505773447ccfaa92148483ca Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sat, 11 May 2019 23:09:50 +0200 Subject: [PATCH 05/15] update openapi.yaml, remove openapi.yaml.old and modify openapi.json --- openapi.json | 432 ++++++++++++++++++++++++++++++++++++++++++++--- openapi.yaml | 262 ++++++++++++++++++++-------- openapi.yaml.old | 101 ----------- 3 files changed, 596 insertions(+), 199 deletions(-) delete mode 100644 openapi.yaml.old diff --git a/openapi.json b/openapi.json index 90a76103..8c3b36b4 100644 --- a/openapi.json +++ b/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "title": "FoundaMl" , + "title": "FoundaMl", "description": "", "termsOfService": "", "contact": { @@ -15,30 +15,29 @@ }, "version": "0.0.1" }, - "servers": [ - { - "url": "http://{platform}.www.foundaml.org", - "description": "Test server", - "variables": { - "platform": { - "default": "dev", - "description": "this value is assigned by the service provider" - }, - "port": { - "enum": [ - "80", - "443" - ], - "default": "80" - }, - "basePath": { - "default": "" - } - } - } - ], + "servers": [ + { + "url": "http://{platform}.www.foundaml.org", + "description": "Test server", + "variables": { + "platform": { + "default": "dev", + "description": "this value is assigned by the service provider" + }, + "port": { + "enum": [ + "80", + "443" + ], + "default": "80" + }, + "basePath": { + "default": "" + } + } + } + ], "paths": { - "/features": { "get": { "description": "", @@ -50,7 +49,38 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/FeaturesConfiguration" + "type": "object", + "required": [ + "id", + "data" + ], + "properties": { + "id": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "type", + "description" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } } } } @@ -59,7 +89,6 @@ } } }, - "/labels": { "get": { "description": "", @@ -71,7 +100,80 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/LabelsConfiguration" + "type": "object", + "required": [ + "id", + "data" + ], + "properties": { + "id": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "type", + "description" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/projects": { + "get": { + "description": "", + "responses": { + "200": { + "description": "", + "content": { + "application.json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "name", + "problem", + "algorithms", + "policy", + "configuration" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "problem": { + "type": "string" + }, + "algorithms": { + "type": "array" + } + } } } } @@ -83,6 +185,282 @@ }, "components": { "schemas": { + "Project": { + "type": "object", + "required": [ + "id", + "name", + "problem", + "algorithms", + "policy", + "configuration" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "problem": { + "type": "string" + }, + "algorithms": { + "type": "array" + } + } + }, + "Algorithm": { + "type": "object", + "required": [ + "id", + "projectId", + "backend", + "security" + ], + "properties": { + "id": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "backend": { + "type": "object", + "required": [ + "class", + "host", + "port", + "featuresTransformer", + "labelsTransformer" + ], + "properties": { + "class": { + "type": "string" + }, + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "featuresTransformer": { + "type": "object", + "required": [ + "signatureName", + "fields" + ], + "properties": { + "signatureName": { + "type": "string" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "labelsTransformer": { + "type": "object", + "required": [ + "fields" + ], + "properties": { + "fields": { + "type": "array", + "items": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + } + } + } + } + } + } + }, + "security": { + "type": "object", + "required": [ + "encryption", + "headers" + ], + "properties": { + "encryption": { + "type": "string" + }, + "headers": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + } + } + } + }, + "SecurityConfiguration": { + "type": "object", + "required": [ + "encryption", + "headers" + ], + "properties": { + "encryption": { + "type": "string" + }, + "headers": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + } + }, + "Backend": { + "type": "object", + "required": [ + "class", + "host", + "port", + "featuresTransformer", + "labelsTransformer" + ], + "properties": { + "class": { + "type": "string" + }, + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "featuresTransformer": { + "type": "object", + "required": [ + "signatureName", + "fields" + ], + "properties": { + "signatureName": { + "type": "string" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "labelsTransformer": { + "type": "object", + "required": [ + "fields" + ], + "properties": { + "fields": { + "type": "array", + "items": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + } + } + } + } + } + } + }, + "TensorFlowFeaturesTransformer": { + "type": "object", + "required": [ + "signatureName", + "fields" + ], + "properties": { + "signatureName": { + "type": "string" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "TensorFlowLabelsTransformer": { + "type": "object", + "required": [ + "fields" + ], + "properties": { + "fields": { + "type": "array", + "items": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + } + } + } + } + }, "FeaturesConfiguration": { "type": "object", "required": [ diff --git a/openapi.yaml b/openapi.yaml index 95f8c531..9d4f6297 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,4 +1,4 @@ -swagger: "2.0" +openapi: "3.0.0" info: title: FoundaMl description: '' @@ -11,86 +11,206 @@ info: name: Apache 2.0 url: 'https://www.apache.org/licenses/LICENSE-2.0.html' version: 0.0.1 -host: "echo-api.endpoints.foundaml.cloud.goog" -securityDefinitions: - api_key: - type: apiKey - name: key - in: query +servers: + - url: 'http://{platform}.www.foundaml.org' + description: Test server + variables: + platform: + default: dev + description: this value is assigned by the service provider + port: + enum: + - '80' + - '443' + default: '80' + basePath: + default: '' paths: /features: get: - operationId: getFeaturesConfiguration - security: - - api_key: [] description: '' responses: '200': description: '' - schema: - type: array - $ref: '#/definitions/FeaturesConfiguration' + content: + application.json: + schema: + type: array + items: + $ref: '#/components/schemas/FeaturesConfiguration' /labels: get: - operationId: getLabelsConfiguration - security: - - api_key: [] description: '' responses: '200': description: '' - schema: - type: array - $ref: '#/definitions/LabelsConfiguration' -definitions: - Project: - type: object - required: - - id - - FeaturesConfiguration: - type: object - required: - - id - - data - properties: - id: - type: string - data: - type: array - items: - type: object - required: - - name - - type - - description - properties: - name: - type: string - type: - type: string - description: - type: string - LabelsConfiguration: - type: object - required: - - id - - data - properties: - id: - type: string - data: - type: array - items: - type: object - required: - - name - - type - - description - properties: - name: - type: string - type: - type: string - description: - type: string + content: + application.json: + schema: + type: array + items: + $ref: '#/components/schemas/LabelsConfiguration' + /projects: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + type: array + items: + $ref: '#/components/schemas/Project' +components: + schemas: + Project: + type: object + required: + - id + - name + - problem + - algorithms + - policy + - configuration + properties: + id: + type: string + name: + type: string + problem: + type: string + algorithms: + type: array + items: + $ref: "#/components/schemas/Algorithm" + Algorithm: + type: object + required: + - id + - projectId + - backend + - security + properties: + id: + type: string + projectId: + type: string + backend: + $ref: "#/components/schemas/Backend" + security: + $ref: "#/components/schemas/SecurityConfiguration" + SecurityConfiguration: + type: object + required: + - encryption + - headers + properties: + encryption: + type: string + headers: + type: array + items: + type: object + required: + - key + - value + properties: + key: + type: string + value: + type: string + Backend: + type: object + required: + - class + - host + - port + - featuresTransformer + - labelsTransformer + properties: + class: + type: string + host: + type: string + port: + type: integer + featuresTransformer: + $ref: "#/components/schemas/TensorFlowFeaturesTransformer" + labelsTransformer: + $ref: "#/components/schemas/TensorFlowLabelsTransformer" + TensorFlowFeaturesTransformer: + type: object + required: + - signatureName + - fields + properties: + signatureName: + type: string + fields: + type: array + items: + type: string + TensorFlowLabelsTransformer: + type: object + required: + - fields + properties: + fields: + type: array + items: + type: object + required: + - from + - to + properties: + from: + type: string + to: + type: string + FeaturesConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string + LabelsConfiguration: + type: object + required: + - id + - data + properties: + id: + type: string + data: + type: array + items: + type: object + required: + - name + - type + - description + properties: + name: + type: string + type: + type: string + description: + type: string diff --git a/openapi.yaml.old b/openapi.yaml.old deleted file mode 100644 index 13820276..00000000 --- a/openapi.yaml.old +++ /dev/null @@ -1,101 +0,0 @@ -openapi: "2.0.0" -info: - title: FoundaMl - description: '' - termsOfService: '' - contact: - name: '' - url: '' - email: sauray.antoine@gmail.com - license: - name: Apache 2.0 - url: 'https://www.apache.org/licenses/LICENSE-2.0.html' - version: 0.0.1 -host: "echo-api.endpoints.foundaml.cloud.goog" -servers: - - url: 'http://{platform}.www.foundaml.org' - description: Test server - variables: - platform: - default: dev - description: this value is assigned by the service provider - port: - enum: - - '80' - - '443' - default: '80' - basePath: - default: '' -paths: - /features: - get: - description: '' - responses: - '200': - description: '' - content: - application.json: - schema: - type: array - items: - $ref: '#/components/schemas/FeaturesConfiguration' - /labels: - get: - description: '' - responses: - '200': - description: '' - content: - application.json: - schema: - type: array - items: - $ref: '#/components/schemas/LabelsConfiguration' -components: - schemas: - FeaturesConfiguration: - type: object - required: - - id - - data - properties: - id: - type: string - data: - type: array - items: - type: object - required: - - name - - type - - description - properties: - name: - type: string - type: - type: string - description: - type: string - LabelsConfiguration: - type: object - required: - - id - - data - properties: - id: - type: string - data: - type: array - items: - type: object - required: - - name - - type - - description - properties: - name: - type: string - type: - type: string - description: - type: string From de6bd5e0d7183390ab1fe3343190ce2e196ab6e0 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sat, 11 May 2019 23:10:14 +0200 Subject: [PATCH 06/15] remove openapi.json --- openapi.json | 534 --------------------------------------------------- 1 file changed, 534 deletions(-) delete mode 100644 openapi.json diff --git a/openapi.json b/openapi.json deleted file mode 100644 index 8c3b36b4..00000000 --- a/openapi.json +++ /dev/null @@ -1,534 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "FoundaMl", - "description": "", - "termsOfService": "", - "contact": { - "name": "", - "url": "", - "email": "sauray.antoine@gmail.com" - }, - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html" - }, - "version": "0.0.1" - }, - "servers": [ - { - "url": "http://{platform}.www.foundaml.org", - "description": "Test server", - "variables": { - "platform": { - "default": "dev", - "description": "this value is assigned by the service provider" - }, - "port": { - "enum": [ - "80", - "443" - ], - "default": "80" - }, - "basePath": { - "default": "" - } - } - } - ], - "paths": { - "/features": { - "get": { - "description": "", - "responses": { - "200": { - "description": "", - "content": { - "application.json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "data" - ], - "properties": { - "id": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "type": "object", - "required": [ - "name", - "type", - "description" - ], - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "description": { - "type": "string" - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/labels": { - "get": { - "description": "", - "responses": { - "200": { - "description": "", - "content": { - "application.json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "data" - ], - "properties": { - "id": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "type": "object", - "required": [ - "name", - "type", - "description" - ], - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "description": { - "type": "string" - } - } - } - } - } - } - } - } - } - } - } - } - }, - "/projects": { - "get": { - "description": "", - "responses": { - "200": { - "description": "", - "content": { - "application.json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "name", - "problem", - "algorithms", - "policy", - "configuration" - ], - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "problem": { - "type": "string" - }, - "algorithms": { - "type": "array" - } - } - } - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Project": { - "type": "object", - "required": [ - "id", - "name", - "problem", - "algorithms", - "policy", - "configuration" - ], - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "problem": { - "type": "string" - }, - "algorithms": { - "type": "array" - } - } - }, - "Algorithm": { - "type": "object", - "required": [ - "id", - "projectId", - "backend", - "security" - ], - "properties": { - "id": { - "type": "string" - }, - "projectId": { - "type": "string" - }, - "backend": { - "type": "object", - "required": [ - "class", - "host", - "port", - "featuresTransformer", - "labelsTransformer" - ], - "properties": { - "class": { - "type": "string" - }, - "host": { - "type": "string" - }, - "port": { - "type": "integer" - }, - "featuresTransformer": { - "type": "object", - "required": [ - "signatureName", - "fields" - ], - "properties": { - "signatureName": { - "type": "string" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "labelsTransformer": { - "type": "object", - "required": [ - "fields" - ], - "properties": { - "fields": { - "type": "array", - "items": { - "type": "object", - "required": [ - "from", - "to" - ], - "properties": { - "from": { - "type": "string" - }, - "to": { - "type": "string" - } - } - } - } - } - } - } - }, - "security": { - "type": "object", - "required": [ - "encryption", - "headers" - ], - "properties": { - "encryption": { - "type": "string" - }, - "headers": { - "type": "array", - "items": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - } - } - } - } - }, - "SecurityConfiguration": { - "type": "object", - "required": [ - "encryption", - "headers" - ], - "properties": { - "encryption": { - "type": "string" - }, - "headers": { - "type": "array", - "items": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - } - } - }, - "Backend": { - "type": "object", - "required": [ - "class", - "host", - "port", - "featuresTransformer", - "labelsTransformer" - ], - "properties": { - "class": { - "type": "string" - }, - "host": { - "type": "string" - }, - "port": { - "type": "integer" - }, - "featuresTransformer": { - "type": "object", - "required": [ - "signatureName", - "fields" - ], - "properties": { - "signatureName": { - "type": "string" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "labelsTransformer": { - "type": "object", - "required": [ - "fields" - ], - "properties": { - "fields": { - "type": "array", - "items": { - "type": "object", - "required": [ - "from", - "to" - ], - "properties": { - "from": { - "type": "string" - }, - "to": { - "type": "string" - } - } - } - } - } - } - } - }, - "TensorFlowFeaturesTransformer": { - "type": "object", - "required": [ - "signatureName", - "fields" - ], - "properties": { - "signatureName": { - "type": "string" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "TensorFlowLabelsTransformer": { - "type": "object", - "required": [ - "fields" - ], - "properties": { - "fields": { - "type": "array", - "items": { - "type": "object", - "required": [ - "from", - "to" - ], - "properties": { - "from": { - "type": "string" - }, - "to": { - "type": "string" - } - } - } - } - } - }, - "FeaturesConfiguration": { - "type": "object", - "required": [ - "id", - "data" - ], - "properties": { - "id": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "type": "object", - "required": [ - "name", - "type", - "description" - ], - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "description": { - "type": "string" - } - } - } - } - } - }, - "LabelsConfiguration": { - "type": "object", - "required": [ - "id", - "data" - ], - "properties": { - "id": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "type": "object", - "required": [ - "name", - "type", - "description" - ], - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "description": { - "type": "string" - } - } - } - } - } - } - } - } -} From ff8fbd46dbbd0e62ecd82bcfbd7ffd8618738969 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sat, 11 May 2019 23:27:24 +0200 Subject: [PATCH 07/15] open api doc --- openapi.yaml | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index 9d4f6297..9e22fb74 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -38,6 +38,25 @@ paths: type: array items: $ref: '#/components/schemas/FeaturesConfiguration' + post: + description: 'Create a new features object' + responses: + '201': + description: 'Successfully created a new features object' + content: + application.json: + schema: + $ref: '#/components/schemas/FeaturesConfiguration' + /features/{featuresId}: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + $ref: '#/components/schemas/FeaturesConfiguration' /labels: get: description: '' @@ -47,7 +66,26 @@ paths: content: application.json: schema: - type: array + $ref: '#/components/schemas/LabelsConfiguration' + post: + description: 'Create a new labels object' + responses: + '201': + description: 'Successfully created a new labels object' + content: + application.json: + schema: + $ref: '#/components/schemas/LabelsConfiguration' + /labels/{labelsId}: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + type: object items: $ref: '#/components/schemas/LabelsConfiguration' /projects: @@ -62,6 +100,45 @@ paths: type: array items: $ref: '#/components/schemas/Project' + post: + description: 'Create a new project' + responses: + '201': + description: 'Successfully created a new project' + content: + application.json: + schema: + $ref: '#/components/schemas/Project' + /projects/{projectId}: + get: + description: '' + responses: + '200': + description: '' + content: + application.json: + schema: + $ref: '#/components/schemas/Project' + /algorithms: + post: + description: 'create a new algorithm' + responses: + '201': + description: 'Successfully created a new algorithm' + content: + application.json: + schema: + $ref: '#/components/schemas/Algorithm' + /predictions: + post: + description: 'Make a prediction on a project' + responses: + '200': + description: 'The prediction is successful' + content: + application.json: + schema: + $ref: '#/components/schemas/Prediction' components: schemas: Project: @@ -214,3 +291,17 @@ components: type: string description: type: string + Prediction: + type: object + required: + - projectId + - features + properties: + projectId: + type: string + features: + type: array + items: + oneOf: + - type: string + - type: number From cc7ebca6ed605732b1d91e9baa31298cc559184e Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sat, 11 May 2019 23:48:08 +0200 Subject: [PATCH 08/15] more routes on openapi doc --- openapi.yaml | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index 9e22fb74..945adfee 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -40,6 +40,14 @@ paths: $ref: '#/components/schemas/FeaturesConfiguration' post: description: 'Create a new features object' + requestBody: + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/FeaturesConfiguration' + required: + true responses: '201': description: 'Successfully created a new features object' @@ -69,6 +77,14 @@ paths: $ref: '#/components/schemas/LabelsConfiguration' post: description: 'Create a new labels object' + requestBody: + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/LabelsConfiguration' + required: + true responses: '201': description: 'Successfully created a new labels object' @@ -102,6 +118,14 @@ paths: $ref: '#/components/schemas/Project' post: description: 'Create a new project' + requestBody: + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + required: + true responses: '201': description: 'Successfully created a new project' @@ -122,6 +146,14 @@ paths: /algorithms: post: description: 'create a new algorithm' + requestBody: + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Algorithm' + required: + true responses: '201': description: 'Successfully created a new algorithm' @@ -132,6 +164,14 @@ paths: /predictions: post: description: 'Make a prediction on a project' + requestBody: + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PredictionRequest' + required: + true responses: '200': description: 'The prediction is successful' @@ -139,6 +179,16 @@ paths: application.json: schema: $ref: '#/components/schemas/Prediction' + /examples: + post: + description: 'Add a human validated example for a prediction' + responses: + '201': + description: 'The example has been created successfully' + content: + application.json: + schema: + $ref: '#/components/schemas/Prediction' components: schemas: Project: @@ -291,7 +341,7 @@ components: type: string description: type: string - Prediction: + PredictionRequest: type: object required: - projectId @@ -305,3 +355,95 @@ components: oneOf: - type: string - type: number + Prediction: + type: object + required: + - type + - id + - projectId + - algorithmId + - features + - labels + - examples + properties: + type: + type: "string" + id: + type: "string" + projectId: + type: "string" + algorithmId: + type: "string" + examples: + type: array + items: + type: string + features: + type: array + items: + oneOf: + - type: string + - type: number + labels: + type: object + required: + - label + - probability + - correctExampleUrl + - incorrectExampleUrl + properties: + label: + type: string + probability: + type: number + correctExampleUrl: + type: string + description: 'Call this url relative to the host to validate this label and make it an example' + incorrectExampleUrl: + type: string + description: 'Call this url relative to the host to invalidate this label and make it a wrong example' + PredictionEvent: + type: object + required: + - type + - id + - projectId + - algorithmId + - features + - labels + - example + properties: + type: + type: "string" + id: + type: "string" + projectId: + type: "string" + algorithmId: + type: "string" + example: + type: "string" + features: + type: array + items: + oneOf: + - type: string + - type: number + labels: + type: object + required: + - label + - probability + - correctExampleUrl + - incorrectExampleUrl + properties: + label: + type: string + probability: + type: number + correctExampleUrl: + type: string + description: 'Call this url relative to the host to validate this label and make it an example' + incorrectExampleUrl: + type: string + description: 'Call this url relative to the host to invalidate this label and make it a wrong example' From 39a1ed8db415435d60fdd9c8d3e929635e3b6cf9 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sun, 12 May 2019 00:11:40 +0200 Subject: [PATCH 09/15] fix openapi.yaml --- openapi.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 945adfee..c0df770b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -58,6 +58,11 @@ paths: /features/{featuresId}: get: description: '' + parameters: + - name: featuresId + in: path + description: The Id of the features object + required: true responses: '200': description: '' @@ -95,6 +100,11 @@ paths: /labels/{labelsId}: get: description: '' + parameters: + - name: labelsId + in: path + description: The Id of the labels object + required: true responses: '200': description: '' @@ -136,6 +146,11 @@ paths: /projects/{projectId}: get: description: '' + parameters: + - name: projectId + in: path + description: The Id of the project + required: true responses: '200': description: '' From 731a32e7ec2f8195657c5b02482721a6af01c15b Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sun, 12 May 2019 00:25:15 +0200 Subject: [PATCH 10/15] add descriptions in openapi --- README.md | 27 --------------------------- openapi.yaml | 36 ++++++++++++++++++------------------ 2 files changed, 18 insertions(+), 45 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index d0b8a35e..00000000 --- a/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Foundaml -[![Build Status](https://travis-ci.org/scoverage/sbt-scoverage.png?branch=master)](https://travis-ci.org/foundaml/server) -[![License](http://img.shields.io/:license-Apache%202-red.svg)](http://www.apache.org/licenses/LICENSE-2.0.txt) - -Foundaml is a service that help you manage your algorithms in a production environment. It's a great way to start industrializing your machine learning projects. Foundaml comes with three principles - -* **Focus on the problem** machine learning is one solution, not the problem. Foundaml asks you to specify a problem first -* **Data is the key** Foundaml helps you to build a pipeline to get your labeled data back from your users. You won't need to waste time hand labelling data. -* **Iteration is crucial** you should start with a simple heuristic before training a big neural network. Foundaml forces you to integrate your algorithms early in the process so you can start delivering results fast. Foundaml also comes with built in data streaming and AB testing support. - -**Useful links** - -[Foundaml website](https://foundaml.github.io/server/) - -[Generating data with Foundaml](https://medium.com/@sauray.antoine/data-generation-for-machine-learning-using-foundaml-5e324e6939f5) - -# Backend -Foundaml does not execute algorithms on its own. It relies on backends for this task. Currently it is compatible with -* TensorFlow serving API - - -## Building -` -sbt stage -docker-compose build -docker-compose up -` diff --git a/openapi.yaml b/openapi.yaml index c0df770b..29a8c73a 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.0" info: title: FoundaMl - description: '' + description: 'Manage your machine learning algorithms' termsOfService: '' contact: name: '' @@ -28,10 +28,10 @@ servers: paths: /features: get: - description: '' + description: 'Get all the features objects' responses: '200': - description: '' + description: 'Successfully retrieved the features objects' content: application.json: schema: @@ -41,7 +41,7 @@ paths: post: description: 'Create a new features object' requestBody: - description: '' + description: 'Successfully retrieved the features objects' content: application/json: schema: @@ -57,7 +57,7 @@ paths: $ref: '#/components/schemas/FeaturesConfiguration' /features/{featuresId}: get: - description: '' + description: 'Get a features object by id' parameters: - name: featuresId in: path @@ -65,17 +65,17 @@ paths: required: true responses: '200': - description: '' + description: 'Successfully found the features object' content: application.json: schema: $ref: '#/components/schemas/FeaturesConfiguration' /labels: get: - description: '' + description: 'Get all the labels object' responses: '200': - description: '' + description: 'Successfully retrieved the labels object' content: application.json: schema: @@ -83,7 +83,7 @@ paths: post: description: 'Create a new labels object' requestBody: - description: '' + description: 'The new labels object' content: application/json: schema: @@ -99,7 +99,7 @@ paths: $ref: '#/components/schemas/LabelsConfiguration' /labels/{labelsId}: get: - description: '' + description: 'Get a labels object by id' parameters: - name: labelsId in: path @@ -107,7 +107,7 @@ paths: required: true responses: '200': - description: '' + description: 'Successfully found the labels object' content: application.json: schema: @@ -116,10 +116,10 @@ paths: $ref: '#/components/schemas/LabelsConfiguration' /projects: get: - description: '' + description: 'Get all the projects' responses: '200': - description: '' + description: 'Successfully retrieved all the projects' content: application.json: schema: @@ -129,7 +129,7 @@ paths: post: description: 'Create a new project' requestBody: - description: '' + description: 'The new project to create' content: application/json: schema: @@ -145,7 +145,7 @@ paths: $ref: '#/components/schemas/Project' /projects/{projectId}: get: - description: '' + description: 'Get a project by ud' parameters: - name: projectId in: path @@ -153,7 +153,7 @@ paths: required: true responses: '200': - description: '' + description: 'Successfully found the project' content: application.json: schema: @@ -162,7 +162,7 @@ paths: post: description: 'create a new algorithm' requestBody: - description: '' + description: 'the new algorithm to create' content: application/json: schema: @@ -180,7 +180,7 @@ paths: post: description: 'Make a prediction on a project' requestBody: - description: '' + description: 'The prediction requets' content: application/json: schema: From 58f80dcc74a87cbbb62c41e0e9d51415a47e0a68 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sun, 12 May 2019 11:57:48 +0200 Subject: [PATCH 11/15] add errors and new type hierarchy FeaturesTransformer and LabelsTransformer --- openapi.yaml | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 29a8c73a..b4a01119 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -70,6 +70,12 @@ paths: application.json: schema: $ref: '#/components/schemas/FeaturesConfiguration' + '404': + description: 'The features object does not exist' + content: + text.plain: + schema: + type: string /labels: get: description: 'Get all the labels object' @@ -114,6 +120,12 @@ paths: type: object items: $ref: '#/components/schemas/LabelsConfiguration' + '404': + description: 'The labels object does not exist' + content: + text.plain: + schema: + type: string /projects: get: description: 'Get all the projects' @@ -158,6 +170,13 @@ paths: application.json: schema: $ref: '#/components/schemas/Project' + '404': + description: 'The project does not exist' + content: + text.plain: + schema: + type: string + /algorithms: post: description: 'create a new algorithm' @@ -278,9 +297,17 @@ components: port: type: integer featuresTransformer: - $ref: "#/components/schemas/TensorFlowFeaturesTransformer" + $ref: "#/components/schemas/FeaturesTransformer" labelsTransformer: - $ref: "#/components/schemas/TensorFlowLabelsTransformer" + $ref: "#/components/schemas/LabelsTransformer" + FeaturesTransformer: + type: object + oneOf: + - $ref '#/components/schemas/TensorFlowFeaturesTransformer' + LabelsTransformer: + type: object + oneOf: + - $ref '#/components/schemas/TensorFlowLabelsTransformer' TensorFlowFeaturesTransformer: type: object required: From 0815884c2cd6d3e8cdaa608234cf02168016e5a6 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sun, 12 May 2019 13:41:23 +0200 Subject: [PATCH 12/15] update Backend in openapi doc --- openapi.yaml | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index b4a01119..531574db 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -285,29 +285,35 @@ components: type: object required: - class - - host - - port - - featuresTransformer - - labelsTransformer properties: class: type: string - host: - type: string - port: - type: integer - featuresTransformer: - $ref: "#/components/schemas/FeaturesTransformer" - labelsTransformer: - $ref: "#/components/schemas/LabelsTransformer" - FeaturesTransformer: - type: object - oneOf: - - $ref '#/components/schemas/TensorFlowFeaturesTransformer' - LabelsTransformer: - type: object - oneOf: - - $ref '#/components/schemas/TensorFlowLabelsTransformer' + discriminator: + propertyName: class + TensorFlowClassificationBackend: + allOf: + - $ref: '#/components/schemas/Backend' + - type: object + properties: + host: + type: string + port: + type: integer + featuresTransformer: + $ref: '#/components/schemas/TensorFlowFeaturesTransformer' + labelsTransformer: + $ref: '#/components/schemas/TensorFlowLabelsTransformer' + TensorFlowRegressionBackend: + allOf: + - $ref: '#/components/schemas/Backend' + - type: object + properties: + host: + type: string + port: + type: integer + featuresTransformer: + $ref: '#/components/schemas/TensorFlowFeaturesTransformer' TensorFlowFeaturesTransformer: type: object required: @@ -322,8 +328,6 @@ components: type: string TensorFlowLabelsTransformer: type: object - required: - - fields properties: fields: type: array From 8b8a0226fdf87bc5aed110390286d33c70130747 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sun, 12 May 2019 17:28:50 +0200 Subject: [PATCH 13/15] add patch project in openapi --- openapi.yaml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 531574db..68b0eb63 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -155,6 +155,19 @@ paths: application.json: schema: $ref: '#/components/schemas/Project' + patch: + description: 'Update a project' + requestBody: + description: 'The fields to patch' + content: + application/json: + schema: + type: object + properties: + name: + type: string + policy: + $ref: '#/components/schemas/AlgorithmPolicy' /projects/{projectId}: get: description: 'Get a project by ud' @@ -245,6 +258,8 @@ components: type: array items: $ref: "#/components/schemas/Algorithm" + policy: + $ref: '#/components/schemas/AlgorithmPolicy' Algorithm: type: object required: @@ -493,3 +508,37 @@ components: incorrectExampleUrl: type: string description: 'Call this url relative to the host to invalidate this label and make it a wrong example' + AlgorithmPolicy: + type: object + required: + - class + properties: + class: + type: string + discriminator: + propertyName: class + NoAlgorithmPolicy: + allOf: + - $ref: '#/components/schemas/AlgorithmPolicy' + - type: object + DefaultAlgorithmPolicy: + allOf: + - $ref: '#/components/schemas/AlgorithmPolicy' + - type: object + properties: + algorithmId: + type: string + WeightedAlgorithmPolicy: + allOf: + - $ref: '#/components/schemas/WeightedAlgorithmPolicy' + - type: object + properties: + weights: + type: array + items: + type: object + properties: + algorithmId: + type: string + weight: + type: number From 6a244e0589f60cc23cedbcd162fb62e8ef855149 Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sun, 12 May 2019 20:09:54 +0200 Subject: [PATCH 14/15] add more doc --- openapi.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 68b0eb63..6947b3cb 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4,7 +4,7 @@ info: description: 'Manage your machine learning algorithms' termsOfService: '' contact: - name: '' + name: 'Antoine Sauray' url: '' email: sauray.antoine@gmail.com license: @@ -12,8 +12,8 @@ info: url: 'https://www.apache.org/licenses/LICENSE-2.0.html' version: 0.0.1 servers: - - url: 'http://{platform}.www.foundaml.org' - description: Test server + - url: 'http://{platform}.api.foundaml.org' + description: Api server variables: platform: default: dev From ce090b53d0bab2799870f40a695dbea06437c1bd Mon Sep 17 00:00:00 2001 From: Antoine Sauray Date: Sun, 12 May 2019 20:29:59 +0200 Subject: [PATCH 15/15] modify email in openapi --- openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index 6947b3cb..4f217669 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -6,7 +6,7 @@ info: contact: name: 'Antoine Sauray' url: '' - email: sauray.antoine@gmail.com + email: antoine@foundaml.org license: name: Apache 2.0 url: 'https://www.apache.org/licenses/LICENSE-2.0.html'