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
}