...

Source file src/github.com/redhat-developer/odo/pkg/dev/kubedev/innerloop.go

Documentation: github.com/redhat-developer/odo/pkg/dev/kubedev

     1  package kubedev
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    10  	parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
    11  	corev1 "k8s.io/api/core/v1"
    12  
    13  	"github.com/redhat-developer/odo/pkg/component"
    14  	"github.com/redhat-developer/odo/pkg/dev/common"
    15  	"github.com/redhat-developer/odo/pkg/devfile/image"
    16  	"github.com/redhat-developer/odo/pkg/libdevfile"
    17  	"github.com/redhat-developer/odo/pkg/log"
    18  	odocontext "github.com/redhat-developer/odo/pkg/odo/context"
    19  	"github.com/redhat-developer/odo/pkg/port"
    20  	"github.com/redhat-developer/odo/pkg/sync"
    21  	"github.com/redhat-developer/odo/pkg/watch"
    22  
    23  	"k8s.io/klog"
    24  )
    25  
    26  func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) error {
    27  	var (
    28  		componentName = odocontext.GetComponentName(ctx)
    29  		devfilePath   = odocontext.GetDevfilePath(ctx)
    30  		path          = filepath.Dir(devfilePath)
    31  	)
    32  
    33  	// Now the Deployment has a Ready replica, we can get the Pod to work inside it
    34  	pod, err := o.kubernetesClient.GetPodUsingComponentName(componentName)
    35  	if err != nil {
    36  		return fmt.Errorf("unable to get pod for component %s: %w", componentName, err)
    37  	}
    38  
    39  	podChanged := componentStatus.GetState() == watch.StateWaitDeployment
    40  
    41  	execRequired, err := o.syncFiles(ctx, parameters, pod, podChanged)
    42  	if err != nil {
    43  		componentStatus.SetState(watch.StateReady)
    44  		return fmt.Errorf("failed to sync to component with name %s: %w", componentName, err)
    45  	}
    46  
    47  	if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(parameters.Devfile) {
    48  		// PostStart events from the devfile will only be executed when the component
    49  		// didn't previously exist
    50  		handler := component.NewRunHandler(
    51  			ctx,
    52  			o.kubernetesClient,
    53  			o.execClient,
    54  			o.configAutomountClient,
    55  			// TODO(feloy) set these values when we want to support Apply Image/Kubernetes/OpenShift commands for PostStart commands
    56  			nil, nil,
    57  			component.HandlerOptions{
    58  				PodName:           pod.Name,
    59  				ContainersRunning: component.GetContainersNames(pod),
    60  				Msg:               "Executing post-start command in container",
    61  			},
    62  		)
    63  		err = libdevfile.ExecPostStartEvents(ctx, parameters.Devfile, handler)
    64  		if err != nil {
    65  			return err
    66  		}
    67  	}
    68  	componentStatus.PostStartEventsDone = true
    69  
    70  	var hasRunOrDebugCmd bool
    71  	innerLoopWithCommands := !parameters.StartOptions.SkipCommands
    72  	if innerLoopWithCommands {
    73  		var (
    74  			cmdKind = devfilev1.RunCommandGroupKind
    75  			cmdName = parameters.StartOptions.RunCommand
    76  		)
    77  		if parameters.StartOptions.Debug {
    78  			cmdKind = devfilev1.DebugCommandGroupKind
    79  			cmdName = parameters.StartOptions.DebugCommand
    80  		}
    81  
    82  		var cmd devfilev1.Command
    83  		cmd, hasRunOrDebugCmd, err = libdevfile.GetCommand(parameters.Devfile, cmdName, cmdKind)
    84  		if err != nil {
    85  			return err
    86  		}
    87  
    88  		var running bool
    89  		var isComposite bool
    90  		var runHandler libdevfile.Handler
    91  		if hasRunOrDebugCmd {
    92  			var commandType devfilev1.CommandType
    93  			commandType, err = parsercommon.GetCommandType(cmd)
    94  			if err != nil {
    95  				return err
    96  			}
    97  
    98  			cmdHandler := component.NewRunHandler(
    99  				ctx,
   100  				o.kubernetesClient,
   101  				o.execClient,
   102  				o.configAutomountClient,
   103  				o.filesystem,
   104  				image.SelectBackend(ctx),
   105  				component.HandlerOptions{
   106  					PodName:           pod.GetName(),
   107  					ContainersRunning: component.GetContainersNames(pod),
   108  					Devfile:           parameters.Devfile,
   109  					Path:              path,
   110  				},
   111  			)
   112  
   113  			if commandType == devfilev1.ExecCommandType {
   114  				running, err = cmdHandler.IsRemoteProcessForCommandRunning(ctx, cmd, pod.Name)
   115  				if err != nil {
   116  					return err
   117  				}
   118  			} else if commandType == devfilev1.CompositeCommandType {
   119  				// this handler will run each command in this composite command individually,
   120  				// and will determine whether each command is running or not.
   121  				isComposite = true
   122  			} else {
   123  				return fmt.Errorf("unsupported type %q for Devfile command %s, only exec and composite are handled",
   124  					commandType, cmd.Id)
   125  			}
   126  
   127  			cmdHandler.ComponentExists = running || isComposite
   128  			runHandler = cmdHandler
   129  		}
   130  
   131  		klog.V(4).Infof("running=%v, execRequired=%v",
   132  			running, execRequired)
   133  
   134  		if isComposite || !running || execRequired {
   135  			// Invoke the build command once (before calling libdevfile.ExecuteCommandByNameAndKind), as, if cmd is a composite command,
   136  			// the handler we pass will be called for each command in that composite command.
   137  			doExecuteBuildCommand := func() error {
   138  				execHandler := component.NewRunHandler(
   139  					ctx,
   140  					o.kubernetesClient,
   141  					o.execClient,
   142  					o.configAutomountClient,
   143  
   144  					// TODO(feloy) set these values when we want to support Apply Image/Kubernetes/OpenShift commands for PostStart commands
   145  					nil, nil, component.HandlerOptions{
   146  						PodName:           pod.Name,
   147  						ComponentExists:   running,
   148  						ContainersRunning: component.GetContainersNames(pod),
   149  						Msg:               "Building your application in container",
   150  					},
   151  				)
   152  				return libdevfile.Build(ctx, parameters.Devfile, parameters.StartOptions.BuildCommand, execHandler)
   153  			}
   154  			if err = doExecuteBuildCommand(); err != nil {
   155  				componentStatus.SetState(watch.StateReady)
   156  				return err
   157  			}
   158  
   159  			if hasRunOrDebugCmd {
   160  				err = libdevfile.ExecuteCommandByNameAndKind(ctx, parameters.Devfile, cmdName, cmdKind, runHandler, false)
   161  				if err != nil {
   162  					return err
   163  				}
   164  				componentStatus.RunExecuted = true
   165  			} else {
   166  				msg := fmt.Sprintf("Missing default %v command", cmdKind)
   167  				if cmdName != "" {
   168  					msg = fmt.Sprintf("Missing %v command with name %q", cmdKind, cmdName)
   169  				}
   170  				log.Warning(msg)
   171  			}
   172  		}
   173  	}
   174  
   175  	if podChanged || o.portsChanged {
   176  		o.portForwardClient.StopPortForwarding(ctx, componentName)
   177  	}
   178  
   179  	if innerLoopWithCommands && hasRunOrDebugCmd && len(o.portsToForward) != 0 {
   180  		// Check that the application is actually listening on the ports declared in the Devfile, so we are sure that port-forwarding will work
   181  		appReadySpinner := log.Spinner("Waiting for the application to be ready")
   182  		err = o.checkAppPorts(ctx, pod.Name, o.portsToForward)
   183  		appReadySpinner.End(err == nil)
   184  		if err != nil {
   185  			log.Warningf("Port forwarding might not work correctly: %v", err)
   186  			log.Warning("Running `odo logs --follow` might help in identifying the problem.")
   187  			fmt.Fprintln(log.GetStdout())
   188  		}
   189  	}
   190  
   191  	err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.StartOptions.Debug, parameters.StartOptions.RandomPorts, log.GetStdout(), parameters.StartOptions.ErrOut, parameters.StartOptions.CustomForwardedPorts, parameters.StartOptions.CustomAddress)
   192  	if err != nil {
   193  		return common.NewErrPortForward(err)
   194  	}
   195  	componentStatus.EndpointsForwarded = o.portForwardClient.GetForwardedPorts()
   196  
   197  	componentStatus.SetState(watch.StateReady)
   198  	return nil
   199  }
   200  
   201  func (o *DevClient) syncFiles(ctx context.Context, parameters common.PushParameters, pod *corev1.Pod, podChanged bool) (bool, error) {
   202  	var (
   203  		devfileObj    = odocontext.GetEffectiveDevfileObj(ctx)
   204  		componentName = odocontext.GetComponentName(ctx)
   205  		devfilePath   = odocontext.GetDevfilePath(ctx)
   206  		path          = filepath.Dir(devfilePath)
   207  	)
   208  
   209  	s := log.Spinner("Syncing files into the container")
   210  	defer s.End(false)
   211  
   212  	// Find at least one pod with the source volume mounted, error out if none can be found
   213  	containerName, syncFolder, err := common.GetFirstContainerWithSourceVolume(pod.Spec.Containers)
   214  	if err != nil {
   215  		return false, fmt.Errorf("error while retrieving container from pod %s with a mounted project volume: %w", pod.GetName(), err)
   216  	}
   217  
   218  	syncFilesMap := make(map[string]string)
   219  	var devfileCmd devfilev1.Command
   220  	innerLoopWithCommands := !parameters.StartOptions.SkipCommands
   221  	if innerLoopWithCommands {
   222  		var (
   223  			cmdKind = devfilev1.RunCommandGroupKind
   224  			cmdName = parameters.StartOptions.RunCommand
   225  		)
   226  		if parameters.StartOptions.Debug {
   227  			cmdKind = devfilev1.DebugCommandGroupKind
   228  			cmdName = parameters.StartOptions.DebugCommand
   229  		}
   230  		var hasCmd bool
   231  		devfileCmd, hasCmd, err = libdevfile.GetCommand(*devfileObj, cmdName, cmdKind)
   232  		if err != nil {
   233  			return false, err
   234  		}
   235  		if hasCmd {
   236  			syncFilesMap = common.GetSyncFilesFromAttributes(devfileCmd)
   237  		} else {
   238  			klog.V(2).Infof("no command found with name %q and kind %v, syncing files without command attributes", cmdName, cmdKind)
   239  		}
   240  	}
   241  
   242  	// Get a sync adapter. Check if project files have changed and sync accordingly
   243  	compInfo := sync.ComponentInfo{
   244  		ComponentName: componentName,
   245  		ContainerName: containerName,
   246  		PodName:       pod.GetName(),
   247  		SyncFolder:    syncFolder,
   248  	}
   249  
   250  	syncParams := sync.SyncParameters{
   251  		Path:                     path,
   252  		WatchFiles:               parameters.WatchFiles,
   253  		WatchDeletedFiles:        parameters.WatchDeletedFiles,
   254  		IgnoredFiles:             parameters.StartOptions.IgnorePaths,
   255  		DevfileScanIndexForWatch: parameters.DevfileScanIndexForWatch,
   256  
   257  		CompInfo:  compInfo,
   258  		ForcePush: !o.deploymentExists || podChanged,
   259  		Files:     syncFilesMap,
   260  	}
   261  
   262  	execRequired, err := o.syncClient.SyncFiles(ctx, syncParams)
   263  	if err != nil {
   264  		return false, err
   265  	}
   266  	s.End(true)
   267  	return execRequired, nil
   268  }
   269  
   270  func (o *DevClient) checkAppPorts(ctx context.Context, podName string, portsToFwd map[string][]devfilev1.Endpoint) error {
   271  	containerPortsMapping := make(map[string][]int)
   272  	for c, ports := range portsToFwd {
   273  		for _, p := range ports {
   274  			containerPortsMapping[c] = append(containerPortsMapping[c], p.TargetPort)
   275  		}
   276  	}
   277  	return port.CheckAppPortsListening(ctx, o.execClient, podName, containerPortsMapping, 1*time.Minute)
   278  }
   279  

View as plain text