mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Implement HTTP Server based on OpenAPI spec (#6835)
* Implement HTTP Server based on OpenAPI spec Signed-off-by: Parthvi Vala <pvala@redhat.com> Co-authored-by: Armel Soro <asoro@redhat.com> Co-authored-by: Philippe Martin <phmartin@redhat.com> * Starter server when odo dev starts Signed-off-by: Parthvi Vala <pvala@redhat.com> * Add --api-server and --api-server-port flags to start API Server; write the port to stat file; TODO: make this feature experimental Signed-off-by: Parthvi Vala <pvala@redhat.com> Co-authored-by: Armel Soro <asoro@redhat.com> Co-authored-by: Philippe Martin <phmartin@redhat.com> Signed-off-by: Parthvi Vala <pvala@redhat.com> Make the flag experimental Signed-off-by: Parthvi Vala <pvala@redhat.com> Make apiserver and apiserverport flag local Signed-off-by: Parthvi Vala <pvala@redhat.com> * Use container image to run openapi-generator-tool instead of a local CLI Co-authored-by: Armel Soro <asoro@redhat.com> Signed-off-by: Parthvi Vala <pvala@redhat.com> * Add integration test Signed-off-by: Parthvi Vala <pvala@redhat.com> * Use label podman Signed-off-by: Parthvi Vala <pvala@redhat.com> * Regenerate the api files with openapitool v6.6.0 and changes from review Signed-off-by: Parthvi Vala <pvala@redhat.com> Co-authored-by: Armel Soro <asoro@redhat.com> --------- Signed-off-by: Parthvi Vala <pvala@redhat.com> Co-authored-by: Armel Soro <asoro@redhat.com> Co-authored-by: Philippe Martin <phmartin@redhat.com>
This commit is contained in:
@@ -17,6 +17,8 @@ run:
|
||||
# Allowed values: readonly|vendor|mod
|
||||
# By default, it isn't set.
|
||||
modules-download-mode: vendor
|
||||
skip-dirs:
|
||||
- pkg/apiserver-gen
|
||||
|
||||
linters:
|
||||
# Note that some linters not listed below are enabled by default.
|
||||
|
||||
22
Makefile
22
Makefile
@@ -151,10 +151,6 @@ cross: ## compile for multiple platforms
|
||||
generate-cli-structure:
|
||||
go run cmd/cli-doc/cli-doc.go structure
|
||||
|
||||
.PHONY: generate-cli-reference
|
||||
generate-cli-reference:
|
||||
go run cmd/cli-doc/cli-doc.go reference > docs/cli-reference.adoc
|
||||
|
||||
# run make cross before this!
|
||||
.PHONY: prepare-release
|
||||
prepare-release: cross ## create gzipped binaries in ./dist/release/ for uploading to GitHub release page
|
||||
@@ -232,3 +228,21 @@ test-e2e:
|
||||
.PHONY: test-doc-automation
|
||||
test-doc-automation:
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS_ONE) --junit-report="test-doc-automation.xml" tests/documentation/...
|
||||
|
||||
|
||||
# Generate OpenAPISpec library based on ododevapispec.yaml inside pkg/apiserver-gen; this will only generate interfaces
|
||||
# Actual implementation must be done inside pkg/apiserver-impl
|
||||
# Apart from generating the files, this target also formats the generated files
|
||||
# and removes openapi.yaml to avoid any confusion regarding ododevapispec.yaml file and which file to use.
|
||||
.PHONY: generate-apiserver
|
||||
generate-apiserver: ## Generate OpenAPISpec library based on ododevapispec.yaml inside pkg/apiserver-gen
|
||||
podman run --rm \
|
||||
-v ${PWD}:/local \
|
||||
docker.io/openapitools/openapi-generator-cli:v6.6.0 \
|
||||
generate \
|
||||
-i /local/ododevapispec.yaml \
|
||||
-g go-server \
|
||||
-o /local/pkg/apiserver-gen \
|
||||
--additional-properties=outputAsLibrary=true,onlyInterfaces=true,hideGenerationTimestamp=true && \
|
||||
echo "Formatting generated files:" && go fmt ./pkg/apiserver-gen/... && \
|
||||
echo "Removing pkg/apiserver-gen/api/openapi.yaml" && rm ./pkg/apiserver-gen/api/openapi.yaml
|
||||
|
||||
2
go.mod
2
go.mod
@@ -22,6 +22,7 @@ require (
|
||||
github.com/go-openapi/spec v0.20.8
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.3
|
||||
github.com/kubernetes-sigs/service-catalog v0.3.1
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
@@ -126,7 +127,6 @@ require (
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gookit/color v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
|
||||
213
ododevapispec.yaml
Normal file
213
ododevapispec.yaml
Normal file
@@ -0,0 +1,213 @@
|
||||
openapi: '3.0.2'
|
||||
info:
|
||||
title: odo dev
|
||||
version: '0.1'
|
||||
description: API interface for 'odo dev'
|
||||
servers:
|
||||
- url: /api/v1
|
||||
paths:
|
||||
/instance:
|
||||
get:
|
||||
description: Get information about the this 'odo dev' instance.
|
||||
responses:
|
||||
'200':
|
||||
description: Information about the this 'odo dev' instance.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
componentDirectory:
|
||||
type: string
|
||||
description: Directory on which this 'odo dev' instance is running
|
||||
pid:
|
||||
type: integer
|
||||
description: PID of the this 'odo dev' instance.
|
||||
example:
|
||||
componentDirectory: "/Users/user/Documents/myproject"
|
||||
pid: 42
|
||||
|
||||
delete:
|
||||
description: "Stop this 'odo dev' instance"
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralSuccess'
|
||||
example:
|
||||
message: "'odo dev' instance with pid: 42 is shuting down."
|
||||
description: "'odo dev' instance will shutdown."
|
||||
|
||||
/component:
|
||||
get:
|
||||
description: Get the Information about the component controlled by this 'odo dev' instance.
|
||||
responses:
|
||||
'200':
|
||||
description: Information about the component.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
component:
|
||||
type: object
|
||||
description: Description of the component. This is the same as output of 'odo describe component -o json'
|
||||
example:
|
||||
{
|
||||
"devfilePath": "/home/tomas/Code/odo-examples/java-maven/devfile.yaml",
|
||||
"devfileData": {
|
||||
"devfile": {
|
||||
"schemaVersion": "2.1.0",
|
||||
"metadata": {
|
||||
"name": "demo",
|
||||
"version": "1.1.1",
|
||||
"displayName": "Maven Java",
|
||||
"description": "Upstream Maven and OpenJDK 11",
|
||||
"tags": [
|
||||
"Java",
|
||||
"Maven"
|
||||
],
|
||||
"icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/java-maven.jpg",
|
||||
"projectType": "Maven",
|
||||
"language": "Java"
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "tools",
|
||||
"container": {
|
||||
"image": "quay.io/eclipse/che-java11-maven:next",
|
||||
"env": [
|
||||
{
|
||||
"name": "DEBUG_PORT",
|
||||
"value": "5858"
|
||||
}
|
||||
],
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "m2",
|
||||
"path": "/home/user/.m2"
|
||||
}
|
||||
],
|
||||
"memoryLimit": "512Mi",
|
||||
"mountSources": true,
|
||||
"dedicatedPod": false,
|
||||
"endpoints": [
|
||||
{
|
||||
"name": "http-maven",
|
||||
"targetPort": 8080,
|
||||
"secure": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "m2",
|
||||
"volume": {
|
||||
"ephemeral": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"starterProjects": [
|
||||
{
|
||||
"name": "springbootproject",
|
||||
"git": {
|
||||
"remotes": {
|
||||
"origin": "https://github.com/odo-devfiles/springboot-ex.git"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"id": "mvn-package",
|
||||
"exec": {
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"commandLine": "mvn -Dmaven.repo.local=/home/user/.m2/repository package",
|
||||
"component": "tools",
|
||||
"workingDir": "${PROJECT_SOURCE}",
|
||||
"hotReloadCapable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "run",
|
||||
"exec": {
|
||||
"group": {
|
||||
"kind": "run",
|
||||
"isDefault": true
|
||||
},
|
||||
"commandLine": "java -jar target/*.jar",
|
||||
"component": "tools",
|
||||
"workingDir": "${PROJECT_SOURCE}",
|
||||
"hotReloadCapable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "debug",
|
||||
"exec": {
|
||||
"group": {
|
||||
"kind": "debug",
|
||||
"isDefault": true
|
||||
},
|
||||
"commandLine": "java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=${DEBUG_PORT},suspend=n -jar target/*.jar",
|
||||
"component": "tools",
|
||||
"workingDir": "${PROJECT_SOURCE}",
|
||||
"hotReloadCapable": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"supportedOdoFeatures": {
|
||||
"dev": true,
|
||||
"deploy": false,
|
||||
"debug": true
|
||||
}
|
||||
},
|
||||
"runningIn": {
|
||||
"deploy": false,
|
||||
"dev": true
|
||||
},
|
||||
"managedBy": "odo"
|
||||
}
|
||||
|
||||
/component/command:
|
||||
post:
|
||||
description: Instruct 'odo dev' to perform given command on the component
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Name of the command that should be executed
|
||||
type: string
|
||||
enum:
|
||||
- "push"
|
||||
example:
|
||||
action: push
|
||||
responses:
|
||||
'200':
|
||||
description: command was successfully executed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeneralSuccess'
|
||||
example:
|
||||
message: "push was successfully executed"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
GeneralSuccess:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
GeneralError:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
23
pkg/apiserver-gen/.openapi-generator-ignore
Normal file
23
pkg/apiserver-gen/.openapi-generator-ignore
Normal file
@@ -0,0 +1,23 @@
|
||||
# OpenAPI Generator Ignore
|
||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||
|
||||
# Use this file to prevent files from being overwritten by the generator.
|
||||
# The patterns follow closely to .gitignore or .dockerignore.
|
||||
|
||||
# As an example, the C# client generator defines ApiClient.cs.
|
||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||
#ApiClient.cs
|
||||
|
||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||
#foo/*/qux
|
||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||
|
||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||
#foo/**/qux
|
||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||
|
||||
# You can also negate patterns with an exclamation (!).
|
||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||
#docs/*.md
|
||||
# Then explicitly reverse the ignore rule for a single file:
|
||||
#!docs/README.md
|
||||
14
pkg/apiserver-gen/.openapi-generator/FILES
Normal file
14
pkg/apiserver-gen/.openapi-generator/FILES
Normal file
@@ -0,0 +1,14 @@
|
||||
README.md
|
||||
api/openapi.yaml
|
||||
go/api.go
|
||||
go/api_default.go
|
||||
go/error.go
|
||||
go/helpers.go
|
||||
go/impl.go
|
||||
go/logger.go
|
||||
go/model__component_command_post_request.go
|
||||
go/model__component_get_200_response.go
|
||||
go/model__instance_get_200_response.go
|
||||
go/model_general_error.go
|
||||
go/model_general_success.go
|
||||
go/routers.go
|
||||
1
pkg/apiserver-gen/.openapi-generator/VERSION
Normal file
1
pkg/apiserver-gen/.openapi-generator/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
6.6.0
|
||||
33
pkg/apiserver-gen/README.md
Normal file
33
pkg/apiserver-gen/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Go API Server for openapi
|
||||
|
||||
API interface for 'odo dev'
|
||||
|
||||
## Overview
|
||||
This server was generated by the [openapi-generator]
|
||||
(https://openapi-generator.tech) project.
|
||||
By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub.
|
||||
-
|
||||
|
||||
To see how to make this your own, look here:
|
||||
|
||||
[README](https://openapi-generator.tech)
|
||||
|
||||
- API version: 0.1
|
||||
|
||||
|
||||
### Running the server
|
||||
To run the server, follow these simple steps:
|
||||
|
||||
```
|
||||
go run main.go
|
||||
```
|
||||
|
||||
To run the server in a docker container
|
||||
```
|
||||
docker build --network=host -t openapi .
|
||||
```
|
||||
|
||||
Once image is built use
|
||||
```
|
||||
docker run --rm -it openapi
|
||||
```
|
||||
36
pkg/apiserver-gen/go/api.go
Normal file
36
pkg/apiserver-gen/go/api.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// DefaultApiRouter defines the required methods for binding the api requests to a responses for the DefaultApi
|
||||
// The DefaultApiRouter implementation should parse necessary information from the http request,
|
||||
// pass the data to a DefaultApiServicer to perform the required actions, then write the service results to the http response.
|
||||
type DefaultApiRouter interface {
|
||||
ComponentCommandPost(http.ResponseWriter, *http.Request)
|
||||
ComponentGet(http.ResponseWriter, *http.Request)
|
||||
InstanceDelete(http.ResponseWriter, *http.Request)
|
||||
InstanceGet(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// DefaultApiServicer defines the api actions for the DefaultApi service
|
||||
// This interface intended to stay up to date with the openapi yaml used to generate it,
|
||||
// while the service implementation can be ignored with the .openapi-generator-ignore file
|
||||
// and updated with the logic required for the API.
|
||||
type DefaultApiServicer interface {
|
||||
ComponentCommandPost(context.Context, ComponentCommandPostRequest) (ImplResponse, error)
|
||||
ComponentGet(context.Context) (ImplResponse, error)
|
||||
InstanceDelete(context.Context) (ImplResponse, error)
|
||||
InstanceGet(context.Context) (ImplResponse, error)
|
||||
}
|
||||
139
pkg/apiserver-gen/go/api_default.go
Normal file
139
pkg/apiserver-gen/go/api_default.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultApiController binds http requests to an api service and writes the service results to the http response
|
||||
type DefaultApiController struct {
|
||||
service DefaultApiServicer
|
||||
errorHandler ErrorHandler
|
||||
}
|
||||
|
||||
// DefaultApiOption for how the controller is set up.
|
||||
type DefaultApiOption func(*DefaultApiController)
|
||||
|
||||
// WithDefaultApiErrorHandler inject ErrorHandler into controller
|
||||
func WithDefaultApiErrorHandler(h ErrorHandler) DefaultApiOption {
|
||||
return func(c *DefaultApiController) {
|
||||
c.errorHandler = h
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultApiController creates a default api controller
|
||||
func NewDefaultApiController(s DefaultApiServicer, opts ...DefaultApiOption) Router {
|
||||
controller := &DefaultApiController{
|
||||
service: s,
|
||||
errorHandler: DefaultErrorHandler,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(controller)
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
// Routes returns all the api routes for the DefaultApiController
|
||||
func (c *DefaultApiController) Routes() Routes {
|
||||
return Routes{
|
||||
{
|
||||
"ComponentCommandPost",
|
||||
strings.ToUpper("Post"),
|
||||
"/api/v1/component/command",
|
||||
c.ComponentCommandPost,
|
||||
},
|
||||
{
|
||||
"ComponentGet",
|
||||
strings.ToUpper("Get"),
|
||||
"/api/v1/component",
|
||||
c.ComponentGet,
|
||||
},
|
||||
{
|
||||
"InstanceDelete",
|
||||
strings.ToUpper("Delete"),
|
||||
"/api/v1/instance",
|
||||
c.InstanceDelete,
|
||||
},
|
||||
{
|
||||
"InstanceGet",
|
||||
strings.ToUpper("Get"),
|
||||
"/api/v1/instance",
|
||||
c.InstanceGet,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ComponentCommandPost -
|
||||
func (c *DefaultApiController) ComponentCommandPost(w http.ResponseWriter, r *http.Request) {
|
||||
componentCommandPostRequestParam := ComponentCommandPostRequest{}
|
||||
d := json.NewDecoder(r.Body)
|
||||
d.DisallowUnknownFields()
|
||||
if err := d.Decode(&componentCommandPostRequestParam); err != nil {
|
||||
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
|
||||
return
|
||||
}
|
||||
if err := AssertComponentCommandPostRequestRequired(componentCommandPostRequestParam); err != nil {
|
||||
c.errorHandler(w, r, err, nil)
|
||||
return
|
||||
}
|
||||
result, err := c.service.ComponentCommandPost(r.Context(), componentCommandPostRequestParam)
|
||||
// If an error occurred, encode the error with the status code
|
||||
if err != nil {
|
||||
c.errorHandler(w, r, err, &result)
|
||||
return
|
||||
}
|
||||
// If no error, encode the body and the result code
|
||||
EncodeJSONResponse(result.Body, &result.Code, w)
|
||||
|
||||
}
|
||||
|
||||
// ComponentGet -
|
||||
func (c *DefaultApiController) ComponentGet(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := c.service.ComponentGet(r.Context())
|
||||
// If an error occurred, encode the error with the status code
|
||||
if err != nil {
|
||||
c.errorHandler(w, r, err, &result)
|
||||
return
|
||||
}
|
||||
// If no error, encode the body and the result code
|
||||
EncodeJSONResponse(result.Body, &result.Code, w)
|
||||
|
||||
}
|
||||
|
||||
// InstanceDelete -
|
||||
func (c *DefaultApiController) InstanceDelete(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := c.service.InstanceDelete(r.Context())
|
||||
// If an error occurred, encode the error with the status code
|
||||
if err != nil {
|
||||
c.errorHandler(w, r, err, &result)
|
||||
return
|
||||
}
|
||||
// If no error, encode the body and the result code
|
||||
EncodeJSONResponse(result.Body, &result.Code, w)
|
||||
|
||||
}
|
||||
|
||||
// InstanceGet -
|
||||
func (c *DefaultApiController) InstanceGet(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := c.service.InstanceGet(r.Context())
|
||||
// If an error occurred, encode the error with the status code
|
||||
if err != nil {
|
||||
c.errorHandler(w, r, err, &result)
|
||||
return
|
||||
}
|
||||
// If no error, encode the body and the result code
|
||||
EncodeJSONResponse(result.Body, &result.Code, w)
|
||||
|
||||
}
|
||||
62
pkg/apiserver-gen/go/error.go
Normal file
62
pkg/apiserver-gen/go/error.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrTypeAssertionError is thrown when type an interface does not match the asserted type
|
||||
ErrTypeAssertionError = errors.New("unable to assert type")
|
||||
)
|
||||
|
||||
// ParsingError indicates that an error has occurred when parsing request parameters
|
||||
type ParsingError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *ParsingError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e *ParsingError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// RequiredError indicates that an error has occurred when parsing request parameters
|
||||
type RequiredError struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
func (e *RequiredError) Error() string {
|
||||
return fmt.Sprintf("required field '%s' is zero value.", e.Field)
|
||||
}
|
||||
|
||||
// ErrorHandler defines the required method for handling error. You may implement it and inject this into a controller if
|
||||
// you would like errors to be handled differently from the DefaultErrorHandler
|
||||
type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error, result *ImplResponse)
|
||||
|
||||
// DefaultErrorHandler defines the default logic on how to handle errors from the controller. Any errors from parsing
|
||||
// request params will return a StatusBadRequest. Otherwise, the error code originating from the servicer will be used.
|
||||
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, result *ImplResponse) {
|
||||
if _, ok := err.(*ParsingError); ok {
|
||||
// Handle parsing errors
|
||||
EncodeJSONResponse(err.Error(), func(i int) *int { return &i }(http.StatusBadRequest), w)
|
||||
} else if _, ok := err.(*RequiredError); ok {
|
||||
// Handle missing required errors
|
||||
EncodeJSONResponse(err.Error(), func(i int) *int { return &i }(http.StatusUnprocessableEntity), w)
|
||||
} else {
|
||||
// Handle all other errors
|
||||
EncodeJSONResponse(err.Error(), &result.Code, w)
|
||||
}
|
||||
}
|
||||
54
pkg/apiserver-gen/go/helpers.go
Normal file
54
pkg/apiserver-gen/go/helpers.go
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Response return a ImplResponse struct filled
|
||||
func Response(code int, body interface{}) ImplResponse {
|
||||
return ImplResponse{
|
||||
Code: code,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
// IsZeroValue checks if the val is the zero-ed value.
|
||||
func IsZeroValue(val interface{}) bool {
|
||||
return val == nil || reflect.DeepEqual(val, reflect.Zero(reflect.TypeOf(val)).Interface())
|
||||
}
|
||||
|
||||
// AssertRecurseInterfaceRequired recursively checks each struct in a slice against the callback.
|
||||
// This method traverse nested slices in a preorder fashion.
|
||||
func AssertRecurseInterfaceRequired(obj interface{}, callback func(interface{}) error) error {
|
||||
return AssertRecurseValueRequired(reflect.ValueOf(obj), callback)
|
||||
}
|
||||
|
||||
// AssertRecurseValueRequired checks each struct in the nested slice against the callback.
|
||||
// This method traverse nested slices in a preorder fashion.
|
||||
func AssertRecurseValueRequired(value reflect.Value, callback func(interface{}) error) error {
|
||||
switch value.Kind() {
|
||||
// If it is a struct we check using callback
|
||||
case reflect.Struct:
|
||||
if err := callback(value.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If it is a slice we continue recursion
|
||||
case reflect.Slice:
|
||||
for i := 0; i < value.Len(); i += 1 {
|
||||
if err := AssertRecurseValueRequired(value.Index(i), callback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
16
pkg/apiserver-gen/go/impl.go
Normal file
16
pkg/apiserver-gen/go/impl.go
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
// ImplResponse response defines an error code with the associated body
|
||||
type ImplResponse struct {
|
||||
Code int
|
||||
Body interface{}
|
||||
}
|
||||
32
pkg/apiserver-gen/go/logger.go
Normal file
32
pkg/apiserver-gen/go/logger.go
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Logger(inner http.Handler, name string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
inner.ServeHTTP(w, r)
|
||||
|
||||
log.Printf(
|
||||
"%s %s %s %s",
|
||||
r.Method,
|
||||
r.RequestURI,
|
||||
name,
|
||||
time.Since(start),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type ComponentCommandPostRequest struct {
|
||||
|
||||
// Name of the command that should be executed
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// AssertComponentCommandPostRequestRequired checks if the required fields are not zero-ed
|
||||
func AssertComponentCommandPostRequestRequired(obj ComponentCommandPostRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseComponentCommandPostRequestRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of ComponentCommandPostRequest (e.g. [][]ComponentCommandPostRequest), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseComponentCommandPostRequestRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aComponentCommandPostRequest, ok := obj.(ComponentCommandPostRequest)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertComponentCommandPostRequestRequired(aComponentCommandPostRequest)
|
||||
})
|
||||
}
|
||||
33
pkg/apiserver-gen/go/model__component_get_200_response.go
Normal file
33
pkg/apiserver-gen/go/model__component_get_200_response.go
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type ComponentGet200Response struct {
|
||||
|
||||
// Description of the component. This is the same as output of 'odo describe component -o json'
|
||||
Component map[string]interface{} `json:"component,omitempty"`
|
||||
}
|
||||
|
||||
// AssertComponentGet200ResponseRequired checks if the required fields are not zero-ed
|
||||
func AssertComponentGet200ResponseRequired(obj ComponentGet200Response) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseComponentGet200ResponseRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of ComponentGet200Response (e.g. [][]ComponentGet200Response), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseComponentGet200ResponseRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aComponentGet200Response, ok := obj.(ComponentGet200Response)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertComponentGet200ResponseRequired(aComponentGet200Response)
|
||||
})
|
||||
}
|
||||
36
pkg/apiserver-gen/go/model__instance_get_200_response.go
Normal file
36
pkg/apiserver-gen/go/model__instance_get_200_response.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type InstanceGet200Response struct {
|
||||
|
||||
// Directory on which this 'odo dev' instance is running
|
||||
ComponentDirectory string `json:"componentDirectory,omitempty"`
|
||||
|
||||
// PID of the this 'odo dev' instance.
|
||||
Pid int32 `json:"pid,omitempty"`
|
||||
}
|
||||
|
||||
// AssertInstanceGet200ResponseRequired checks if the required fields are not zero-ed
|
||||
func AssertInstanceGet200ResponseRequired(obj InstanceGet200Response) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseInstanceGet200ResponseRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of InstanceGet200Response (e.g. [][]InstanceGet200Response), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseInstanceGet200ResponseRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aInstanceGet200Response, ok := obj.(InstanceGet200Response)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertInstanceGet200ResponseRequired(aInstanceGet200Response)
|
||||
})
|
||||
}
|
||||
31
pkg/apiserver-gen/go/model_general_error.go
Normal file
31
pkg/apiserver-gen/go/model_general_error.go
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type GeneralError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// AssertGeneralErrorRequired checks if the required fields are not zero-ed
|
||||
func AssertGeneralErrorRequired(obj GeneralError) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseGeneralErrorRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of GeneralError (e.g. [][]GeneralError), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseGeneralErrorRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aGeneralError, ok := obj.(GeneralError)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertGeneralErrorRequired(aGeneralError)
|
||||
})
|
||||
}
|
||||
31
pkg/apiserver-gen/go/model_general_success.go
Normal file
31
pkg/apiserver-gen/go/model_general_success.go
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
type GeneralSuccess struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// AssertGeneralSuccessRequired checks if the required fields are not zero-ed
|
||||
func AssertGeneralSuccessRequired(obj GeneralSuccess) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertRecurseGeneralSuccessRequired recursively checks if required fields are not zero-ed in a nested slice.
|
||||
// Accepts only nested slice of GeneralSuccess (e.g. [][]GeneralSuccess), otherwise ErrTypeAssertionError is thrown.
|
||||
func AssertRecurseGeneralSuccessRequired(objSlice interface{}) error {
|
||||
return AssertRecurseInterfaceRequired(objSlice, func(obj interface{}) error {
|
||||
aGeneralSuccess, ok := obj.(GeneralSuccess)
|
||||
if !ok {
|
||||
return ErrTypeAssertionError
|
||||
}
|
||||
return AssertGeneralSuccessRequired(aGeneralSuccess)
|
||||
})
|
||||
}
|
||||
296
pkg/apiserver-gen/go/routers.go
Normal file
296
pkg/apiserver-gen/go/routers.go
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* odo dev
|
||||
*
|
||||
* API interface for 'odo dev'
|
||||
*
|
||||
* API version: 0.1
|
||||
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gorilla/mux"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Route defines the parameters for an api endpoint
|
||||
type Route struct {
|
||||
Name string
|
||||
Method string
|
||||
Pattern string
|
||||
HandlerFunc http.HandlerFunc
|
||||
}
|
||||
|
||||
// Routes are a collection of defined api endpoints
|
||||
type Routes []Route
|
||||
|
||||
// Router defines the required methods for retrieving api routes
|
||||
type Router interface {
|
||||
Routes() Routes
|
||||
}
|
||||
|
||||
const errMsgRequiredMissing = "required parameter is missing"
|
||||
|
||||
// NewRouter creates a new router for any number of api routers
|
||||
func NewRouter(routers ...Router) *mux.Router {
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
for _, api := range routers {
|
||||
for _, route := range api.Routes() {
|
||||
var handler http.Handler
|
||||
handler = route.HandlerFunc
|
||||
handler = Logger(handler, route.Name)
|
||||
|
||||
router.
|
||||
Methods(route.Method).
|
||||
Path(route.Pattern).
|
||||
Name(route.Name).
|
||||
Handler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code
|
||||
func EncodeJSONResponse(i interface{}, status *int, w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
if status != nil {
|
||||
w.WriteHeader(*status)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
if i != nil {
|
||||
return json.NewEncoder(w).Encode(i)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file
|
||||
func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) {
|
||||
_, fileHeader, err := r.FormFile(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return readFileHeaderToTempFile(fileHeader)
|
||||
}
|
||||
|
||||
// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files
|
||||
func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) {
|
||||
if err := r.ParseMultipartForm(32 << 20); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]*os.File, 0, len(r.MultipartForm.File[key]))
|
||||
|
||||
for _, fileHeader := range r.MultipartForm.File[key] {
|
||||
file, err := readFileHeaderToTempFile(fileHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file
|
||||
func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) {
|
||||
formFile, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer formFile.Close()
|
||||
|
||||
fileBytes, err := ioutil.ReadAll(formFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := ioutil.TempFile("", fileHeader.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
file.Write(fileBytes)
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// parseFloatParameter parses a string parameter to an int64.
|
||||
func parseFloatParameter(param string, bitSize int, required bool) (float64, error) {
|
||||
if param == "" {
|
||||
if required {
|
||||
return 0, errors.New(errMsgRequiredMissing)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return strconv.ParseFloat(param, bitSize)
|
||||
}
|
||||
|
||||
// parseFloat64Parameter parses a string parameter to an float64.
|
||||
func parseFloat64Parameter(param string, required bool) (float64, error) {
|
||||
return parseFloatParameter(param, 64, required)
|
||||
}
|
||||
|
||||
// parseFloat32Parameter parses a string parameter to an float32.
|
||||
func parseFloat32Parameter(param string, required bool) (float32, error) {
|
||||
val, err := parseFloatParameter(param, 32, required)
|
||||
return float32(val), err
|
||||
}
|
||||
|
||||
// parseIntParameter parses a string parameter to an int64.
|
||||
func parseIntParameter(param string, bitSize int, required bool) (int64, error) {
|
||||
if param == "" {
|
||||
if required {
|
||||
return 0, errors.New(errMsgRequiredMissing)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return strconv.ParseInt(param, 10, bitSize)
|
||||
}
|
||||
|
||||
// parseInt64Parameter parses a string parameter to an int64.
|
||||
func parseInt64Parameter(param string, required bool) (int64, error) {
|
||||
return parseIntParameter(param, 64, required)
|
||||
}
|
||||
|
||||
// parseInt32Parameter parses a string parameter to an int32.
|
||||
func parseInt32Parameter(param string, required bool) (int32, error) {
|
||||
val, err := parseIntParameter(param, 32, required)
|
||||
return int32(val), err
|
||||
}
|
||||
|
||||
// parseBoolParameter parses a string parameter to a bool
|
||||
func parseBoolParameter(param string, required bool) (bool, error) {
|
||||
if param == "" {
|
||||
if required {
|
||||
return false, errors.New(errMsgRequiredMissing)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
val, err := strconv.ParseBool(param)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return bool(val), nil
|
||||
}
|
||||
|
||||
// parseFloat64ArrayParameter parses a string parameter containing array of values to []Float64.
|
||||
func parseFloat64ArrayParameter(param, delim string, required bool) ([]float64, error) {
|
||||
if param == "" {
|
||||
if required {
|
||||
return nil, errors.New(errMsgRequiredMissing)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
str := strings.Split(param, delim)
|
||||
floats := make([]float64, len(str))
|
||||
|
||||
for i, s := range str {
|
||||
if v, err := strconv.ParseFloat(s, 64); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
floats[i] = v
|
||||
}
|
||||
}
|
||||
|
||||
return floats, nil
|
||||
}
|
||||
|
||||
// parseFloat32ArrayParameter parses a string parameter containing array of values to []float32.
|
||||
func parseFloat32ArrayParameter(param, delim string, required bool) ([]float32, error) {
|
||||
if param == "" {
|
||||
if required {
|
||||
return nil, errors.New(errMsgRequiredMissing)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
str := strings.Split(param, delim)
|
||||
floats := make([]float32, len(str))
|
||||
|
||||
for i, s := range str {
|
||||
if v, err := strconv.ParseFloat(s, 32); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
floats[i] = float32(v)
|
||||
}
|
||||
}
|
||||
|
||||
return floats, nil
|
||||
}
|
||||
|
||||
// parseInt64ArrayParameter parses a string parameter containing array of values to []int64.
|
||||
func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, error) {
|
||||
if param == "" {
|
||||
if required {
|
||||
return nil, errors.New(errMsgRequiredMissing)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
str := strings.Split(param, delim)
|
||||
ints := make([]int64, len(str))
|
||||
|
||||
for i, s := range str {
|
||||
if v, err := strconv.ParseInt(s, 10, 64); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
ints[i] = v
|
||||
}
|
||||
}
|
||||
|
||||
return ints, nil
|
||||
}
|
||||
|
||||
// parseInt32ArrayParameter parses a string parameter containing array of values to []int32.
|
||||
func parseInt32ArrayParameter(param, delim string, required bool) ([]int32, error) {
|
||||
if param == "" {
|
||||
if required {
|
||||
return nil, errors.New(errMsgRequiredMissing)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
str := strings.Split(param, delim)
|
||||
ints := make([]int32, len(str))
|
||||
|
||||
for i, s := range str {
|
||||
if v, err := strconv.ParseInt(s, 10, 32); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
ints[i] = int32(v)
|
||||
}
|
||||
}
|
||||
|
||||
return ints, nil
|
||||
}
|
||||
63
pkg/apiserver-impl/api_default_service.go
Normal file
63
pkg/apiserver-impl/api_default_service.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package apiserver_impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
openapi "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// DefaultApiService is a service that implements the logic for the DefaultApiServicer
|
||||
// This service should implement the business logic for every endpoint for the DefaultApi API.
|
||||
// Include any external packages or services that will be required by this service.
|
||||
type DefaultApiService struct {
|
||||
}
|
||||
|
||||
// NewDefaultApiService creates a default api service
|
||||
func NewDefaultApiService() openapi.DefaultApiServicer {
|
||||
return &DefaultApiService{}
|
||||
}
|
||||
|
||||
// ComponentCommandPost -
|
||||
func (s *DefaultApiService) ComponentCommandPost(ctx context.Context, componentCommandPostRequest openapi.ComponentCommandPostRequest) (openapi.ImplResponse, error) {
|
||||
// TODO - update ComponentCommandPost with the required logic for this service method.
|
||||
// Add api_default_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
|
||||
|
||||
// TODO: Uncomment the next line to return response Response(200, GeneralSuccess{}) or use other options such as http.Ok ...
|
||||
// return Response(200, GeneralSuccess{}), nil
|
||||
|
||||
return openapi.Response(http.StatusNotImplemented, nil), errors.New("ComponentCommandPost method not implemented")
|
||||
}
|
||||
|
||||
// ComponentGet -
|
||||
func (s *DefaultApiService) ComponentGet(ctx context.Context) (openapi.ImplResponse, error) {
|
||||
// TODO - update ComponentGet with the required logic for this service method.
|
||||
// Add api_default_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
|
||||
|
||||
// TODO: Uncomment the next line to return response Response(200, ComponentGet200Response{}) or use other options such as http.Ok ...
|
||||
// return Response(200, ComponentGet200Response{}), nil
|
||||
|
||||
return openapi.Response(http.StatusNotImplemented, nil), errors.New("ComponentGet method not implemented")
|
||||
}
|
||||
|
||||
// InstanceDelete -
|
||||
func (s *DefaultApiService) InstanceDelete(ctx context.Context) (openapi.ImplResponse, error) {
|
||||
// TODO - update InstanceDelete with the required logic for this service method.
|
||||
// Add api_default_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
|
||||
|
||||
// TODO: Uncomment the next line to return response Response(200, GeneralSuccess{}) or use other options such as http.Ok ...
|
||||
// return Response(200, GeneralSuccess{}), nil
|
||||
|
||||
return openapi.Response(http.StatusNotImplemented, nil), errors.New("InstanceDelete method not implemented")
|
||||
}
|
||||
|
||||
// InstanceGet -
|
||||
func (s *DefaultApiService) InstanceGet(ctx context.Context) (openapi.ImplResponse, error) {
|
||||
// TODO - update InstanceGet with the required logic for this service method.
|
||||
// Add api_default_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
|
||||
|
||||
// TODO: Uncomment the next line to return response Response(200, InstanceGet200Response{}) or use other options such as http.Ok ...
|
||||
// return Response(200, InstanceGet200Response{}), nil
|
||||
|
||||
return openapi.Response(http.StatusNotImplemented, nil), errors.New("InstanceGet method not implemented")
|
||||
}
|
||||
57
pkg/apiserver-impl/starterserver.go
Normal file
57
pkg/apiserver-impl/starterserver.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package apiserver_impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
openapi "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
|
||||
"github.com/redhat-developer/odo/pkg/state"
|
||||
"github.com/redhat-developer/odo/pkg/util"
|
||||
"k8s.io/klog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func StartServer(ctx context.Context, cancelFunc context.CancelFunc, port int, stateClient state.Client) {
|
||||
|
||||
defaultApiService := NewDefaultApiService()
|
||||
defaultApiController := openapi.NewDefaultApiController(defaultApiService)
|
||||
|
||||
router := openapi.NewRouter(defaultApiController)
|
||||
|
||||
var err error
|
||||
|
||||
if port == 0 {
|
||||
port, err = util.NextFreePort(20000, 30001, nil, "")
|
||||
if err != nil {
|
||||
klog.V(0).Infof("Unable to start the API server; encountered error: %v", err)
|
||||
cancelFunc()
|
||||
}
|
||||
}
|
||||
|
||||
err = stateClient.SetAPIServerPort(ctx, port)
|
||||
if err != nil {
|
||||
klog.V(0).Infof("Unable to start the API server; encountered error: %v", err)
|
||||
cancelFunc()
|
||||
}
|
||||
|
||||
klog.V(0).Infof("API Server started at localhost:%d/api/v1", port)
|
||||
|
||||
server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: router}
|
||||
var errChan = make(chan error)
|
||||
go func() {
|
||||
err = server.ListenAndServe()
|
||||
errChan <- err
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
klog.V(0).Infof("Shutting down the API server: %v", ctx.Err())
|
||||
err = server.Shutdown(ctx)
|
||||
if err != nil {
|
||||
klog.V(1).Infof("Error while shutting down the API server: %v", err)
|
||||
}
|
||||
case err = <-errChan:
|
||||
klog.V(0).Infof("Stopping the API server; encountered error: %v", err)
|
||||
cancelFunc()
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func odoRootCmd(ctx context.Context, name, fullName string, testClientset client
|
||||
_delete.NewCmdDelete(ctx, _delete.RecommendedCommandName, util.GetFullName(fullName, _delete.RecommendedCommandName), testClientset),
|
||||
add.NewCmdAdd(add.RecommendedCommandName, util.GetFullName(fullName, add.RecommendedCommandName), testClientset),
|
||||
remove.NewCmdRemove(remove.RecommendedCommandName, util.GetFullName(fullName, remove.RecommendedCommandName), testClientset),
|
||||
dev.NewCmdDev(dev.RecommendedCommandName, util.GetFullName(fullName, dev.RecommendedCommandName), testClientset),
|
||||
dev.NewCmdDev(ctx, dev.RecommendedCommandName, util.GetFullName(fullName, dev.RecommendedCommandName), testClientset),
|
||||
alizer.NewCmdAlizer(alizer.RecommendedCommandName, util.GetFullName(fullName, alizer.RecommendedCommandName), testClientset),
|
||||
describe.NewCmdDescribe(ctx, describe.RecommendedCommandName, util.GetFullName(fullName, describe.RecommendedCommandName), testClientset),
|
||||
registry.NewCmdRegistry(registry.RecommendedCommandName, util.GetFullName(fullName, registry.RecommendedCommandName), testClientset),
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
apiserver_impl "github.com/redhat-developer/odo/pkg/apiserver-impl"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/feature"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -70,6 +72,8 @@ type DevOptions struct {
|
||||
portForwardFlag []string
|
||||
addressFlag string
|
||||
noCommandsFlag bool
|
||||
apiServerFlag bool
|
||||
apiServerPortFlag int
|
||||
}
|
||||
|
||||
var _ genericclioptions.Runnable = (*DevOptions)(nil)
|
||||
@@ -173,6 +177,12 @@ func (o *DevOptions) Validate(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.apiServerFlag && o.apiServerPortFlag != 0 {
|
||||
if !util.IsPortFree(o.apiServerPortFlag, "") {
|
||||
return fmt.Errorf("port %d is not free; please try another port", o.apiServerPortFlag)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -242,6 +252,11 @@ func (o *DevOptions) Run(ctx context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.apiServerFlag {
|
||||
// Start the server here; it will be shutdown when context is cancelled; or if the server encounters an error
|
||||
apiserver_impl.StartServer(ctx, o.cancel, o.apiServerPortFlag, o.clientset.StateClient)
|
||||
}
|
||||
|
||||
return o.clientset.DevClient.Start(
|
||||
o.ctx,
|
||||
dev.StartOptions{
|
||||
@@ -282,7 +297,7 @@ func (o *DevOptions) Cleanup(ctx context.Context, commandError error) {
|
||||
}
|
||||
|
||||
// NewCmdDev implements the odo dev command
|
||||
func NewCmdDev(name, fullName string, testClientset clientset.Clientset) *cobra.Command {
|
||||
func NewCmdDev(ctx context.Context, name, fullName string, testClientset clientset.Clientset) *cobra.Command {
|
||||
o := NewDevOptions()
|
||||
devCmd := &cobra.Command{
|
||||
Use: name,
|
||||
@@ -311,6 +326,10 @@ It forwards endpoints with any exposure values ('public', 'internal' or 'none')
|
||||
devCmd.Flags().StringVar(&o.addressFlag, "address", "127.0.0.1", "Define custom address for port forwarding.")
|
||||
devCmd.Flags().BoolVar(&o.noCommandsFlag, "no-commands", false, "Do not run any commands; just start the development environment.")
|
||||
|
||||
if feature.IsExperimentalModeEnabled(ctx) {
|
||||
devCmd.Flags().BoolVar(&o.apiServerFlag, "api-server", false, "Start the API Server; this is an experimental feature")
|
||||
devCmd.Flags().IntVar(&o.apiServerPortFlag, "api-server-port", 0, "Define custom port for API Server; this flag should be used in combination with --api-server flag.")
|
||||
}
|
||||
clientset.Add(devCmd,
|
||||
clientset.BINDING,
|
||||
clientset.DEV,
|
||||
|
||||
@@ -17,6 +17,10 @@ var (
|
||||
GenericPlatformFlag = OdoFeature{
|
||||
isExperimental: false,
|
||||
}
|
||||
|
||||
APIServerFlag = OdoFeature{
|
||||
isExperimental: true,
|
||||
}
|
||||
)
|
||||
|
||||
// IsEnabled returns whether the specified feature should be enabled or not.
|
||||
|
||||
@@ -4,7 +4,6 @@ package cmdline
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
)
|
||||
|
||||
@@ -15,7 +14,7 @@ type Cmdline interface {
|
||||
// GetFlags returns a map of flags set
|
||||
GetFlags() map[string]string
|
||||
|
||||
// FlagValue returns the value for a flag
|
||||
// FlagValue returns the string value for a flag
|
||||
FlagValue(flagName string) (string, error)
|
||||
|
||||
// FlagValueIfSet returns the value for a flag, or an empty string if not set
|
||||
|
||||
@@ -59,7 +59,7 @@ func (o *Cobra) GetWorkingDirectory() (string, error) {
|
||||
return dfutil.GetAbsPath(".")
|
||||
}
|
||||
|
||||
// FlagValueIfSet retrieves the value of the specified flag if it is set for the given command
|
||||
// FlagValue retrieves the value of the specified flag if it is set for the given command
|
||||
func (o *Cobra) FlagValue(flagName string) (string, error) {
|
||||
return o.cmd.Flags().GetString(flagName)
|
||||
}
|
||||
|
||||
@@ -18,4 +18,7 @@ type Client interface {
|
||||
|
||||
// SaveExit resets the state file to indicate odo is not running
|
||||
SaveExit(ctx context.Context) error
|
||||
|
||||
// SetAPIServerPort sets the port where API server is listening in the state file and saves it to the file, updating the metadata
|
||||
SetAPIServerPort(ctx context.Context, port int) error
|
||||
}
|
||||
|
||||
@@ -92,6 +92,17 @@ func (o *State) SaveExit(ctx context.Context) error {
|
||||
return o.saveCommonIfOwner(pid)
|
||||
}
|
||||
|
||||
func (o *State) SetAPIServerPort(ctx context.Context, port int) error {
|
||||
var (
|
||||
pid = odocontext.GetPID(ctx)
|
||||
platform = fcontext.GetPlatform(ctx, commonflags.PlatformCluster)
|
||||
)
|
||||
|
||||
o.content.APIServerPort = port
|
||||
o.content.Platform = platform
|
||||
return o.save(ctx, pid)
|
||||
}
|
||||
|
||||
// save writes the content structure in json format in file
|
||||
func (o *State) save(ctx context.Context, pid int) error {
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ type Content struct {
|
||||
Platform string `json:"platform"`
|
||||
// ForwardedPorts are the ports forwarded during odo dev session
|
||||
ForwardedPorts []api.ForwardedPort `json:"forwardedPorts"`
|
||||
APIServerPort int `json:"apiServerPort"`
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ActiveState/termtest/expect"
|
||||
@@ -110,13 +111,14 @@ import (
|
||||
*/
|
||||
|
||||
type DevSession struct {
|
||||
session *gexec.Session
|
||||
stopped bool
|
||||
console *expect.Console
|
||||
address string
|
||||
StdOut string
|
||||
ErrOut string
|
||||
Endpoints map[string]string
|
||||
session *gexec.Session
|
||||
stopped bool
|
||||
console *expect.Console
|
||||
address string
|
||||
StdOut string
|
||||
ErrOut string
|
||||
Endpoints map[string]string
|
||||
APIServerEndpoint string
|
||||
}
|
||||
|
||||
type DevSessionOpts struct {
|
||||
@@ -128,6 +130,8 @@ type DevSessionOpts struct {
|
||||
NoWatch bool
|
||||
NoCommands bool
|
||||
CustomAddress string
|
||||
StartAPIServer bool
|
||||
APIServerPort int
|
||||
}
|
||||
|
||||
// StartDevMode starts a dev session with `odo dev`
|
||||
@@ -156,6 +160,12 @@ func StartDevMode(options DevSessionOpts) (devSession DevSession, err error) {
|
||||
if options.CustomAddress != "" {
|
||||
args = append(args, "--address", options.CustomAddress)
|
||||
}
|
||||
if options.StartAPIServer {
|
||||
args = append(args, "--api-server")
|
||||
if options.APIServerPort != 0 {
|
||||
args = append(args, "--api-server-port", fmt.Sprintf("%d", options.APIServerPort))
|
||||
}
|
||||
}
|
||||
args = append(args, options.CmdlineArgs...)
|
||||
cmd := Cmd("odo", args...)
|
||||
cmd.Cmd.Stdin = c.Tty()
|
||||
@@ -186,6 +196,10 @@ func StartDevMode(options DevSessionOpts) (devSession DevSession, err error) {
|
||||
result.StdOut = string(outContents)
|
||||
result.ErrOut = string(errContents)
|
||||
result.Endpoints = getPorts(string(outContents), options.CustomAddress)
|
||||
if options.StartAPIServer {
|
||||
// errContents because the server message is still printed as a log/warning
|
||||
result.APIServerEndpoint = getAPIServerPort(string(errContents))
|
||||
}
|
||||
return result, nil
|
||||
|
||||
}
|
||||
@@ -358,3 +372,12 @@ func getPorts(s, address string) map[string]string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getAPIServerPort returns the address at which api server is running
|
||||
//
|
||||
// `I0617 11:40:44.124391 49578 starterserver.go:36] API Server started at localhost:20000/api/v1`
|
||||
func getAPIServerPort(s string) string {
|
||||
re := regexp.MustCompile(`(API Server started at localhost:[0-9]+\/api\/v1)`)
|
||||
matches := re.FindString(s)
|
||||
return strings.Split(matches, "at ")[1]
|
||||
}
|
||||
|
||||
73
tests/integration/cmd_dev_api_server_test.go
Normal file
73
tests/integration/cmd_dev_api_server_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/redhat-developer/odo/tests/helper"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var _ = Describe("odo dev command with api server tests", func() {
|
||||
var cmpName string
|
||||
var commonVar helper.CommonVar
|
||||
|
||||
// This is run before every Spec (It)
|
||||
var _ = BeforeEach(func() {
|
||||
commonVar = helper.CommonBeforeEach()
|
||||
cmpName = helper.RandString(6)
|
||||
helper.Chdir(commonVar.Context)
|
||||
Expect(helper.VerifyFileExists(".odo/env/env.yaml")).To(BeFalse())
|
||||
})
|
||||
|
||||
// This is run after every Spec (It)
|
||||
var _ = AfterEach(func() {
|
||||
helper.CommonAfterEach(commonVar)
|
||||
})
|
||||
for _, podman := range []bool{false, true} {
|
||||
podman := podman
|
||||
for _, customPort := range []bool{false, true} {
|
||||
customPort := customPort
|
||||
When("the component is bootstrapped", helper.LabelPodmanIf(podman, func() {
|
||||
BeforeEach(func() {
|
||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
|
||||
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(commonVar.Context, "devfile.yaml"), cmpName)
|
||||
})
|
||||
When(fmt.Sprintf("odo dev is run with --api-server flag (custom api server port=%v)", customPort), func() {
|
||||
var (
|
||||
devSession helper.DevSession
|
||||
localPort = helper.GetCustomStartPort()
|
||||
)
|
||||
BeforeEach(func() {
|
||||
opts := helper.DevSessionOpts{
|
||||
RunOnPodman: podman,
|
||||
StartAPIServer: true,
|
||||
EnvVars: []string{"ODO_EXPERIMENTAL_MODE=true"},
|
||||
}
|
||||
if customPort {
|
||||
opts.APIServerPort = localPort
|
||||
}
|
||||
var err error
|
||||
devSession, err = helper.StartDevMode(opts)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
AfterEach(func() {
|
||||
devSession.Stop()
|
||||
devSession.WaitEnd()
|
||||
})
|
||||
It("should start the Dev server when --api-server flag is passed", func() {
|
||||
if customPort {
|
||||
Expect(devSession.APIServerEndpoint).To(ContainSubstring(fmt.Sprintf("%d", localPort)))
|
||||
}
|
||||
url := fmt.Sprintf("http://%s/instance", devSession.APIServerEndpoint)
|
||||
resp, err := http.Get(url)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// TODO: Change this once it is implemented
|
||||
Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusNotImplemented))
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user