R/deploy.R

Defines functions cr_deploy_packagetests cr_deploy_pkgdown cr_deploy_r

Documented in cr_deploy_packagetests cr_deploy_pkgdown cr_deploy_r

#' Deploy an R script with an optional schedule
#'
#' Will create a build to run an R script in Cloud Build with an optional schedule from Cloud Scheduler
#'
#' @inheritParams cr_buildstep_r
#' @inheritParams cr_build
#' @inheritParams cr_schedule
#' @inheritParams cr_schedule_http
#' @param r_image The R docker environment executing the R code
#' @param run_name What name the R code will identify itself as.  If \code{NULL} one is autogenerated.
#' @param pre_steps Other \link{cr_buildstep} to run before the R code executes
#' @param post_steps Other \link{cr_buildstep} to run after the R code executes
#' @param ... Other arguments passed through to \link{cr_buildstep_r}
#' @param schedule_type If you have specified a schedule, this will select what strategy it will use to deploy it. See details
#' @param schedule_pubsub If you have a custom pubsub message to send via an existing topic, use \link{cr_schedule_pubsub} to supply it here
#' @inheritDotParams cr_buildstep_r
#' @details
#'
#' The R script will execute within the root directory of whichever \link{Source} you supply, usually created via \link{cr_build_source} representing a Cloud Storage bucket or a GitHub repository that is copied across before code execution.  Bear in mind if the source changes then the code scheduled may need updating.
#'
#' The \code{r_image} dictates what R libraries the R environment executing the code of \code{r} will have, via the underlying Docker container usually supplied by rocker-project.org.  If you want custom R libraries beyond the default, create a docker container with those R libraries installed (perhaps via \link{cr_deploy_docker})
#'
#' @section Scheduling:
#'
#' If \code{schedule=NULL} then the R script will be run immediately on Cloud Build via \link{cr_build}.
#'
#' If \code{schedule} carries a cron job string (e.g. \code{"15 5 * * *"}) then the build will be scheduled via Cloud Scheduler
#'
#' If \code{schedule_type="pubsub"} then you will need \code{googlePubsubR} installed and set-up and scheduling will involve:
#'
#' \enumerate{
#'   \item Creating a PubSub topic called \code{"{run_name}-topic"} or subscribing to the one you provided in \code{schedule_pubsub}.  It is assumed you have created the PubSub topic beforehand if you do supply your own.
#'   \item Create a Build Trigger called \code{"{run_name}-trigger"} that will run when the PubSub topic is called
#'   \item Create a Cloud Schedule called \code{"{run_name}-trigger"} that will send a pubsub message to the topic: either the default that contains just the name of the script, or the message you supplied in \code{schedule_pubsub}.
#'  }
#'
#' Type "pubsub" is recommended for more complex R scripts as you will have more visibility for debugging schedules via inspecting the PubSub topic, build trigger and build logs, as well as enabling triggering the script from other PubSub topics and allowing to pass dynamic parameters into your schedule scripts via the PubSub message.
#'
#' If \code{schedule_type="http"} then scheduling will involve:
#'
#' \enumerate{
#'   \item Create a Cloud Build API call with your build embedded within it via \link{cr_schedule_http}
#'   \item Schedule the HTTP call using the authentication email supplied in \code{email} or the default \link{cr_email_get}
#'  }
#'
#' This is the old default and is suitable for smaller R scripts or when you don't want to use the other  GCP services.  The authentication for the API call from Cloud Scheduler can cause opaque errors as it will give you invalid response codes whether its that or an error in your R script you wish to schedule.
#'
#' @return If scheduling then a \link{Job}, if building immediately then a \link{Build}
#' @family Deployment functions
#' @export
#'
#' @seealso If you want to run R code upon certain events like GitHub pushes, look at \link{cr_buildtrigger}
#'
#' @examples
#'
#' r_lines <- c(
#'   "list.files()",
#'   "library(dplyr)",
#'   "mtcars %>% select(mpg)",
#'   "sessionInfo()"
#' )
#' source <- cr_build_source(RepoSource("googleCloudStorageR",
#'   branchName = "master"
#' ))
#' \dontrun{
#' cr_project_set("my-project")
#' cr_region_set("europe-west1")
#' cr_email_set("123456@projectid.iam.gserviceaccount.com")
#'
#' # check the script runs ok
#' cr_deploy_r(r_lines, source = source)
#'
#' # schedule the script
#' cr_deploy_r(r_lines, schedule = "15 21 * * *", source = source)
#' }
#'
cr_deploy_r <- function(r,
                        schedule = NULL,
                        source = NULL,
                        run_name = NULL,
                        r_image = "rocker/verse",
                        pre_steps = NULL,
                        post_steps = NULL,
                        timeout = 600L,
                        ...,
                        schedule_type = c("pubsub", "http"),
                        schedule_pubsub = NULL,
                        email = cr_email_get(),
                        region = cr_region_get(),
                        projectId = cr_project_get(),
                        serviceAccount = NULL,
                        launch_browser = interactive()) {
  schedule_type <- match.arg(schedule_type)

  if (is.null(run_name)) {
    run_name <- paste0("cr_rscript_", format(Sys.time(), "%Y%m%s%H%M%S"))
  }

  myMessage(paste("Deploy R script", run_name, "to Cloud Build"),
    level = 3
  )

  build <- cr_build_yaml(
    steps = c(
      pre_steps,
      cr_buildstep_r(
        r = r,
        name = r_image,
        id = run_name,
        ...
      ),
      post_steps
    )
  )

  br <- cr_build_make(build,
    source = source,
    timeout = timeout,
    serviceAccount = serviceAccount
  )

  if(!is.null(schedule)){
    # a cloud build you would like to schedule
    myMessage(paste("Scheduling R script on cron schedule:", schedule),
      level = 3
    )

    brs <- cr_schedule_build(
      br,
      schedule = schedule,
      email = email,
      projectId = projectId,
      name = run_name,
      schedule_type = schedule_type,
      region = region,
      description = run_name
    )

    return(brs)
  }

  # build it now
  br1 <- cr_build(br, launch_browser = launch_browser)

  cr_build_wait(br1, projectId = projectId)
}


#' Deploy a cloudbuild.yml for a pkgdown website of an R package
#'
#' This builds a pkgdown website each time the trigger fires and deploys it to git
#'
#' @inheritParams cr_buildstep_pkgdown
#' @inheritParams cr_buildstep_gitsetup
#' @param steps extra steps to run before the pkgdown website steps run
#' @param cloudbuild_file The cloudbuild yaml file to write to
#' @param create_trigger If not "no" then the buildtrigger will be setup for you via \link{cr_buildtrigger}, if "file" will create a buildtrigger pointing at \code{cloudbuild_file}, if "inline" will put the build inline within the trigger (no file created)
#'
#' @details
#'
#' The trigger repository needs to hold an R package configured to build a pkgdown website.
#'
#' For GitHub, the repository will also need to be linked to the project you are building within, via \url{https://console.cloud.google.com/cloud-build/triggers/connect}
#'
#' The git ssh keys need to be deployed to Google Secret Manager for the deployment of the website - see \link{cr_buildstep_git} - this only needs to be done once per Git account.
#'
#' @seealso Create your own custom deployment using \link{cr_buildstep_pkgdown} which this function uses with some defaults.
#' @family Deployment functions
#' @export
#' @examples
#'
#' pd <- cr_deploy_pkgdown("MarkEdmondson1234/googleCloudRunner",
#'   secret = "my_git_secret",
#'   create_trigger = "no"
#' )
#' pd
#' file.exists("cloudbuild-pkgdown.yml")
#' unlink("cloudbuild-pkgdown.yml")
#' \dontrun{
#' cr_deploy_pkgdown("MarkEdmondson1234/googleCloudRunner",
#'   secret = "my_git_secret",
#'   create_trigger = "inline"
#' )
#' }
#'
cr_deploy_pkgdown <- function(github_repo,
                              secret,
                              steps = NULL,
                              create_trigger = c("file", "inline", "no"),
                              cloudbuild_file = "cloudbuild-pkgdown.yml",
                              git_email = "googlecloudrunner@r.com",
                              env = NULL,
                              build_image = "gcr.io/gcer-public/packagetools:latest",
                              post_setup = NULL,
                              post_clone = NULL) {
  create_trigger <- match.arg(create_trigger)

  build_yaml <-
    cr_build_yaml(steps = c(
      steps,
      cr_buildstep_pkgdown(github_repo,
        git_email = git_email,
        secret = secret,
        env = env,
        build_image = build_image,
        post_setup = post_setup,
        post_clone = post_clone
      )
    ))

  if (create_trigger == "no") {
    cr_build_write(build_yaml, file = cloudbuild_file)
    usethis::ui_line()
    usethis::ui_info("Complete deployment of pkgdown Cloud Build yaml:")
    usethis::ui_todo(c(
      "Go to https://console.cloud.google.com/cloud-build/triggers and
            make a build trigger pointing at this file in your repo:
            {cloudbuild_file} "
    ))

    usethis::ui_info(c("Ignored files filter (glob): docs/**, inst/**, tests/**"))

    return(invisible(build_yaml))
  }

  myMessage("#Creating pkgdown build trigger for", github_repo, level = 3)

  if (create_trigger == "file") {
    cr_build_write(build_yaml, file = cloudbuild_file)
    the_build <- cloudbuild_file
  } else if (create_trigger == "inline") {
    the_build <- cr_build_make(build_yaml)
  }

  trig <- cr_buildtrigger_repo(github_repo, branch = "^master$")

  cr_buildtrigger(
    the_build,
    name = paste0("cr-deploy-pkgdown-", format(Sys.Date(), "%Y%m%d")),
    trigger = trig,
    description = "Build pkgdown website on master branch",
    ignoredFiles = c(
      "docs/**",
      "inst/**",
      "tests/**"
    )
  )
}

#' Deploy a cloudbuild.yml for R package tests and upload to Codecov
#'
#' This tests an R package each time you commit, and uploads the test coverage results to Codecov
#'
#' @inheritParams cr_buildstep_packagetests
#' @param steps extra steps to run before the \link{cr_buildstep_packagetests} steps run (such as decryption of auth files)
#' @param cloudbuild_file The cloudbuild yaml file to write to.  See create_trigger
#' @param ... Other arguments passed to \link{cr_build_make}
#' @inheritDotParams cr_build_make
#' @param create_trigger If creating a trigger, whether to create it from the cloudbuild_file or inline
#' @param trigger_repo If not NULL, a \link{cr_buildtrigger_repo} where a buildtrigger will be created via \link{cr_buildtrigger}
#'
#' @details
#'
#' The trigger repository needs to hold an R package configured to do tests upon.
#'
#' For GitHub, the repository will need to be linked to the project you are building within, via \url{https://console.cloud.google.com/cloud-build/triggers/connect}
#'
#' If your tests need authentication details, add these via \link{cr_buildstep_secret} to the \code{steps} argument, which will prepend decrypting the authentication file before running the tests.
#'
#' If you want codecov to ignore some files then also deploy a .covrignore file to your repository - see covr website at \url{https://covr.r-lib.org/} for details.
#'
#' @seealso Create your own custom deployment using \link{cr_buildstep_packagetests} which this function uses with some defaults
#' @family Deployment functions
#' @export
#' @seealso \link{cr_buildstep_packagetests}
#' @examples
#'
#' # create a local cloudbuild.yml file for packagetests
#' pd <- cr_deploy_packagetests(create_trigger = "no")
#' pd
#'
#' # add a decryption step for an auth file
#' cr_deploy_packagetests(
#'   steps = cr_buildstep_secret("my_secret", "auth.json"),
#'   env = c("NOT_CRAN=true", "MY_AUTH_FILE=auth.json"),
#'   timeout = 1200,
#'   create_trigger = "no"
#' )
#'
#'
#' # creating a buildtrigger repo for trigger_repo
#' repo <- cr_buildtrigger_repo("MarkEdmondson1234/googleCloudRunner",
#'   branch = "master"
#' )
#' \dontrun{
#'
#' # will create the file in the repo, and point a buildtrigger at it
#' cr_deploy_packagetests(create_trigger = "file", trigger_repo = repo)
#'
#'
#' # will make an inline build within a buildtrigger
#' cr_deploy_packagetests(create_trigger = "inline", trigger_repo = repo)
#' }
#'
#' unlink("cloudbuild-tests.yml")
cr_deploy_packagetests <- function(steps = NULL,
                                   cloudbuild_file = "cloudbuild-tests.yml",
                                   env = c("NOT_CRAN=true"),
                                   test_script = NULL,
                                   codecov_script = NULL,
                                   codecov_token = "$_CODECOV_TOKEN",
                                   build_image = "gcr.io/gcer-public/packagetools:latest",
                                   create_trigger = c("file", "inline", "no"),
                                   trigger_repo = NULL,
                                   ...) {
  create_trigger <- match.arg(create_trigger)

  build_yaml <-
    cr_build_yaml(
      steps = c(
        steps,
        cr_buildstep_packagetests(
          test_script = test_script,
          codecov_script = codecov_script,
          codecov_token = codecov_token,
          build_image = build_image,
          env = env
        )
      ),
      ...
    )

  if (create_trigger == "no") {
    cr_build_write(build_yaml, file = cloudbuild_file)

    usethis::ui_line()
    usethis::ui_info("Complete deployment of tests Cloud Build yaml:")
    usethis::ui_todo(c(
      "Go to https://console.cloud.google.com/cloud-build/triggers and
            make a build trigger pointing at this file in your repo:
            {cloudbuild_file} "
    ))
    usethis::ui_info(c(
      "Build Trigger substitution variable settings:",
      "_CODECOV_TOKEN = your-codecov-token",
      "Ignored files filter (glob): docs/** and vignettes/**"
    ))

    return(build_yaml)
  }

  # creating a buildtrigger
  myMessage("#Creating tests build trigger", level = 3)
  assert_that(is.buildtrigger_repo(trigger_repo))

  if (create_trigger == "file") {
    cr_build_write(build_yaml, file = cloudbuild_file)
    the_build <- cloudbuild_file
  } else if (create_trigger == "inline") {
    the_build <- cr_build_make(yaml = build_yaml)
  }

  if (is.null(codecov_token) || codecov_token == "$_CODECOV_TOKEN") {
    myMessage("If you want to use Code Covr, add the Code Covr token in a substitution varaible in the Build Trigger", level = 3)
    subs <- NULL
  } else {
    assert_that(is.string(codecov_token))
    subs <- list(`_CODECOV_TOKEN` = codecov_token)
  }

  cr_buildtrigger(the_build,
    name = paste0(
      "cr-deploy-tests-",
      format(Sys.Date(), "%Y%m%d")
    ),
    trigger = trigger_repo,
    description = "Tests for package",
    substitutions = subs,
    ignoredFiles = c(
      "docs/**",
      "vignettes/**"
    )
  )
}

Try the googleCloudRunner package in your browser

Any scripts or data that you put into this service are public.

googleCloudRunner documentation built on March 18, 2022, 8 p.m.