func run()

in cmd/cloudshell_open/main.go [125:478]


func run(opts runOpts) error {
	ctx := context.Background()
	highlight := func(s string) string { return color.CyanString(s) }
	parameter := func(s string) string { return parameterLabel.Sprint(s) }
	cmdColor := color.New(color.FgHiBlue)

	repo := opts.repoURL
	if repo == "" {
		return fmt.Errorf("--%s not specified", flRepoURL)
	}

	trusted := os.Getenv("TRUSTED_ENVIRONMENT") == "true"
	if !trusted {
		fmt.Printf("%s You launched this custom Cloud Shell image as \"Do not trust\".\n"+
			"In this mode, your credentials are not available and this experience\n"+
			"cannot deploy to Cloud Run. Start over and \"Trust\" the image.\n", errorLabel.Sprint("Error:"))
		return errors.New("aborting due to untrusted cloud shell environment")
	}

	end := logProgress("Waiting for Cloud Shell authorization...",
		"",
		"Failed to get GCP credentials. Please authorize Cloud Shell if you're presented with a prompt.",
	)
	time.Sleep(time.Second * 2)
	waitCtx, cancelWait := context.WithTimeout(ctx, reauthCredentialsWaitTimeout)
	err := waitCredsAvailable(waitCtx, reauthCredentialsPollingInterval)
	cancelWait()
	end(err == nil)
	if err != nil {
		return err
	}

	end = logProgress(fmt.Sprintf("Cloning git repository %s...", highlight(repo)),
		fmt.Sprintf("Cloned git repository %s.", highlight(repo)),
		fmt.Sprintf("Failed to clone git repository %s", highlight(repo)))
	cloneDir, err := handleRepo(repo)
	if trusted && os.Getenv("SKIP_CLONE_REPORTING") == "" {
		// TODO(ahmetb) had to introduce SKIP_CLONE_REPORTING env var here
		// to skip connecting to :8998 while testing locally if this var is set.
		if err := signalRepoCloneStatus(err == nil); err != nil {
			return err
		}
	}
	end(err == nil)
	if err != nil {
		return err
	}

	if opts.gitBranch != "" {
		if err := gitCheckout(cloneDir, opts.gitBranch); err != nil {
			return fmt.Errorf("failed to checkout revision %q: %+v", opts.gitBranch, err)
		}
	}

	appDir := cloneDir
	if opts.subDir != "" {
		// verify if --dir is valid
		appDir = filepath.Join(cloneDir, opts.subDir)
		if fi, err := os.Stat(appDir); err != nil {
			if os.IsNotExist(err) {
				return fmt.Errorf("sub-directory doesn't exist in the cloned repository: %s", appDir)
			}
			return fmt.Errorf("failed to check sub-directory in the repo: %v", err)
		} else if !fi.IsDir() {
			return fmt.Errorf("specified sub-directory path %s is not a directory", appDir)
		}
	}

	appFile, err := getAppFile(appDir)
	if err != nil {
		return fmt.Errorf("error attempting to read the app.json from the cloned repository: %+v", err)
	}

	project := os.Getenv("GOOGLE_CLOUD_PROJECT")

	for project == "" {
		var projects []string

		for len(projects) == 0 {
			end = logProgress("Retrieving your projects...",
				"Queried list of your projects",
				"Failed to retrieve your projects.",
			)
			projects, err = listProjects()
			end(err == nil)
			if err != nil {
				return err
			}

			if len(projects) == 0 {
				fmt.Print(errorPrefix + " " + warningLabel.Sprint("You don't have any projects to deploy into."))
			}
		}

		if len(projects) > 1 {
			fmt.Printf(successPrefix+" Found %s projects in your GCP account.\n",
				successLabel.Sprintf("%d", len(projects)))
		}

		project, err = promptProject(projects)
		if err != nil {
			fmt.Println(errorPrefix + " " + warningLabel.Sprint("You need to create a project"))
			err := promptInstrumentless()
			if err != nil {
				return err
			}
		}
	}

	if err := waitForBilling(project, func(p string) error {
		projectLabel := color.New(color.Bold, color.FgHiCyan).Sprint(project)

		fmt.Println(fmt.Sprintf(errorPrefix+" Project %s does not have an active billing account!", projectLabel))

		billingAccounts, err := billingAccounts()
		if err != nil {
			return fmt.Errorf("could not get billing accounts: %v", err)
		}

		useExisting := false

		if len(billingAccounts) > 0 {
			useExisting, err = prompUseExistingBillingAccount(project)
			if err != nil {
				return err
			}
		}

		if !useExisting {
			err := promptInstrumentless()
			if err != nil {
				return err
			}
		}

		fmt.Println(infoPrefix + " Link the billing account to the project:" +
			"\n  " + linkLabel.Sprintf("https://console.cloud.google.com/billing?project=%s", project))

		fmt.Println(questionPrefix + " " + "Once you're done, press " + parameterLabel.Sprint("Enter") + " to continue: ")

		if _, err := bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil {
			return err
		}

		// TODO(jamesward) automatically set billing account on project

		return nil
	}); err != nil {
		return err
	}

	end = logProgress(
		fmt.Sprintf("Enabling Cloud Run API on project %s...", highlight(project)),
		fmt.Sprintf("Enabled Cloud Run API on project %s.", highlight(project)),
		fmt.Sprintf("Failed to enable required APIs on project %s.", highlight(project)))
	err = enableAPIs(project, []string{"run.googleapis.com", "artifactregistry.googleapis.com"})
	end(err == nil)
	if err != nil {
		return err
	}

	region := os.Getenv("GOOGLE_CLOUD_REGION")

	if region == "" {
		region, err = promptDeploymentRegion(ctx, project)
		if err != nil {
			return err
		}
	}

	end = logProgress(
		fmt.Sprintf("Setting up %s in region %s (if it doesn't already exist)", highlight(artifactRegistry), highlight(region)),
		fmt.Sprintf("Set up %s in region %s (if it doesn't already exist)", highlight(artifactRegistry), highlight(region)),
		"Failed to setup artifact registry.")
	err = createArtifactRegistry(project, region, artifactRegistry)
	end(err == nil)
	if err != nil {
		return err
	}

	repoName := filepath.Base(appDir)
	serviceName := repoName
	if appFile.Name != "" {
		serviceName = appFile.Name
	}
	serviceName, err = tryFixServiceName(serviceName)
	if err != nil {
		return err
	}

	image := fmt.Sprintf("%s-docker.pkg.dev/%s/%s/%s", region, project, artifactRegistry, serviceName)

	existingEnvVars := make(map[string]struct{})
	// todo(jamesward) actually determine if the service exists instead of assuming it doesn't if we get an error
	existingService, err := getService(project, serviceName, region)
	if err == nil {
		// service exists
		existingEnvVars, err = envVars(project, serviceName, region)
	}

	neededEnvs := needEnvs(appFile.Env, existingEnvVars)

	envs, err := promptOrGenerateEnvs(neededEnvs)
	if err != nil {
		return err
	}

	projectEnv := fmt.Sprintf("GOOGLE_CLOUD_PROJECT=%s", project)
	regionEnv := fmt.Sprintf("GOOGLE_CLOUD_REGION=%s", region)
	serviceEnv := fmt.Sprintf("K_SERVICE=%s", serviceName)
	imageEnv := fmt.Sprintf("IMAGE_URL=%s", image)
	appDirEnv := fmt.Sprintf("APP_DIR=%s", appDir)
	inheritedEnv := os.Environ()

	hookEnvs := append([]string{projectEnv, regionEnv, serviceEnv, imageEnv, appDirEnv}, envs...)
	for key, value := range existingEnvVars {
		hookEnvs = append(hookEnvs, fmt.Sprintf("%s=%s", key, value))
	}
	hookEnvs = append(hookEnvs, inheritedEnv...)

	pushImage := true

	if appFile.Hooks.PreBuild.Commands != nil {
		err = runScripts(appDir, appFile.Hooks.PreBuild.Commands, hookEnvs)
	}

	skipBuild := appFile.Build.Skip != nil && *appFile.Build.Skip == true

	skipDocker := false
	skipJib := false

	builderImage := "gcr.io/buildpacks/builder:v1"

	if appFile.Build.Buildpacks.Builder != "" {
		skipDocker = true
		skipJib = true
		builderImage = appFile.Build.Buildpacks.Builder
	}

	dockerFileExists, _ := dockerFileExists(appDir)
	jibMaven, _ := jibMavenConfigured(appDir)

	if skipBuild {
		fmt.Println(infoPrefix + " Skipping built-in build methods")
	} else {
		end = logProgress(fmt.Sprintf("Building container image %s", highlight(image)),
			fmt.Sprintf("Built container image %s", highlight(image)),
			"Failed to build container image.")

		if !skipDocker && dockerFileExists {
			fmt.Println(infoPrefix + " Attempting to build this application with its Dockerfile...")
			fmt.Println(infoPrefix + " FYI, running the following command:")
			cmdColor.Printf("\tdocker build -t %s %s\n", parameter(image), parameter(appDir))
			err = dockerBuild(appDir, image)
		} else if !skipJib && jibMaven {
			pushImage = false
			fmt.Println(infoPrefix + " Attempting to build this application with Jib Maven plugin...")
			fmt.Println(infoPrefix + " FYI, running the following command:")
			cmdColor.Printf("\tmvn package jib:build -Dimage=%s\n", parameter(image))
			err = jibMavenBuild(appDir, image)
		} else {
			fmt.Println(infoPrefix + " Attempting to build this application with Cloud Native Buildpacks (buildpacks.io)...")
			fmt.Println(infoPrefix + " FYI, running the following command:")
			cmdColor.Printf("\tpack build %s --path %s --builder %s\n", parameter(image), parameter(appDir), parameter(builderImage))
			err = packBuild(appDir, image, builderImage)
		}

		end(err == nil)
		if err != nil {
			return fmt.Errorf("attempted to build and failed: %s", err)
		}
	}

	if appFile.Hooks.PostBuild.Commands != nil {
		err = runScripts(appDir, appFile.Hooks.PostBuild.Commands, hookEnvs)
	}

	if pushImage {
		fmt.Println(infoPrefix + " FYI, running the following command:")
		cmdColor.Printf("\tdocker push %s\n", parameter(image))
		end = logProgress("Pushing container image...",
			"Pushed container image to Google Container Registry.",
			"Failed to push container image to Google Container Registry.")
		err = dockerPush(image)
		end(err == nil)
		if err != nil {
			return fmt.Errorf("failed to push image to %s: %+v", image, err)
		}
	}

	if existingService == nil {
		err = runScripts(appDir, appFile.Hooks.PreCreate.Commands, hookEnvs)
		if err != nil {
			return err
		}
	}

	optionsFlags := optionsToFlags(appFile.Options)

	serviceLabel := highlight(serviceName)
	fmt.Println(infoPrefix + " FYI, running the following command:")
	cmdColor.Printf("\tgcloud run deploy %s", parameter(serviceName))
	cmdColor.Println("\\")
	cmdColor.Printf("\t  --project=%s", parameter(project))
	cmdColor.Println("\\")
	cmdColor.Printf("\t  --platform=%s", parameter("managed"))
	cmdColor.Println("\\")
	cmdColor.Printf("\t  --region=%s", parameter(region))
	cmdColor.Println("\\")
	cmdColor.Printf("\t  --image=%s", parameter(image))
	if appFile.Options.Port > 0 {
		cmdColor.Println("\\")
		cmdColor.Printf("\t  --port=%s", parameter(fmt.Sprintf("%d", appFile.Options.Port)))
	}
	if len(envs) > 0 {
		cmdColor.Println("\\")
		cmdColor.Printf("\t  --update-env-vars=%s", parameter(strings.Join(envs, ",")))
	}

	for _, optionFlag := range optionsFlags {
		cmdColor.Println("\\")
		cmdColor.Printf("\t  %s", optionFlag)
	}

	cmdColor.Println("")

	end = logProgress(fmt.Sprintf("Deploying service %s to Cloud Run...", serviceLabel),
		fmt.Sprintf("Successfully deployed service %s to Cloud Run.", serviceLabel),
		"Failed deploying the application to Cloud Run.")
	url, err := deploy(project, serviceName, image, region, envs, appFile.Options)
	end(err == nil)
	if err != nil {
		return err
	}

	hookEnvs = append(hookEnvs, fmt.Sprintf("SERVICE_URL=%s", url))

	if existingService == nil {
		err = runScripts(appDir, appFile.Hooks.PostCreate.Commands, hookEnvs)
		if err != nil {
			return err
		}
	}

	fmt.Printf("* This application is billed only when it's handling requests.\n")
	fmt.Printf("* Manage this application at Cloud Console:\n\t")
	color.New(color.Underline, color.Bold).Printf("https://console.cloud.google.com/run/detail/%s/%s?project=%s\n", region, serviceName, project)
	fmt.Printf("* Learn more about Cloud Run:\n\t")
	color.New(color.Underline, color.Bold).Println("https://cloud.google.com/run/docs")
	fmt.Printf(successPrefix+" %s%s\n",
		color.New(color.Bold).Sprint("Your application is now live here:\n\t"),
		color.New(color.Bold, color.FgGreen, color.Underline).Sprint(url))
	return nil
}