Browse Source

[POR-1798] Porter yaml v2 porter app run command (#3628)

Feroze Mohideen 2 năm trước cách đây
mục cha
commit
df07de771c

+ 22 - 0
api/client/porter_app.go

@@ -462,3 +462,25 @@ func (c *Client) CreateOrUpdateAppEnvironment(
 
 	return resp, err
 }
+
+// PorterYamlV2Pods gets all pods for a given deployment target id and app name
+func (c *Client) PorterYamlV2Pods(
+	ctx context.Context,
+	projectID, clusterID uint,
+	porterAppName string,
+	req *types.PorterYamlV2PodsRequest,
+) (*types.GetReleaseAllPodsResponse, error) {
+	resp := &types.GetReleaseAllPodsResponse{}
+
+	err := c.getRequest(
+		fmt.Sprintf(
+			"/projects/%d/clusters/%d/apps/%s/pods",
+			projectID, clusterID,
+			porterAppName,
+		),
+		req,
+		resp,
+	)
+
+	return resp, err
+}

+ 6 - 1
api/server/handlers/porter_app/pod_status.go

@@ -108,7 +108,12 @@ func (c *PodStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	pods := []v1.Pod{}
 
-	selectors := fmt.Sprintf("porter.run/service-name=%s,porter.run/deployment-target-id=%s,porter.run/app-name=%s", request.ServiceName, request.DeploymentTargetID, appName)
+	var selectors string
+	if request.ServiceName == "" {
+		selectors = fmt.Sprintf("porter.run/deployment-target-id=%s,porter.run/app-name=%s", request.DeploymentTargetID, appName)
+	} else {
+		selectors = fmt.Sprintf("porter.run/service-name=%s,porter.run/deployment-target-id=%s,porter.run/app-name=%s", request.ServiceName, request.DeploymentTargetID, appName)
+	}
 	podsList, err := agent.GetPodsByLabel(selectors, namespace)
 	if err != nil {
 		err = telemetry.Error(ctx, span, err, "unable to get pods by label")

+ 5 - 0
api/types/porter_app.go

@@ -170,3 +170,8 @@ type EnvironmentGroupListItem struct {
 	// LinkedApplications is the list of applications this env group is linked to
 	LinkedApplications []string `json:"linked_applications,omitempty"`
 }
+
+// PorterYamlV2PodsRequest is the request object for client.PorterYamlV2Pods
+type PorterYamlV2PodsRequest struct {
+	DeploymentTargetID string `schema:"deployment_target_id"`
+}

+ 152 - 26
cli/cmd/commands/app.go

@@ -43,6 +43,13 @@ var (
 	appMemoryMi      int
 )
 
+const (
+	// CommandPrefix_CNB_LIFECYCLE_LAUNCHER is the prefix for the container start command if the image is built using heroku buildpacks
+	CommandPrefix_CNB_LIFECYCLE_LAUNCHER = "/cnb/lifecycle/launcher"
+	// CommandPrefix_LAUNCHER is a shortened form of the above
+	CommandPrefix_LAUNCHER = "launcher"
+)
+
 func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
 	appCmd := &cobra.Command{
 		Use:   "app",
@@ -157,38 +164,35 @@ func appRun(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client a
 
 	color.New(color.FgGreen).Println("Attempting to run", strings.Join(execArgs, " "), "for application", args[0])
 
-	appNamespace = fmt.Sprintf("porter-stack-%s", args[0])
+	project, err := client.GetProject(ctx, cliConfig.Project)
+	if err != nil {
+		return fmt.Errorf("could not retrieve project from Porter API. Please contact support@porter.run")
+	}
+
+	var podsSimple []appPodSimple
 
-	if len(execArgs) > 0 {
-		res, err := client.GetPorterApp(ctx, cliConfig.Project, cliConfig.Cluster, args[0])
+	// updated exec args includes launcher command prepended if needed, otherwise it is the same as execArgs
+	var updatedExecArgs []string
+	if project.ValidateApplyV2 {
+		podsSimple, updatedExecArgs, namespace, err = getPodsFromV2PorterYaml(ctx, execArgs, client, cliConfig, args[0])
 		if err != nil {
-			return fmt.Errorf("Unable to run command: %w", err)
-		}
-		if res.Name == "" {
-			return fmt.Errorf("An application named \"%s\" was not found in your project (ID: %d). Please check your spelling and try again.", args[0], cliConfig.Project)
+			return err
 		}
-
-		if res.Builder != "" &&
-			(strings.Contains(res.Builder, "heroku") ||
-				strings.Contains(res.Builder, "paketo")) &&
-			execArgs[0] != "/cnb/lifecycle/launcher" &&
-			execArgs[0] != "launcher" {
-			// this is a buildpacks release using a heroku builder, prepend the launcher
-			execArgs = append([]string{"/cnb/lifecycle/launcher"}, execArgs...)
+		appNamespace = namespace
+	} else {
+		appNamespace = fmt.Sprintf("porter-stack-%s", args[0])
+		podsSimple, updatedExecArgs, err = getPodsFromV1PorterYaml(ctx, execArgs, client, cliConfig, args[0], appNamespace)
+		if err != nil {
+			return err
 		}
 	}
 
-	podsSimple, err := appGetPods(ctx, cliConfig, client, appNamespace, args[0])
-	if err != nil {
-		return fmt.Errorf("Could not retrieve list of pods: %s", err.Error())
-	}
-
 	// if length of pods is 0, throw error
 	var selectedPod appPodSimple
 
 	if len(podsSimple) == 0 {
 		return fmt.Errorf("At least one pod must exist in this deployment.")
-	} else if !appInteractive || len(podsSimple) == 1 {
+	} else if !appExistingPod || len(podsSimple) == 1 {
 		selectedPod = podsSimple[0]
 	} else {
 		podNames := make([]string, 0)
@@ -260,11 +264,34 @@ func appRun(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client a
 		return fmt.Errorf("Could not retrieve kube credentials: %s", err.Error())
 	}
 
+	imageName, err := getImageNameFromPod(ctx, config.Clientset, appNamespace, selectedPod.Name, selectedContainerName)
+	if err != nil {
+		return err
+	}
+
 	if appExistingPod {
-		return appExecuteRun(config, appNamespace, selectedPod.Name, selectedContainerName, execArgs)
+		_, _ = color.New(color.FgGreen).Printf("Connecting to existing pod which is running an image named: %s\n", imageName)
+		return appExecuteRun(config, appNamespace, selectedPod.Name, selectedContainerName, updatedExecArgs)
 	}
 
-	return appExecuteRunEphemeral(ctx, config, appNamespace, selectedPod.Name, selectedContainerName, execArgs)
+	_, _ = color.New(color.FgGreen).Println("Creating a copy pod using image: ", imageName)
+
+	return appExecuteRunEphemeral(ctx, config, appNamespace, selectedPod.Name, selectedContainerName, updatedExecArgs)
+}
+
+func getImageNameFromPod(ctx context.Context, clientset *kubernetes.Clientset, namespace, podName, containerName string) (string, error) {
+	pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
+	if err != nil {
+		return "", err
+	}
+
+	for _, container := range pod.Spec.Containers {
+		if container.Name == containerName {
+			return container.Image, nil
+		}
+	}
+
+	return "", fmt.Errorf("could not find container %s in pod %s", containerName, podName)
 }
 
 func appCleanup(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ []string) error {
@@ -416,17 +443,32 @@ type appPodSimple struct {
 	ContainerNames []string
 }
 
-func appGetPods(ctx context.Context, cliConfig config.CLIConfig, client api.Client, namespace, releaseName string) ([]appPodSimple, error) {
+func appGetPodsV1PorterYaml(ctx context.Context, cliConfig config.CLIConfig, client api.Client, namespace, releaseName string) ([]appPodSimple, bool, error) {
 	pID := cliConfig.Project
 	cID := cliConfig.Cluster
 
+	var containerHasLauncherStartCommand bool
+
 	resp, err := client.GetK8sAllPods(ctx, pID, cID, namespace, releaseName)
 	if err != nil {
-		return nil, err
+		return nil, containerHasLauncherStartCommand, err
 	}
 
+	if resp == nil {
+		return nil, containerHasLauncherStartCommand, errors.New("get pods response is nil")
+	}
 	pods := *resp
 
+	if len(pods) == 0 {
+		return nil, containerHasLauncherStartCommand, errors.New("no running pods found for this application")
+	}
+
+	for _, container := range pods[0].Spec.Containers {
+		if len(container.Command) > 0 && (container.Command[0] == CommandPrefix_LAUNCHER || container.Command[0] == CommandPrefix_CNB_LIFECYCLE_LAUNCHER) {
+			containerHasLauncherStartCommand = true
+		}
+	}
+
 	res := make([]appPodSimple, 0)
 
 	for _, pod := range pods {
@@ -444,7 +486,65 @@ func appGetPods(ctx context.Context, cliConfig config.CLIConfig, client api.Clie
 		}
 	}
 
-	return res, nil
+	return res, containerHasLauncherStartCommand, nil
+}
+
+func appGetPodsV2PorterYaml(ctx context.Context, cliConfig config.CLIConfig, client api.Client, porterAppName string) ([]appPodSimple, string, bool, error) {
+	pID := cliConfig.Project
+	cID := cliConfig.Cluster
+	var containerHasLauncherStartCommand bool
+
+	targetResp, err := client.DefaultDeploymentTarget(ctx, pID, cID)
+	if err != nil {
+		return nil, "", containerHasLauncherStartCommand, fmt.Errorf("error calling default deployment target endpoint: %w", err)
+	}
+
+	if targetResp.DeploymentTargetID == "" {
+		return nil, "", containerHasLauncherStartCommand, errors.New("deployment target id is empty")
+	}
+
+	resp, err := client.PorterYamlV2Pods(ctx, pID, cID, porterAppName, &types.PorterYamlV2PodsRequest{
+		DeploymentTargetID: targetResp.DeploymentTargetID,
+	})
+	if err != nil {
+		return nil, "", containerHasLauncherStartCommand, err
+	}
+
+	if resp == nil {
+		return nil, "", containerHasLauncherStartCommand, errors.New("get pods response is nil")
+	}
+	pods := *resp
+
+	if len(pods) == 0 {
+		return nil, "", containerHasLauncherStartCommand, errors.New("no running pods found for this application")
+	}
+
+	namespace := pods[0].Namespace
+
+	for _, container := range pods[0].Spec.Containers {
+		if len(container.Command) > 0 && (container.Command[0] == CommandPrefix_LAUNCHER || container.Command[0] == CommandPrefix_CNB_LIFECYCLE_LAUNCHER) {
+			containerHasLauncherStartCommand = true
+		}
+	}
+
+	res := make([]appPodSimple, 0)
+
+	for _, pod := range pods {
+		if pod.Status.Phase == v1.PodRunning {
+			containerNames := make([]string, 0)
+
+			for _, container := range pod.Spec.Containers {
+				containerNames = append(containerNames, container.Name)
+			}
+
+			res = append(res, appPodSimple{
+				Name:           pod.ObjectMeta.Name,
+				ContainerNames: containerNames,
+			})
+		}
+	}
+
+	return res, namespace, containerHasLauncherStartCommand, nil
 }
 
 func appExecuteRun(config *AppPorterRunSharedConfig, namespace, name, container string, args []string) error {
@@ -1084,3 +1184,29 @@ func appUpdateTag(ctx context.Context, _ *types.GetAuthenticatedUserResponse, cl
 	color.New(color.FgGreen).Printf("Successfully updated application %s to use tag \"%s\"\n", args[0], appTag)
 	return nil
 }
+
+func getPodsFromV1PorterYaml(ctx context.Context, execArgs []string, client api.Client, cliConfig config.CLIConfig, porterAppName string, namespace string) ([]appPodSimple, []string, error) {
+	podsSimple, containerHasLauncherStartCommand, err := appGetPodsV1PorterYaml(ctx, cliConfig, client, namespace, porterAppName)
+	if err != nil {
+		return nil, nil, fmt.Errorf("could not retrieve list of pods: %s", err.Error())
+	}
+
+	if len(execArgs) > 0 && execArgs[0] != CommandPrefix_CNB_LIFECYCLE_LAUNCHER && execArgs[0] != CommandPrefix_LAUNCHER && containerHasLauncherStartCommand {
+		execArgs = append([]string{CommandPrefix_CNB_LIFECYCLE_LAUNCHER}, execArgs...)
+	}
+
+	return podsSimple, execArgs, nil
+}
+
+func getPodsFromV2PorterYaml(ctx context.Context, execArgs []string, client api.Client, cliConfig config.CLIConfig, porterAppName string) ([]appPodSimple, []string, string, error) {
+	podsSimple, namespace, containerHasLauncherStartCommand, err := appGetPodsV2PorterYaml(ctx, cliConfig, client, porterAppName)
+	if err != nil {
+		return nil, nil, "", fmt.Errorf("could not retrieve list of pods: %w", err)
+	}
+
+	if len(execArgs) > 0 && execArgs[0] != CommandPrefix_CNB_LIFECYCLE_LAUNCHER && execArgs[0] != CommandPrefix_LAUNCHER && containerHasLauncherStartCommand {
+		execArgs = append([]string{CommandPrefix_CNB_LIFECYCLE_LAUNCHER}, execArgs...)
+	}
+
+	return podsSimple, execArgs, namespace, nil
+}