mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Implement API endpoints (#6915)
* Pass odo context to api server * Get /instance * DELETE /instance implementation * Move describe logic to pkg/component/describe * Get /component implementation * POST /component/command implementation * Fix example by replacing action with name * Fix integration test * Integration tests * Add comment for PushWatcher * Test DELETE /instance without --no-watch * Apply suggestions from code review Co-authored-by: Armel Soro <armel@rm3l.org> * Return an error if not ready for push * Fix windows tests * Fix tests for Windows --------- Co-authored-by: Armel Soro <armel@rm3l.org>
This commit is contained in:
@@ -188,7 +188,7 @@ paths:
|
||||
enum:
|
||||
- "push"
|
||||
example:
|
||||
action: push
|
||||
name: push
|
||||
responses:
|
||||
'200':
|
||||
description: command was successfully executed
|
||||
|
||||
@@ -14,7 +14,6 @@ To see how to make this your own, look here:
|
||||
|
||||
- API version: 0.1
|
||||
|
||||
|
||||
### Running the server
|
||||
To run the server, follow these simple steps:
|
||||
|
||||
|
||||
@@ -2,62 +2,87 @@ package apiserver_impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
openapi "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
openapi "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
|
||||
"github.com/redhat-developer/odo/pkg/component/describe"
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
|
||||
"github.com/redhat-developer/odo/pkg/podman"
|
||||
"github.com/redhat-developer/odo/pkg/state"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
cancel context.CancelFunc
|
||||
pushWatcher chan<- struct{}
|
||||
kubeClient kclient.ClientInterface
|
||||
podmanClient podman.Client
|
||||
stateClient state.Client
|
||||
}
|
||||
|
||||
// NewDefaultApiService creates a default api service
|
||||
func NewDefaultApiService() openapi.DefaultApiServicer {
|
||||
return &DefaultApiService{}
|
||||
func NewDefaultApiService(
|
||||
cancel context.CancelFunc,
|
||||
pushWatcher chan<- struct{},
|
||||
kubeClient kclient.ClientInterface,
|
||||
podmanClient podman.Client,
|
||||
stateClient state.Client,
|
||||
) openapi.DefaultApiServicer {
|
||||
return &DefaultApiService{
|
||||
cancel: cancel,
|
||||
pushWatcher: pushWatcher,
|
||||
kubeClient: kubeClient,
|
||||
podmanClient: podmanClient,
|
||||
stateClient: stateClient,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
switch componentCommandPostRequest.Name {
|
||||
case "push":
|
||||
select {
|
||||
case s.pushWatcher <- struct{}{}:
|
||||
return openapi.Response(http.StatusOK, openapi.GeneralSuccess{
|
||||
Message: "push was successfully executed",
|
||||
}), nil
|
||||
default:
|
||||
return openapi.Response(http.StatusTooManyRequests, openapi.GeneralError{
|
||||
Message: "a push operation is not possible at this time. Please retry later",
|
||||
}), nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
default:
|
||||
return openapi.Response(http.StatusBadRequest, nil), fmt.Errorf("command name %q not supported. Supported values are: %q", componentCommandPostRequest.Name, "push")
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
value, _, err := describe.DescribeDevfileComponent(ctx, s.kubeClient, s.podmanClient, s.stateClient)
|
||||
if err != nil {
|
||||
return openapi.Response(http.StatusInternalServerError, ""), fmt.Errorf("error getting the description of the component: %w", err)
|
||||
}
|
||||
return openapi.Response(http.StatusOK, value), nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
s.cancel()
|
||||
return openapi.Response(http.StatusOK, openapi.GeneralSuccess{
|
||||
Message: fmt.Sprintf("'odo dev' instance with pid: %d is shutting down.", odocontext.GetPID(ctx)),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
response := openapi.InstanceGet200Response{
|
||||
Pid: int32(odocontext.GetPID(ctx)),
|
||||
ComponentDirectory: odocontext.GetWorkingDirectory(ctx),
|
||||
}
|
||||
return openapi.Response(http.StatusOK, response), nil
|
||||
}
|
||||
|
||||
@@ -3,16 +3,38 @@ package apiserver_impl
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
openapi "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
"github.com/redhat-developer/odo/pkg/podman"
|
||||
"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) {
|
||||
type ApiServer struct {
|
||||
PushWatcher <-chan struct{}
|
||||
}
|
||||
|
||||
defaultApiService := NewDefaultApiService()
|
||||
func StartServer(
|
||||
ctx context.Context,
|
||||
cancelFunc context.CancelFunc,
|
||||
port int,
|
||||
kubernetesClient kclient.ClientInterface,
|
||||
podmanClient podman.Client,
|
||||
stateClient state.Client,
|
||||
) ApiServer {
|
||||
|
||||
pushWatcher := make(chan struct{})
|
||||
defaultApiService := NewDefaultApiService(
|
||||
cancelFunc,
|
||||
pushWatcher,
|
||||
kubernetesClient,
|
||||
podmanClient,
|
||||
stateClient,
|
||||
)
|
||||
defaultApiController := openapi.NewDefaultApiController(defaultApiService)
|
||||
|
||||
router := openapi.NewRouter(defaultApiController)
|
||||
@@ -38,6 +60,9 @@ func StartServer(ctx context.Context, cancelFunc context.CancelFunc, port int, s
|
||||
server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: router}
|
||||
var errChan = make(chan error)
|
||||
go func() {
|
||||
server.BaseContext = func(net.Listener) context.Context {
|
||||
return ctx
|
||||
}
|
||||
err = server.ListenAndServe()
|
||||
errChan <- err
|
||||
}()
|
||||
@@ -54,4 +79,8 @@ func StartServer(ctx context.Context, cancelFunc context.CancelFunc, port int, s
|
||||
cancelFunc()
|
||||
}
|
||||
}()
|
||||
|
||||
return ApiServer{
|
||||
PushWatcher: pushWatcher,
|
||||
}
|
||||
}
|
||||
|
||||
249
pkg/component/describe/describe.go
Normal file
249
pkg/component/describe/describe.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package describe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/v2/pkg/devfile/generator"
|
||||
"github.com/devfile/library/v2/pkg/devfile/parser"
|
||||
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
"github.com/redhat-developer/odo/pkg/component"
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
clierrors "github.com/redhat-developer/odo/pkg/odo/cli/errors"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/feature"
|
||||
"github.com/redhat-developer/odo/pkg/odo/commonflags"
|
||||
fcontext "github.com/redhat-developer/odo/pkg/odo/commonflags/context"
|
||||
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
|
||||
"github.com/redhat-developer/odo/pkg/podman"
|
||||
"github.com/redhat-developer/odo/pkg/state"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// DescribeDevfileComponent describes the component defined by the devfile in the current directory
|
||||
func DescribeDevfileComponent(
|
||||
ctx context.Context,
|
||||
kubeClient kclient.ClientInterface,
|
||||
podmanClient podman.Client,
|
||||
stateClient state.Client,
|
||||
) (result api.Component, devfile *parser.DevfileObj, err error) {
|
||||
var (
|
||||
devfileObj = odocontext.GetEffectiveDevfileObj(ctx)
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
componentName = odocontext.GetComponentName(ctx)
|
||||
)
|
||||
|
||||
isPlatformFeatureEnabled := feature.IsEnabled(ctx, feature.GenericPlatformFlag)
|
||||
platform := fcontext.GetPlatform(ctx, "")
|
||||
switch platform {
|
||||
case "":
|
||||
if kubeClient == nil {
|
||||
log.Warning(kclient.NewNoConnectionError())
|
||||
}
|
||||
if isPlatformFeatureEnabled && podmanClient == nil {
|
||||
log.Warning(podman.NewPodmanNotFoundError(nil))
|
||||
}
|
||||
case commonflags.PlatformCluster:
|
||||
if kubeClient == nil {
|
||||
return api.Component{}, nil, kclient.NewNoConnectionError()
|
||||
}
|
||||
podmanClient = nil
|
||||
case commonflags.PlatformPodman:
|
||||
if podmanClient == nil {
|
||||
return api.Component{}, nil, podman.NewPodmanNotFoundError(nil)
|
||||
}
|
||||
kubeClient = nil
|
||||
}
|
||||
|
||||
// TODO(feloy) Pass PID with `--pid` flag
|
||||
allFwdPorts, err := stateClient.GetForwardedPorts(ctx)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
if isPlatformFeatureEnabled {
|
||||
for i := range allFwdPorts {
|
||||
if allFwdPorts[i].Platform == "" {
|
||||
allFwdPorts[i].Platform = commonflags.PlatformCluster
|
||||
}
|
||||
}
|
||||
}
|
||||
var forwardedPorts []api.ForwardedPort
|
||||
switch platform {
|
||||
case "":
|
||||
if isPlatformFeatureEnabled {
|
||||
// Read ports from all platforms
|
||||
forwardedPorts = allFwdPorts
|
||||
} else {
|
||||
// Limit to cluster ports only
|
||||
for _, p := range allFwdPorts {
|
||||
if p.Platform == "" || p.Platform == commonflags.PlatformCluster {
|
||||
forwardedPorts = append(forwardedPorts, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
case commonflags.PlatformCluster:
|
||||
for _, p := range allFwdPorts {
|
||||
if p.Platform == "" || p.Platform == commonflags.PlatformCluster {
|
||||
forwardedPorts = append(forwardedPorts, p)
|
||||
}
|
||||
}
|
||||
case commonflags.PlatformPodman:
|
||||
for _, p := range allFwdPorts {
|
||||
if p.Platform == commonflags.PlatformPodman {
|
||||
forwardedPorts = append(forwardedPorts, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runningOn, err := GetRunningOn(ctx, componentName, kubeClient, podmanClient)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
|
||||
var ingresses []api.ConnectionData
|
||||
var routes []api.ConnectionData
|
||||
if kubeClient != nil {
|
||||
ingresses, routes, err = component.ListRoutesAndIngresses(kubeClient, componentName, odocontext.GetApplication(ctx))
|
||||
if err != nil {
|
||||
err = clierrors.NewWarning("failed to get ingresses/routes", err)
|
||||
// Do not return the error yet, as it is only a warning
|
||||
}
|
||||
}
|
||||
|
||||
cmp := api.Component{
|
||||
DevfilePath: devfilePath,
|
||||
DevfileData: api.GetDevfileData(*devfileObj),
|
||||
DevForwardedPorts: forwardedPorts,
|
||||
RunningIn: api.MergeRunningModes(runningOn),
|
||||
RunningOn: runningOn,
|
||||
ManagedBy: "odo",
|
||||
Ingresses: ingresses,
|
||||
Routes: routes,
|
||||
}
|
||||
if !isPlatformFeatureEnabled {
|
||||
// Display RunningOn field only if the feature is enabled
|
||||
cmp.RunningOn = nil
|
||||
}
|
||||
updateWithRemoteSourceLocation(&cmp)
|
||||
return cmp, devfileObj, err
|
||||
}
|
||||
|
||||
// DescribeNamedComponent describes a component given its name
|
||||
func DescribeNamedComponent(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
kubeClient kclient.ClientInterface,
|
||||
podmanClient podman.Client,
|
||||
) (result api.Component, devfileObj *parser.DevfileObj, err error) {
|
||||
|
||||
isPlatformFeatureEnabled := feature.IsEnabled(ctx, feature.GenericPlatformFlag)
|
||||
platform := fcontext.GetPlatform(ctx, "")
|
||||
switch platform {
|
||||
case "":
|
||||
if isPlatformFeatureEnabled {
|
||||
//Get info from all platforms
|
||||
if kubeClient == nil {
|
||||
log.Warning(kclient.NewNoConnectionError())
|
||||
}
|
||||
if podmanClient == nil {
|
||||
log.Warning(podman.NewPodmanNotFoundError(nil))
|
||||
}
|
||||
} else {
|
||||
if kubeClient == nil {
|
||||
return api.Component{}, nil, kclient.NewNoConnectionError()
|
||||
}
|
||||
podmanClient = nil
|
||||
}
|
||||
case commonflags.PlatformCluster:
|
||||
if kubeClient == nil {
|
||||
return api.Component{}, nil, kclient.NewNoConnectionError()
|
||||
}
|
||||
podmanClient = nil
|
||||
case commonflags.PlatformPodman:
|
||||
if podmanClient == nil {
|
||||
return api.Component{}, nil, podman.NewPodmanNotFoundError(nil)
|
||||
}
|
||||
kubeClient = nil
|
||||
}
|
||||
|
||||
runningOn, err := GetRunningOn(ctx, name, kubeClient, podmanClient)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
|
||||
devfile, err := component.GetDevfileInfo(ctx, kubeClient, podmanClient, name)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
|
||||
var ingresses []api.ConnectionData
|
||||
var routes []api.ConnectionData
|
||||
if kubeClient != nil {
|
||||
ingresses, routes, err = component.ListRoutesAndIngresses(kubeClient, name, odocontext.GetApplication(ctx))
|
||||
if err != nil {
|
||||
return api.Component{}, nil, fmt.Errorf("failed to get ingresses/routes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmp := api.Component{
|
||||
DevfileData: &api.DevfileData{
|
||||
Devfile: devfile.Data,
|
||||
},
|
||||
RunningIn: api.MergeRunningModes(runningOn),
|
||||
RunningOn: runningOn,
|
||||
ManagedBy: "odo",
|
||||
Ingresses: ingresses,
|
||||
Routes: routes,
|
||||
}
|
||||
if !feature.IsEnabled(ctx, feature.GenericPlatformFlag) {
|
||||
// Display RunningOn field only if the feature is enabled
|
||||
cmp.RunningOn = nil
|
||||
}
|
||||
|
||||
return cmp, &devfile, nil
|
||||
}
|
||||
|
||||
func GetRunningOn(ctx context.Context, n string, kubeClient kclient.ClientInterface, podmanClient podman.Client) (map[string]api.RunningModes, error) {
|
||||
var runningOn map[string]api.RunningModes
|
||||
runningModesMap, err := component.GetRunningModes(ctx, kubeClient, podmanClient, n)
|
||||
if err != nil {
|
||||
if !errors.As(err, &component.NoComponentFoundError{}) {
|
||||
return nil, err
|
||||
}
|
||||
// it is ok if the component is not deployed
|
||||
runningModesMap = nil
|
||||
}
|
||||
if runningModesMap != nil {
|
||||
runningOn = make(map[string]api.RunningModes, len(runningModesMap))
|
||||
if kubeClient != nil && runningModesMap[kubeClient] != nil {
|
||||
runningOn[commonflags.PlatformCluster] = runningModesMap[kubeClient]
|
||||
}
|
||||
if podmanClient != nil && runningModesMap[podmanClient] != nil {
|
||||
runningOn[commonflags.PlatformPodman] = runningModesMap[podmanClient]
|
||||
}
|
||||
}
|
||||
return runningOn, nil
|
||||
}
|
||||
|
||||
func updateWithRemoteSourceLocation(cmp *api.Component) {
|
||||
components, err := cmp.DevfileData.Devfile.GetComponents(common.DevfileOptions{
|
||||
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, comp := range components {
|
||||
if comp.Container.GetMountSources() {
|
||||
if comp.Container.SourceMapping == "" {
|
||||
comp.Container.SourceMapping = generator.DevfileSourceVolumeMount
|
||||
err = cmp.DevfileData.Devfile.UpdateComponent(comp)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("error occurred while updating the component %s; cause: %s", comp.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,8 @@ type StartOptions struct {
|
||||
ForwardLocalhost bool
|
||||
// Variables to override in the Devfile
|
||||
Variables map[string]string
|
||||
// PushWatcher is a channel that will emit an event when Pushing files to the component is requested
|
||||
PushWatcher <-chan struct{}
|
||||
|
||||
Out io.Writer
|
||||
ErrOut io.Writer
|
||||
|
||||
@@ -7,15 +7,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/v2/pkg/devfile/generator"
|
||||
"github.com/devfile/library/v2/pkg/devfile/parser"
|
||||
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog"
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
"github.com/redhat-developer/odo/pkg/component"
|
||||
"github.com/redhat-developer/odo/pkg/component/describe"
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
clierrors "github.com/redhat-developer/odo/pkg/odo/cli/errors"
|
||||
@@ -26,7 +24,6 @@ import (
|
||||
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
|
||||
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
|
||||
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
|
||||
"github.com/redhat-developer/odo/pkg/podman"
|
||||
)
|
||||
|
||||
// ComponentRecommendedCommandName is the recommended component sub-command name
|
||||
@@ -128,232 +125,9 @@ func (o *ComponentOptions) RunForJsonOutput(ctx context.Context) (out interface{
|
||||
|
||||
func (o *ComponentOptions) run(ctx context.Context) (result api.Component, devfileObj *parser.DevfileObj, err error) {
|
||||
if o.nameFlag != "" {
|
||||
return o.describeNamedComponent(ctx, o.nameFlag)
|
||||
return describe.DescribeNamedComponent(ctx, o.nameFlag, o.clientset.KubernetesClient, o.clientset.PodmanClient)
|
||||
}
|
||||
return o.describeDevfileComponent(ctx)
|
||||
}
|
||||
|
||||
// describeNamedComponent describes a component given its name
|
||||
func (o *ComponentOptions) describeNamedComponent(ctx context.Context, name string) (result api.Component, devfileObj *parser.DevfileObj, err error) {
|
||||
var (
|
||||
kubeClient = o.clientset.KubernetesClient
|
||||
podmanClient = o.clientset.PodmanClient
|
||||
)
|
||||
|
||||
isPlatformFeatureEnabled := feature.IsEnabled(ctx, feature.GenericPlatformFlag)
|
||||
platform := fcontext.GetPlatform(ctx, "")
|
||||
switch platform {
|
||||
case "":
|
||||
if isPlatformFeatureEnabled {
|
||||
//Get info from all platforms
|
||||
if kubeClient == nil {
|
||||
log.Warning(kclient.NewNoConnectionError())
|
||||
}
|
||||
if podmanClient == nil {
|
||||
log.Warning(podman.NewPodmanNotFoundError(nil))
|
||||
}
|
||||
} else {
|
||||
if kubeClient == nil {
|
||||
return api.Component{}, nil, kclient.NewNoConnectionError()
|
||||
}
|
||||
podmanClient = nil
|
||||
}
|
||||
case commonflags.PlatformCluster:
|
||||
if kubeClient == nil {
|
||||
return api.Component{}, nil, kclient.NewNoConnectionError()
|
||||
}
|
||||
podmanClient = nil
|
||||
case commonflags.PlatformPodman:
|
||||
if podmanClient == nil {
|
||||
return api.Component{}, nil, podman.NewPodmanNotFoundError(nil)
|
||||
}
|
||||
kubeClient = nil
|
||||
}
|
||||
|
||||
runningOn, err := getRunningOn(ctx, name, kubeClient, podmanClient)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
|
||||
devfile, err := component.GetDevfileInfo(ctx, kubeClient, podmanClient, name)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
|
||||
var ingresses []api.ConnectionData
|
||||
var routes []api.ConnectionData
|
||||
if kubeClient != nil {
|
||||
ingresses, routes, err = component.ListRoutesAndIngresses(kubeClient, name, odocontext.GetApplication(ctx))
|
||||
if err != nil {
|
||||
return api.Component{}, nil, fmt.Errorf("failed to get ingresses/routes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmp := api.Component{
|
||||
DevfileData: &api.DevfileData{
|
||||
Devfile: devfile.Data,
|
||||
},
|
||||
RunningIn: api.MergeRunningModes(runningOn),
|
||||
RunningOn: runningOn,
|
||||
ManagedBy: "odo",
|
||||
Ingresses: ingresses,
|
||||
Routes: routes,
|
||||
}
|
||||
if !feature.IsEnabled(ctx, feature.GenericPlatformFlag) {
|
||||
// Display RunningOn field only if the feature is enabled
|
||||
cmp.RunningOn = nil
|
||||
}
|
||||
|
||||
return cmp, &devfile, nil
|
||||
}
|
||||
|
||||
// describeDevfileComponent describes the component defined by the devfile in the current directory
|
||||
func (o *ComponentOptions) describeDevfileComponent(ctx context.Context) (result api.Component, devfile *parser.DevfileObj, err error) {
|
||||
var (
|
||||
devfileObj = odocontext.GetEffectiveDevfileObj(ctx)
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
componentName = odocontext.GetComponentName(ctx)
|
||||
)
|
||||
var (
|
||||
kubeClient = o.clientset.KubernetesClient
|
||||
podmanClient = o.clientset.PodmanClient
|
||||
)
|
||||
|
||||
isPlatformFeatureEnabled := feature.IsEnabled(ctx, feature.GenericPlatformFlag)
|
||||
platform := fcontext.GetPlatform(ctx, "")
|
||||
switch platform {
|
||||
case "":
|
||||
if kubeClient == nil {
|
||||
log.Warning(kclient.NewNoConnectionError())
|
||||
}
|
||||
if isPlatformFeatureEnabled && podmanClient == nil {
|
||||
log.Warning(podman.NewPodmanNotFoundError(nil))
|
||||
}
|
||||
case commonflags.PlatformCluster:
|
||||
if kubeClient == nil {
|
||||
return api.Component{}, nil, kclient.NewNoConnectionError()
|
||||
}
|
||||
podmanClient = nil
|
||||
case commonflags.PlatformPodman:
|
||||
if podmanClient == nil {
|
||||
return api.Component{}, nil, podman.NewPodmanNotFoundError(nil)
|
||||
}
|
||||
kubeClient = nil
|
||||
}
|
||||
|
||||
// TODO(feloy) Pass PID with `--pid` flag
|
||||
allFwdPorts, err := o.clientset.StateClient.GetForwardedPorts(ctx)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
if isPlatformFeatureEnabled {
|
||||
for i := range allFwdPorts {
|
||||
if allFwdPorts[i].Platform == "" {
|
||||
allFwdPorts[i].Platform = commonflags.PlatformCluster
|
||||
}
|
||||
}
|
||||
}
|
||||
var forwardedPorts []api.ForwardedPort
|
||||
switch platform {
|
||||
case "":
|
||||
if isPlatformFeatureEnabled {
|
||||
// Read ports from all platforms
|
||||
forwardedPorts = allFwdPorts
|
||||
} else {
|
||||
// Limit to cluster ports only
|
||||
for _, p := range allFwdPorts {
|
||||
if p.Platform == "" || p.Platform == commonflags.PlatformCluster {
|
||||
forwardedPorts = append(forwardedPorts, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
case commonflags.PlatformCluster:
|
||||
for _, p := range allFwdPorts {
|
||||
if p.Platform == "" || p.Platform == commonflags.PlatformCluster {
|
||||
forwardedPorts = append(forwardedPorts, p)
|
||||
}
|
||||
}
|
||||
case commonflags.PlatformPodman:
|
||||
for _, p := range allFwdPorts {
|
||||
if p.Platform == commonflags.PlatformPodman {
|
||||
forwardedPorts = append(forwardedPorts, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runningOn, err := getRunningOn(ctx, componentName, kubeClient, podmanClient)
|
||||
if err != nil {
|
||||
return api.Component{}, nil, err
|
||||
}
|
||||
|
||||
var ingresses []api.ConnectionData
|
||||
var routes []api.ConnectionData
|
||||
if kubeClient != nil {
|
||||
ingresses, routes, err = component.ListRoutesAndIngresses(kubeClient, componentName, odocontext.GetApplication(ctx))
|
||||
if err != nil {
|
||||
err = clierrors.NewWarning("failed to get ingresses/routes", err)
|
||||
// Do not return the error yet, as it is only a warning
|
||||
}
|
||||
}
|
||||
|
||||
cmp := api.Component{
|
||||
DevfilePath: devfilePath,
|
||||
DevfileData: api.GetDevfileData(*devfileObj),
|
||||
DevForwardedPorts: forwardedPorts,
|
||||
RunningIn: api.MergeRunningModes(runningOn),
|
||||
RunningOn: runningOn,
|
||||
ManagedBy: "odo",
|
||||
Ingresses: ingresses,
|
||||
Routes: routes,
|
||||
}
|
||||
if !isPlatformFeatureEnabled {
|
||||
// Display RunningOn field only if the feature is enabled
|
||||
cmp.RunningOn = nil
|
||||
}
|
||||
updateWithRemoteSourceLocation(&cmp)
|
||||
return cmp, devfileObj, err
|
||||
}
|
||||
|
||||
func updateWithRemoteSourceLocation(cmp *api.Component) {
|
||||
components, err := cmp.DevfileData.Devfile.GetComponents(common.DevfileOptions{
|
||||
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, comp := range components {
|
||||
if comp.Container.GetMountSources() {
|
||||
if comp.Container.SourceMapping == "" {
|
||||
comp.Container.SourceMapping = generator.DevfileSourceVolumeMount
|
||||
err = cmp.DevfileData.Devfile.UpdateComponent(comp)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("error occurred while updating the component %s; cause: %s", comp.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRunningOn(ctx context.Context, n string, kubeClient kclient.ClientInterface, podmanClient podman.Client) (map[string]api.RunningModes, error) {
|
||||
var runningOn map[string]api.RunningModes
|
||||
runningModesMap, err := component.GetRunningModes(ctx, kubeClient, podmanClient, n)
|
||||
if err != nil {
|
||||
if !errors.As(err, &component.NoComponentFoundError{}) {
|
||||
return nil, err
|
||||
}
|
||||
// it is ok if the component is not deployed
|
||||
runningModesMap = nil
|
||||
}
|
||||
if runningModesMap != nil {
|
||||
runningOn = make(map[string]api.RunningModes, len(runningModesMap))
|
||||
if kubeClient != nil && runningModesMap[kubeClient] != nil {
|
||||
runningOn[commonflags.PlatformCluster] = runningModesMap[kubeClient]
|
||||
}
|
||||
if podmanClient != nil && runningModesMap[podmanClient] != nil {
|
||||
runningOn[commonflags.PlatformPodman] = runningModesMap[podmanClient]
|
||||
}
|
||||
}
|
||||
return runningOn, nil
|
||||
return describe.DescribeDevfileComponent(ctx, o.clientset.KubernetesClient, o.clientset.PodmanClient, o.clientset.StateClient)
|
||||
}
|
||||
|
||||
func printHumanReadableOutput(ctx context.Context, cmp api.Component, devfileObj *parser.DevfileObj) error {
|
||||
|
||||
@@ -113,7 +113,6 @@ func (o *DevOptions) PreInit() string {
|
||||
func (o *DevOptions) Complete(ctx context.Context, cmdline cmdline.Cmdline, args []string) error {
|
||||
// Define this first so that if user hits Ctrl+c very soon after running odo dev, odo doesn't panic
|
||||
o.ctx, o.cancel = context.WithCancel(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -258,9 +257,17 @@ func (o *DevOptions) Run(ctx context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var apiServer apiserver_impl.ApiServer
|
||||
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)
|
||||
apiServer = apiserver_impl.StartServer(
|
||||
ctx,
|
||||
o.cancel,
|
||||
o.apiServerPortFlag,
|
||||
o.clientset.KubernetesClient,
|
||||
o.clientset.PodmanClient,
|
||||
o.clientset.StateClient,
|
||||
)
|
||||
}
|
||||
|
||||
return o.clientset.DevClient.Start(
|
||||
@@ -278,6 +285,7 @@ func (o *DevOptions) Run(ctx context.Context) (err error) {
|
||||
Variables: variables,
|
||||
CustomForwardedPorts: o.forwardedPorts,
|
||||
CustomAddress: o.addressFlag,
|
||||
PushWatcher: apiServer.PushWatcher,
|
||||
Out: o.out,
|
||||
ErrOut: o.errOut,
|
||||
},
|
||||
|
||||
@@ -257,6 +257,10 @@ func (o *WatchClient) eventWatcher(
|
||||
sourcesTimer.Reset(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
case <-parameters.StartOptions.PushWatcher:
|
||||
o.forceSync = true
|
||||
sourcesTimer.Reset(100 * time.Millisecond)
|
||||
|
||||
case ev := <-o.deploymentWatcher.ResultChan():
|
||||
switch obj := ev.Object.(type) {
|
||||
case *appsv1.Deployment:
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/redhat-developer/odo/tests/helper"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/redhat-developer/odo/pkg/labels"
|
||||
"github.com/redhat-developer/odo/tests/helper"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
var _ = Describe("odo dev command with api server tests", func() {
|
||||
@@ -63,11 +69,157 @@ var _ = Describe("odo dev command with api server tests", func() {
|
||||
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))
|
||||
Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK))
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
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("odo dev is run with --api-server flag", func() {
|
||||
var (
|
||||
devSession helper.DevSession
|
||||
)
|
||||
BeforeEach(func() {
|
||||
opts := helper.DevSessionOpts{
|
||||
RunOnPodman: podman,
|
||||
StartAPIServer: true,
|
||||
EnvVars: []string{"ODO_EXPERIMENTAL_MODE=true"},
|
||||
}
|
||||
var err error
|
||||
devSession, err = helper.StartDevMode(opts)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
AfterEach(func() {
|
||||
devSession.Stop()
|
||||
devSession.WaitEnd()
|
||||
})
|
||||
It("should serve endpoints", func() {
|
||||
By("GETting /instance", func() {
|
||||
url := fmt.Sprintf("http://%s/instance", devSession.APIServerEndpoint)
|
||||
resp, err := http.Get(url)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK))
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
strBody := string(body)
|
||||
helper.JsonPathExist(strBody, "pid")
|
||||
helper.JsonPathContentIs(strBody, "componentDirectory", commonVar.Context)
|
||||
})
|
||||
By("GETting /component", func() {
|
||||
url := fmt.Sprintf("http://%s/component", devSession.APIServerEndpoint)
|
||||
resp, err := http.Get(url)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK))
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
strBody := string(body)
|
||||
helper.JsonPathContentIs(strBody, "devfilePath", filepath.Join(commonVar.Context, "devfile.yaml"))
|
||||
helper.JsonPathContentIs(strBody, "devfileData.devfile.metadata.name", cmpName)
|
||||
helper.JsonPathContentIs(strBody, "devfileData.supportedOdoFeatures.dev", "true")
|
||||
helper.JsonPathContentIs(strBody, "devfileData.supportedOdoFeatures.deploy", "false")
|
||||
helper.JsonPathContentIs(strBody, "devfileData.supportedOdoFeatures.debug", "false")
|
||||
helper.JsonPathContentIs(strBody, "managedBy", "odo")
|
||||
if podman {
|
||||
helper.JsonPathDoesNotExist(strBody, "runningOn.cluster")
|
||||
helper.JsonPathExist(strBody, "runningOn.podman")
|
||||
helper.JsonPathContentIs(strBody, "runningOn.podman.dev", "true")
|
||||
helper.JsonPathContentIs(strBody, "runningOn.podman.deploy", "false")
|
||||
} else {
|
||||
helper.JsonPathDoesNotExist(strBody, "runningOn.podman")
|
||||
helper.JsonPathExist(strBody, "runningOn.cluster")
|
||||
helper.JsonPathContentIs(strBody, "runningOn.cluster.dev", "true")
|
||||
helper.JsonPathContentIs(strBody, "runningOn.cluster.deploy", "false")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
When("/component/command endpoint is POSTed", func() {
|
||||
BeforeEach(func() {
|
||||
url := fmt.Sprintf("http://%s/component/command", devSession.APIServerEndpoint)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(`{"name": "push"}`)))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK))
|
||||
})
|
||||
|
||||
It("should trigger a push", func() {
|
||||
err := devSession.WaitSync()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
When("/instance endpoint is DELETEd", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
url := fmt.Sprintf("http://%s/instance", devSession.APIServerEndpoint)
|
||||
req, err := http.NewRequest(http.MethodDelete, url, bytes.NewBuffer([]byte{}))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK))
|
||||
})
|
||||
|
||||
It("should terminate the dev session", func() {
|
||||
devSession.WaitEnd()
|
||||
fmt.Println("<<< Session terminated >>>")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("odo is executed with --no-watch and --api-server flags", helper.LabelPodmanIf(podman, func() {
|
||||
|
||||
var devSession helper.DevSession
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
args := []string{"--no-watch"}
|
||||
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
|
||||
CmdlineArgs: args,
|
||||
RunOnPodman: podman,
|
||||
StartAPIServer: true,
|
||||
EnvVars: []string{"ODO_EXPERIMENTAL_MODE=true"},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
devSession.Stop()
|
||||
devSession.WaitEnd()
|
||||
})
|
||||
|
||||
When("a file in component directory is modified", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "App started", "App is super started")
|
||||
devSession.CheckNotSynced(10 * time.Second)
|
||||
})
|
||||
|
||||
When("/component/command endpoint is POSTed", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
url := fmt.Sprintf("http://%s/component/command", devSession.APIServerEndpoint)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(`{"name": "push"}`)))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK))
|
||||
})
|
||||
|
||||
It("should trigger a push", func() {
|
||||
err := devSession.WaitSync()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
|
||||
execResult, _ := component.Exec("runtime", []string{"cat", "/projects/server.js"}, pointer.Bool(true))
|
||||
Expect(execResult).To(ContainSubstring("App is super started"))
|
||||
})
|
||||
})
|
||||
})
|
||||
}))
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user