├── .github ├── .gitignore ├── dependabot.yml ├── issue_template.md ├── pull_request_template.md ├── CONTRIBUTING.md └── workflows │ └── tic.yml ├── vignettes ├── .gitignore ├── tic.Rmd └── circle.Rmd ├── .gitattributes ├── tests ├── testthat.R ├── testthat │ ├── test-auth.R │ ├── helper-circle.R │ ├── test-helpers-github.R │ ├── test-checkout-keys.R │ ├── test-env-vars.R │ ├── test-1-builds.R │ ├── test-general.R │ └── test-2-print.R └── fixtures │ ├── set_env_var().yml │ ├── delete_env_var().yml │ ├── enable_repo().yml │ ├── get_circle_user().yml │ ├── get_env_vars().yml │ ├── create_checkout_key().yml │ ├── has_checkout_key().yml │ ├── new_build().yml │ ├── delete_checkout_key().yml │ ├── s3-print-circle_pipeline.yml │ └── list_projects().yml ├── man-roxygen ├── quiet.R ├── remote.R ├── repo.R ├── user.R ├── vcs.R ├── api_version.R └── token.R ├── man ├── figures │ └── user-key.png ├── edit_circle_config.Rd ├── get_circle_user.Rd ├── github_info.Rd ├── list_projects.Rd ├── enable_repo.Rd ├── new_build.Rd ├── get_build_artifacts.Rd ├── browse_circle_token.Rd ├── use_circle_deploy.Rd ├── circle.Rd ├── env_var.Rd ├── circle-package.Rd ├── builds.Rd └── checkout_key.Rd ├── pkgdown ├── pkgdown │ └── favicon │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ └── apple-touch-icon-180x180.png └── _pkgdown.yml ├── tic.R ├── codecov.yml ├── .Rbuildignore ├── .lintr ├── circle.Rproj ├── .pre-commit-config.yaml ├── NAMESPACE ├── cran-comments.md ├── R ├── circle-package.R ├── helpers-github.R ├── use-circle-deploy.R ├── circle.R ├── auth.R ├── env-var.R ├── format-builds-helpers.R ├── builds.R ├── general.R └── checkout-key.R ├── DESCRIPTION ├── .gitignore ├── NEWS.md ├── README.md ├── .circleci └── config.yml └── codemeta.json /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | tests/fixtures/**/* -diff 3 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(circle) 3 | 4 | test_package("circle") 5 | -------------------------------------------------------------------------------- /man-roxygen/quiet.R: -------------------------------------------------------------------------------- 1 | #' @param quiet `[logical]`\cr 2 | #' If `TRUE`, console output is suppressed. 3 | -------------------------------------------------------------------------------- /man/figures/user-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/man/figures/user-key.png -------------------------------------------------------------------------------- /man-roxygen/remote.R: -------------------------------------------------------------------------------- 1 | #' @param remote `[character]`\cr 2 | #' The Github remote which should be used. 3 | -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /man-roxygen/repo.R: -------------------------------------------------------------------------------- 1 | #' @param repo `[character]`\cr 2 | #' The repository slug to use. Must follow the "`user/repo`" structure. 3 | -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /man-roxygen/user.R: -------------------------------------------------------------------------------- 1 | #' @param user `[character]`\cr 2 | #' The username for the repository. By default queried using `get_user()`. 3 | -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci/circle/HEAD/pkgdown/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /man-roxygen/vcs.R: -------------------------------------------------------------------------------- 1 | #' @param vcs_type `[character]`\cr The version control system to use. 2 | #' Defaults to "gh" (Github). 3 | # Other possible values are "bitbucket". 4 | -------------------------------------------------------------------------------- /man-roxygen/api_version.R: -------------------------------------------------------------------------------- 1 | #' @param api_version `[character]`\cr 2 | #' A character string specifying the Circle CI API version. 3 | #' This usually does not need to be changed by the user. 4 | -------------------------------------------------------------------------------- /tic.R: -------------------------------------------------------------------------------- 1 | do_package_checks(codecov = FALSE) 2 | 3 | if (ci_on_circle()) { 4 | get_stage("before_deploy") %>% 5 | add_step(step_install_github("ropensci/rotemplate")) 6 | do_pkgdown(orphan = TRUE) 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | # Keep dependencies for GitHub Actions up-to-date 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "monthly" 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 1% 13 | -------------------------------------------------------------------------------- /man-roxygen/token.R: -------------------------------------------------------------------------------- 1 | #' @param .token `[character]`\cr 2 | #' Authentication token. Defaults to GITHUB_PAT or GITHUB_TOKEN environment 3 | #' variables, in this order if any is set. See gh_token() if you need more 4 | #' flexibility, e.g. different tokens for different GitHub Enterprise 5 | #' deployments. 6 | -------------------------------------------------------------------------------- /man/edit_circle_config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/auth.R 3 | \name{edit_circle_config} 4 | \alias{edit_circle_config} 5 | \title{Open circle Configuration file} 6 | \usage{ 7 | edit_circle_config() 8 | } 9 | \value{ 10 | No return value, called for side effects. 11 | } 12 | \description{ 13 | Opens \verb{~/.circleci/cli.yml}. 14 | } 15 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | README.Rmd 3 | drat.sh 4 | knitreadme.sh 5 | ^.*\.Rproj$ 6 | ^\.Rproj\.user$ 7 | ^\.httr-oauth$ 8 | ^LICENSE\.md$ 9 | ^codecov\.yml$ 10 | ^man-roxygen$ 11 | ^\.pre-commit-config\.yaml$ 12 | ^\.travis\.yml$ 13 | ^appveyor\.yml$ 14 | ^tic\.R$ 15 | ^\.circleci$ 16 | ^\.lintr$ 17 | ^codemeta\.json$ 18 | ^\.github$ 19 | ^pkgdown$ 20 | ^\.ccache$ 21 | ^docs$ 22 | ^\.vscode$ 23 | ^debug\.R$ 24 | ^CRAN-RELEASE$ 25 | ^cran-comments\.md$ 26 | .envrc -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: with_defaults( 2 | # lintr defaults: https://github.com/jimhester/lintr#available-linters 3 | # the following setup changes/removes certain linters 4 | object_name_linter = object_name_linter(c("snake_case", "CamelCase")), # only allow snake case and camel case object names 5 | cyclocomp_linter = NULL, # do not check function complexity 6 | commented_code_linter = NULL, # allow code in comments 7 | line_length_linter = line_length_linter(100) 8 | ) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-auth.R: -------------------------------------------------------------------------------- 1 | vcr::use_cassette("enable_repo()", record = "new_episodes", { 2 | test_that("enable_repo() works", { 3 | skip_on_cran() 4 | 5 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 6 | # fail to lookup the git repo when running code coverage 7 | foo <- suppressMessages(enable_repo( 8 | repo = Sys.getenv("CIRCLE_REPO"), 9 | user = Sys.getenv("CIRCLE_OWNER") 10 | )) 11 | 12 | expect_s3_class(foo, "circle_api") 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /circle.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageCheckArgs: --no-tests 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /tests/testthat/helper-circle.R: -------------------------------------------------------------------------------- 1 | library("vcr") 2 | invisible(vcr::vcr_configure( 3 | filter_sensitive_data = list( 4 | "<<>>" = Sys.getenv("R_CIRCLE"), 5 | "<<>>" = Sys.getenv("CIRCLE_R_PACKAGE_GITHUB_PAT") 6 | ), 7 | dir = "../fixtures", 8 | log = FALSE, 9 | log_opts = list(file = "console") 10 | )) 11 | vcr::check_cassette_names() 12 | 13 | # set repo with access right on Circle CI here for local testing 14 | Sys.setenv("CIRCLE_REPO" = "circle") 15 | Sys.setenv("CIRCLE_OWNER" = "ropensci") 16 | -------------------------------------------------------------------------------- /man/get_circle_user.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/general.R 3 | \name{get_circle_user} 4 | \alias{get_circle_user} 5 | \title{Get Circle CI user} 6 | \usage{ 7 | get_circle_user() 8 | } 9 | \value{ 10 | A named vector of class \code{circle_user} containing information about 11 | about the authenticated user: 12 | \itemize{ 13 | \item Full name 14 | \item Username 15 | \item ID 16 | \item API endpoint 17 | } 18 | } 19 | \description{ 20 | Retrieve details about the authenticated Circle CI user. 21 | } 22 | \examples{ 23 | \dontrun{ 24 | get_circle_user() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
Session Info 6 | 7 | ```r 8 | 9 | ``` 10 |
11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/lorenzwalthert/precommit 3 | rev: v0.3.2.9001 4 | hooks: 5 | - id: style-files 6 | args: [--style_fun=tidyverse_style] 7 | - id: parsable-R 8 | - id: lintr 9 | args: [--warn_only] 10 | - id: no-browser-statement 11 | - id: readme-rmd-rendered 12 | - id: roxygenize 13 | - id: codemeta-description-updated 14 | - id: use-tidy-description 15 | - repo: https://github.com/lorenzwalthert/precommit-markdown-link-check 16 | rev: v0.0.0.9002 # Use the sha / tag you want to point at 17 | hooks: 18 | - id: markdown-link-check 19 | -------------------------------------------------------------------------------- /pkgdown/_pkgdown.yml: -------------------------------------------------------------------------------- 1 | template: 2 | package: rotemplate 3 | 4 | authors: 5 | Patrick Schratz: 6 | href: https://pat-s.me 7 | 8 | reference: 9 | - title: Authentication 10 | contents: 11 | - get_circle_user 12 | - enable_repo 13 | - browse_circle_token 14 | - edit_circle_config 15 | - title: Builds & Jobs 16 | contents: 17 | - circle 18 | - get_pipelines 19 | - get_jobs 20 | - contains("workflow") 21 | - contains("build") 22 | - title: Manage Environment Variables 23 | contents: 24 | - get_env_vars 25 | - set_env_var 26 | - title: Deployment 27 | contents: 28 | - use_circle_deploy 29 | - title: Manage API Keys 30 | contents: 31 | - contains("key") 32 | - title: Miscellaneous 33 | contents: 34 | - circle-package 35 | - list_projects 36 | -------------------------------------------------------------------------------- /tests/testthat/test-helpers-github.R: -------------------------------------------------------------------------------- 1 | test_that("github helper functions work", { 2 | unlink(paste0(tempdir(), "/circle"), recursive = TRUE) 3 | gert::git_clone( 4 | sprintf( 5 | "https://github.com/%s/%s.git", 6 | Sys.getenv("CIRCLE_OWNER"), 7 | Sys.getenv("CIRCLE_REPO") 8 | ), 9 | paste0(tempdir(), sprintf("/%s", Sys.getenv("CIRCLE_REPO"))) 10 | ) 11 | 12 | withr::with_dir(paste0(tempdir(), sprintf("/%s", Sys.getenv("CIRCLE_REPO"))), { 13 | # github_info() ------------------------------------------------------------ 14 | if (Sys.getenv("CI") != "") { 15 | info <- github_info(.token = Sys.getenv("CIRCLE_R_PACKAGE_GITHUB_PAT")) 16 | } else { 17 | info <- github_info() 18 | } 19 | expect_s3_class(info, "gh_response") 20 | expect_equal(info$name, "circle") 21 | expect_equal(info$owner$login, "ropensci") 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(circle::format,circle_collection) 4 | S3method(circle::format,circle_job) 5 | S3method(circle::format,circle_workflow) 6 | S3method(print,circle_collection) 7 | S3method(print,circle_job) 8 | S3method(print,circle_workflow) 9 | export(browse_circle_token) 10 | export(circle) 11 | export(create_checkout_key) 12 | export(delete_checkout_key) 13 | export(delete_env_var) 14 | export(edit_circle_config) 15 | export(enable_repo) 16 | export(get_build_artifacts) 17 | export(get_checkout_keys) 18 | export(get_circle_user) 19 | export(get_env_vars) 20 | export(get_jobs) 21 | export(get_pipelines) 22 | export(get_workflows) 23 | export(has_checkout_key) 24 | export(list_projects) 25 | export(new_build) 26 | export(retry_workflow) 27 | export(set_env_var) 28 | export(use_circle_deploy) 29 | import(cli) 30 | import(httr) 31 | importFrom(cli,cli_text) 32 | importFrom(gh,gh) 33 | importFrom(jsonlite,fromJSON) 34 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | circle 0.7.3 2 | 3 | ## Cran Repository Policy 4 | 5 | - [x] Reviewed CRP last edited 2024-04-04. 6 | 7 | See changes at https://github.com/eddelbuettel/crp/compare/master@%7B2022-07-26%7D...master@%7B2024-04-04%7D 8 | 9 | ## R CMD check results 10 | 11 | - [x] Checked locally, R 4.4.1 12 | - [x] Checked on CI system, R 4.4.1 13 | - [x] Checked on win-builder, R devel 14 | 15 | Check the boxes above after successful execution and remove this line. Then run `fledge::release()`. 16 | 17 | ## Current CRAN check results 18 | 19 | - [x] Checked on 2024-07-31, problems found: https://cran.r-project.org/web/checks/check_results_circle.html 20 | - [x] WARN: r-devel-linux-x86_64-debian-clang, r-devel-linux-x86_64-debian-gcc, r-devel-linux-x86_64-fedora-clang, r-devel-linux-x86_64-fedora-gcc, r-devel-windows-x86_64 21 | Missing or unexported object: ‘usethis::github_token’ 22 | 23 | Check results at: https://cran.r-project.org/web/checks/check_results_circle.html 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ## Description 8 | 9 | 10 | ## Related Issue 11 | 14 | 15 | ## Example 16 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /man/github_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers-github.R 3 | \name{github_info} 4 | \alias{github_info} 5 | \title{Github information} 6 | \usage{ 7 | github_info(path = ".", remote = "origin", .token = NULL) 8 | } 9 | \arguments{ 10 | \item{path}{\verb{[string]}\cr 11 | The path to a GitHub-enabled Git repository (or a subdirectory thereof).} 12 | 13 | \item{remote}{\verb{[character]}\cr 14 | The Github remote which should be used.} 15 | 16 | \item{.token}{\verb{[character]}\cr 17 | Authentication token. Defaults to GITHUB_PAT or GITHUB_TOKEN environment 18 | variables, in this order if any is set. See gh_token() if you need more 19 | flexibility, e.g. different tokens for different GitHub Enterprise 20 | deployments.} 21 | } 22 | \value{ 23 | Object of class \code{gh_response} (list type) with information about the 24 | queried repository. 25 | } 26 | \description{ 27 | Retrieves metadata about a Git repository from GitHub. 28 | 29 | \code{github_info()} returns a list as obtained from the GET "/repos/:repo" API. 30 | } 31 | \keyword{internal} 32 | -------------------------------------------------------------------------------- /tests/testthat/test-checkout-keys.R: -------------------------------------------------------------------------------- 1 | vcr::use_cassette("create_checkout_key()", { 2 | test_that("create_checkout_key() works", { 3 | skip_on_cran() 4 | 5 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 6 | # fail to lookup the git repo when running code coverage 7 | 8 | expect_s3_class( 9 | create_checkout_key( 10 | type = "user-key", 11 | repo = Sys.getenv("CIRCLE_REPO"), 12 | user = Sys.getenv("CIRCLE_OWNER") 13 | ), 14 | "circle_api" 15 | ) 16 | }) 17 | }) 18 | 19 | vcr::use_cassette("delete_checkout_key()", { 20 | test_that("delete_checkout_key() works", { 21 | skip_on_cran() 22 | 23 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 24 | # fail to lookup the git repo when running code coverage 25 | 26 | keys <- get_checkout_keys( 27 | repo = Sys.getenv("CIRCLE_REPO"), 28 | user = Sys.getenv("CIRCLE_OWNER") 29 | ) 30 | expect_s3_class(keys, "circle_api") 31 | 32 | fp <- content(keys$response)$items[[1]]$fingerprint 33 | 34 | expect_s3_class( 35 | delete_checkout_key( 36 | fingerprint = fp, 37 | repo = "circle", user = "ropensci" 38 | ), 39 | "circle_api" 40 | ) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /man/list_projects.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/general.R 3 | \name{list_projects} 4 | \alias{list_projects} 5 | \title{List Circle CI Projects} 6 | \usage{ 7 | list_projects(repo = github_info()$name, user = github_info()$owner$login) 8 | } 9 | \arguments{ 10 | \item{repo}{\verb{[character]}\cr 11 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 12 | 13 | \item{user}{\verb{[character]}\cr 14 | The username for the repository. By default queried using \code{get_user()}.} 15 | } 16 | \value{ 17 | An object of class \code{circle_api} with the following elements 18 | \itemize{ 19 | \item \code{content} (queried content) 20 | \item \code{path} (API request) 21 | \item \code{response} (HTTP response information) 22 | } 23 | } 24 | \description{ 25 | Retrieve a list of Circle CI repositories for the authenticated 26 | user. 27 | } 28 | \details{ 29 | Retrieves a very detailed list of repository and repo-related 30 | information for all Circle CI repository attached to the current user. 31 | 32 | This endpoint uses API v1.1 and will probably be removed in the near 33 | future. 34 | } 35 | \examples{ 36 | \dontrun{ 37 | list_projects() 38 | } 39 | } 40 | \seealso{ 41 | \code{\link[=get_pipelines]{get_pipelines()}}, \code{\link[=get_workflows]{get_workflows()}}, \code{\link[=get_jobs]{get_jobs()}} 42 | } 43 | -------------------------------------------------------------------------------- /man/enable_repo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/general.R 3 | \name{enable_repo} 4 | \alias{enable_repo} 5 | \title{Enable a repo on Circle CI} 6 | \usage{ 7 | enable_repo( 8 | repo = github_info()$name, 9 | user = github_info()$owner$login, 10 | vcs_type = "gh", 11 | api_version = "v1.1", 12 | quiet = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{repo}{\verb{[character]}\cr 17 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 18 | 19 | \item{user}{\verb{[character]}\cr 20 | The username for the repository. By default queried using \code{get_user()}.} 21 | 22 | \item{vcs_type}{\verb{[character]}\cr The version control system to use. 23 | Defaults to "gh" (Github).} 24 | 25 | \item{api_version}{\verb{[character]}\cr 26 | A character string specifying the Circle CI API version. 27 | This usually does not need to be changed by the user.} 28 | 29 | \item{quiet}{\verb{[logical]}\cr 30 | If \code{TRUE}, console output is suppressed.} 31 | } 32 | \value{ 33 | An object of class \code{circle_api} with the following elements 34 | \itemize{ 35 | \item \code{content} (queried content) 36 | \item \code{path} (API request) 37 | \item \code{response} (HTTP response information) 38 | } 39 | } 40 | \description{ 41 | "Follows" a repo on Circle CI so that builds can be triggered. 42 | } 43 | \examples{ 44 | \dontrun{ 45 | enable_repo() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /R/circle-package.R: -------------------------------------------------------------------------------- 1 | #' @title Circle CI API Client 2 | #' @description This package provides functionality for interacting with the 3 | #' Circle CI API. [Circle CI](https://circleci.com) is a continuous 4 | #' integration provider which allows for 5 | #' automated testing of software each time that software is publicly committed 6 | #' to a repository on GitHub. 7 | #' 8 | #' This package interacts with the Circle CI REST API and allows to execute 9 | #' tasks in R without visiting the the website. This includes monitoring 10 | #' builds, modifying build environment settings and environment variables, and 11 | #' cancelling or restarting builds. 12 | #' 13 | #' Use of this package requires a Circle API key. Unless a key is already set, 14 | #' users will be guided through the creation of a key, 15 | #' API keys are disposable, but should still be treated securely. 16 | #' 17 | #' The following functions simplify integrating R package testing and 18 | #' deployment with GitHub and Circle CI: 19 | #' - [enable_repo()] enables Circle CI for your repository, 20 | #' - [use_circle_deploy()] installs a public deploy key on GitHub and the 21 | #' corresponding private key on Circle CI to simplify deployments to GitHub 22 | #' from Circle CI. 23 | #' @examples 24 | #' \dontrun{ 25 | #' # check to see if you've authenticated correctly 26 | #' get_circle_user() 27 | #' } 28 | #' 29 | #' @docType package 30 | #' @name circle-package 31 | "_PACKAGE" 32 | -------------------------------------------------------------------------------- /man/new_build.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/general.R 3 | \name{new_build} 4 | \alias{new_build} 5 | \title{Trigger a New Build on Circle CI} 6 | \usage{ 7 | new_build( 8 | repo = github_info()$name, 9 | user = github_info()$owner$login, 10 | vcs_type = "gh", 11 | branch = "master", 12 | quiet = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{repo}{\verb{[character]}\cr 17 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 18 | 19 | \item{user}{\verb{[character]}\cr 20 | The username for the repository. By default queried using \code{get_user()}.} 21 | 22 | \item{vcs_type}{\verb{[character]}\cr The version control system to use. 23 | Defaults to "gh" (Github).} 24 | 25 | \item{branch}{A character string specifying the repository branch.} 26 | 27 | \item{quiet}{\verb{[logical]}\cr 28 | If \code{TRUE}, console output is suppressed.} 29 | } 30 | \value{ 31 | An object of class \code{circle_api} with the following elements 32 | \itemize{ 33 | \item \code{content} (queried content) 34 | \item \code{path} (API request) 35 | \item \code{response} (HTTP response information) 36 | } 37 | } 38 | \description{ 39 | Triggers a new build for a specific repo branch. 40 | } 41 | \details{ 42 | Trigger a new Circle CI build for a specific repo branch. 43 | } 44 | \examples{ 45 | \dontrun{ 46 | new_build() 47 | } 48 | } 49 | \seealso{ 50 | \code{\link[=retry_workflow]{retry_workflow()}} 51 | } 52 | -------------------------------------------------------------------------------- /man/get_build_artifacts.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/general.R 3 | \name{get_build_artifacts} 4 | \alias{get_build_artifacts} 5 | \title{Get Build Artifacts of a Specific Job} 6 | \usage{ 7 | get_build_artifacts( 8 | job_id = NULL, 9 | repo = github_info()$name, 10 | user = github_info()$owner$login, 11 | vcs_type = "gh", 12 | api_version = "v2" 13 | ) 14 | } 15 | \arguments{ 16 | \item{job_id}{\verb{[character]}\cr 17 | A Circle CI job id.} 18 | 19 | \item{repo}{\verb{[character]}\cr 20 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 21 | 22 | \item{user}{\verb{[character]}\cr 23 | The username for the repository. By default queried using \code{get_user()}.} 24 | 25 | \item{vcs_type}{\verb{[character]}\cr The version control system to use. 26 | Defaults to "gh" (Github).} 27 | 28 | \item{api_version}{\verb{[character]}\cr 29 | A character string specifying the Circle CI API version. 30 | This usually does not need to be changed by the user.} 31 | } 32 | \value{ 33 | An object of class \code{circle_api} with the following elements 34 | \itemize{ 35 | \item \code{content} (queried content) 36 | \item \code{path} (API request) 37 | \item \code{response} (HTTP response information) 38 | } 39 | } 40 | \description{ 41 | Retrieve artifacts from a specific build. 42 | } 43 | \examples{ 44 | \dontrun{ 45 | job_id <- get_jobs()[[1]]$id 46 | get_build_artifacts(job_id) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/testthat/test-env-vars.R: -------------------------------------------------------------------------------- 1 | vcr::use_cassette("set_env_var()", { 2 | test_that("set_env_var() works", { 3 | skip_on_cran() 4 | 5 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 6 | # fail to lookup the git repo when running code coverage 7 | 8 | out <- set_env_var( 9 | repo = Sys.getenv("CIRCLE_REPO"), 10 | user = Sys.getenv("CIRCLE_OWNER"), 11 | list(foo = "test"), 12 | quiet = TRUE 13 | ) 14 | expect_s3_class(out, "circle_api") 15 | }) 16 | }) 17 | 18 | vcr::use_cassette("get_env_vars()", { 19 | test_that("get_env_vars() works", { 20 | skip_on_cran() 21 | 22 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 23 | # fail to lookup the git repo when running code coverage 24 | 25 | expect_silent( 26 | get_env_vars( 27 | repo = Sys.getenv("CIRCLE_REPO"), 28 | user = Sys.getenv("CIRCLE_OWNER") 29 | ) 30 | ) 31 | }) 32 | }) 33 | 34 | vcr::use_cassette("delete_env_var()", { 35 | test_that("delete_env_var() works", { 36 | skip_on_cran() 37 | 38 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 39 | # fail to lookup the git repo when running code coverage 40 | 41 | expect_silent( 42 | delete_env_var( 43 | repo = Sys.getenv("CIRCLE_REPO"), 44 | user = Sys.getenv("CIRCLE_OWNER"), 45 | var = "foo", quiet = TRUE 46 | ) 47 | ) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: circle 2 | Title: R Client Package for Circle CI 3 | Version: 0.7.3 4 | Authors@R: c( 5 | person("Patrick", "Schratz", , "patrick.schratz@gmail.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0003-0748-6624")), 7 | person("Max", "Joseph", role = "rev", 8 | comment = "Max reviewed the package for ropensci, see "), 9 | person("Sharla", "Gelfand", role = "rev", 10 | comment = "Sharla reviewed the package for ropensci, see ") 11 | ) 12 | Description: Tools for interacting with the 'Circle CI' API 13 | (). Besides executing common tasks 14 | such as querying build logs and restarting builds, this package also 15 | helps setting up permissions to deploy from builds. 16 | License: GPL-3 17 | URL: https://docs.ropensci.org/circle/, https://github.com/ropensci/circle 18 | BugReports: https://github.com/ropensci/circle/issues 19 | Imports: 20 | cli (>= 2.0.0), 21 | gh, 22 | httr, 23 | jsonlite 24 | Suggests: 25 | clipr, 26 | covr, 27 | gert, 28 | knitr, 29 | openssl, 30 | purrr, 31 | rmarkdown, 32 | testthat (>= 3.0.0), 33 | usethis (>= 2.0.0), 34 | utils, 35 | vcr, 36 | withr 37 | VignetteBuilder: 38 | knitr 39 | Config/testthat/edition: 3 40 | Config/testthat/parallel: true 41 | Encoding: UTF-8 42 | Roxygen: list(markdown = TRUE) 43 | RoxygenNote: 7.3.2 44 | -------------------------------------------------------------------------------- /man/browse_circle_token.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/auth.R 3 | \name{browse_circle_token} 4 | \alias{browse_circle_token} 5 | \title{Authenticate to Circle CI} 6 | \usage{ 7 | browse_circle_token() 8 | } 9 | \value{ 10 | Returns \code{TRUE} (invisibly). 11 | } 12 | \description{ 13 | A Circle CI API token is needed to interact with the Circle CI API. 14 | \code{browse_circle_token()} opens a browser window for the respective Circle CI 15 | endpoint to retrieve the key. 16 | } 17 | \section{Store API token}{ 18 | 19 | 20 | \code{circle} supports two ways of storing the Circle API tokens: 21 | \itemize{ 22 | \item via env vars \code{R_CIRCLE} 23 | \item via \verb{~/.circleci/cli.yml} 24 | } 25 | 26 | The latter should already be present if you already used the \code{circle} CLI 27 | tool at some point in the past. If not, its up to your preference which 28 | approach to use. 29 | 30 | The following instructions should help to set up \verb{~/.circleci/cli.yml} 31 | correctly: 32 | \enumerate{ 33 | \item Copy the token from the browser after having called 34 | \code{browse_circle_token()}. You can use 35 | \code{edit_circle_config()} to open \verb{~/.circleci/cli.yml}. 36 | \item The token should be stored using the following structure 37 | 38 | \if{html}{\out{
}}\preformatted{host: https://circleci.com 39 | endpoint: graphql-unstable 40 | token: 41 | }\if{html}{\out{
}} 42 | } 43 | } 44 | 45 | \examples{ 46 | \dontrun{ 47 | browse_circle_token() 48 | 49 | edit_circle_config() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/testthat/test-1-builds.R: -------------------------------------------------------------------------------- 1 | vcr::use_cassette("get_jobs()", { 2 | test_that("enable_repo() works", { 3 | skip_on_cran() 4 | 5 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 6 | # fail to lookup the git repo when running code coverage 7 | 8 | # calls `get_workflows()` and `get_pipelines()` internally 9 | out <- suppressMessages( 10 | get_jobs( 11 | repo = Sys.getenv("CIRCLE_REPO"), 12 | user = Sys.getenv("CIRCLE_OWNER") 13 | ) 14 | ) 15 | 16 | expect_s3_class(out, "circle_collection") 17 | }) 18 | }) 19 | 20 | vcr::use_cassette("get_build_artifacts()", { 21 | test_that("get_build_artifacts() works", { 22 | skip_on_cran() 23 | 24 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 25 | # fail to lookup the git repo when running code coverage 26 | expect_s3_class( 27 | get_build_artifacts( 28 | repo = Sys.getenv("CIRCLE_REPO"), 29 | user = Sys.getenv("CIRCLE_OWNER") 30 | ), 31 | "circle_api" 32 | ) 33 | }) 34 | }) 35 | 36 | vcr::use_cassette("retry_workflow()", { 37 | test_that("retry_workflow() works", { 38 | skip_on_cran() 39 | 40 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 41 | # fail to lookup the git repo when running code coverage 42 | workflow_id <- suppressMessages( 43 | get_workflows( 44 | repo = Sys.getenv("CIRCLE_REPO"), 45 | user = Sys.getenv("CIRCLE_OWNER") 46 | )[[10]]$id 47 | ) 48 | expect_s3_class( 49 | retry_workflow(workflow_id), 50 | "circle_api" 51 | ) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /man/use_circle_deploy.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/use-circle-deploy.R 3 | \name{use_circle_deploy} 4 | \alias{use_circle_deploy} 5 | \title{Set Up Build Deployment Between Circle CI And Github} 6 | \usage{ 7 | use_circle_deploy( 8 | repo = github_info()$name, 9 | user = github_info()$owner$login, 10 | quiet = FALSE 11 | ) 12 | } 13 | \arguments{ 14 | \item{repo}{\verb{[character]}\cr 15 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 16 | 17 | \item{user}{\verb{[character]}\cr 18 | The username for the repository. By default queried using \code{get_user()}.} 19 | 20 | \item{quiet}{\verb{[logical]}\cr 21 | If \code{TRUE}, console output is suppressed.} 22 | } 23 | \value{ 24 | No return value, called for side effects. 25 | } 26 | \description{ 27 | Creates a Circle CI "user-key" (= SSH key pair) if none exists 28 | yet to enable deployment from Circle CI builds to GitHub. 29 | } 30 | \details{ 31 | The easiest way to achieve a deployment from Circle CI builds to a Github 32 | repo is by creating a so called "user-key" (i.e. an SSH key pair) on 33 | Circle CI. 34 | 35 | \code{use_circle_deploy()} tries to be smart by exiting early if such a key is 36 | already present. 37 | 38 | If the repo has not been enabled yet on Circle CI, please run \code{enable_repo()} 39 | first. 40 | Also to be able to authenticate to Github in the first place a personal 41 | access token needs to be set (via env var \code{GITHUB_TOKEN}). 42 | \code{usethis::github_token()} can be used to check if one is already set. 43 | If none is set, this function will prompt you to create one. 44 | } 45 | \examples{ 46 | \dontrun{ 47 | use_circle_deploy() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/fixtures/set_env_var().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: post 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/envvar?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '{"name":"foo","value":"test"}' 8 | headers: 9 | Content-Type: application/json 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 201 14 | category: Success 15 | reason: Created 16 | message: 'Success: (201) Created' 17 | headers: 18 | content-type: application/json;charset=utf-8 19 | date: Tue, 03 Nov 2020 11:39:40 GMT 20 | server: nginx 21 | set-cookie: ring-session=R5u8frl4LXLOdExt3excCX8vm5XDckpeUMjfVFBCiwDA%2FNjAaxarr716f32RHvics635tUfn6bTa64%2FQZaE67vk%2F%2FDQQcZQzPcam0J9ehTlVmjGp9XadPcc%2BXV4U5d7JWdi2uFAA2FokL%2FecDyjslnG9bHQJ2at%2FYqK%2BN4mvNUatA%2FE6IPERDyvkbbre%2BmUTynb23c1hRMxoqwCOoEDDAuy3p3yY%2BhjR%2BQ5FcTeZR%2F4%3D--YpFe4DRUgqMRFXVdzxBeA0bc7J6LgksZFSan5QwH9vQ%3D;Path=/;HttpOnly;Expires=Tue, 22 | 02 Nov 2021 21:24:11 +0000;Max-Age=1209600;Secure 23 | strict-transport-security: max-age=15724800 24 | x-circleci-identity: circle-www-api-v1-777c756979-dpd2d 25 | x-frame-options: DENY 26 | x-ratelimit-limit: '151' 27 | x-ratelimit-remaining: '150' 28 | x-ratelimit-reset: '0' 29 | x-request-id: 6459853f-21ce-4d20-9c2f-37c6a72449f9 30 | x-route: /api/v2/project/:project-slug/envvar 31 | content-length: '31' 32 | connection: keep-alive 33 | body: 34 | encoding: UTF-8 35 | file: no 36 | string: '{"name":"foo","value":"xxxxst"}' 37 | recorded_at: 2020-11-03 11:39:40 GMT 38 | recorded_with: vcr/0.5.4, webmockr/0.7.0 39 | -------------------------------------------------------------------------------- /tests/fixtures/delete_env_var().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: delete 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/envvar/foo?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | content-encoding: gzip 19 | content-type: application/json;charset=utf-8 20 | date: Tue, 03 Nov 2020 11:39:41 GMT 21 | server: nginx 22 | set-cookie: ring-session=TBYe0vbHSZYidigGKzLeUZBxhCLwN8sg042Cu9cWW1FhYNrPRPSBzndW%2F0T3ss2Gzfnq8xHDm0HWp9DpQIm%2FaRAV2lvkRBkfN6WDd6cBMHCe8NWagoZ2moS9G7ZqvaY85OxdDB7Yn%2BTl91u%2FVJwUjrpWEk%2B2%2FRhR%2FweCSPKQXuRcK0CEvNEbP3xT2%2FJ6dfWBzSF8sgnT2EaUK2Y0MxYJatu1X39nk0ZGETubTRMLkD0%3D--Q%2B1FWn6Q%2FG1qxQ5BX0TtufH%2B7iZT4IM8vKmw97MGkNo%3D;Path=/;HttpOnly;Expires=Tue, 23 | 02 Nov 2021 15:14:47 +0000;Max-Age=1209600;Secure 24 | strict-transport-security: max-age=15724800 25 | x-circleci-identity: circle-www-api-v1-777c756979-2mkkb 26 | x-frame-options: DENY 27 | x-ratelimit-limit: '151' 28 | x-ratelimit-remaining: '150' 29 | x-ratelimit-reset: '0' 30 | x-request-id: 480f9132-29fb-4196-abd2-49d4f1a41432 31 | x-route: /api/v2/project/:project-slug/envvar/:name 32 | content-length: '63' 33 | connection: keep-alive 34 | body: 35 | encoding: UTF-8 36 | file: no 37 | string: '{"message":"Environment variable deleted."}' 38 | recorded_at: 2020-11-03 11:39:41 GMT 39 | recorded_with: vcr/0.5.4, webmockr/0.7.0 40 | -------------------------------------------------------------------------------- /tests/fixtures/enable_repo().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: post 4 | uri: https://circleci.com/api/v1.1/project/gh/ropensci/circle/follow?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | content-encoding: gzip 19 | content-type: application/json;charset=utf-8 20 | date: Tue, 03 Nov 2020 11:39:38 GMT 21 | server: nginx 22 | set-cookie: ring-session=E7wxR%2FtLgVjt%2FRKXmMOU98RpLd%2Buyyurc%2BqP6n7Sw2ahTz3BKC4wMUTflwqRhqif5HsDHluelrhjCcIJTUap4VTJdMh41PJDFsR9Rn4Q7iVAgakp4pY6ku1T4Pn8NeVPPuBDziSDs18iEs2IDkBVv78bMYEF611%2Fbncy%2B3OUilZFQCAppToOsZLsh%2FrrGGGwZxIshnAJJhnIrB2SOQ%2F7y3EIcJ%2FusK1UCygN4%2FiC%2BjA%3D--zA425wL655ZCLczf%2B6%2BFbEQyb6wCiDh2KxnB1nkn0HM%3D;Path=/;HttpOnly;Expires=Tue, 23 | 02 Nov 2021 15:19:06 +0000;Max-Age=1209600;Secure 24 | strict-transport-security: max-age=15724800 25 | x-circleci-identity: circle-www-api-v1-777c756979-cb9sr 26 | x-frame-options: DENY 27 | x-ratelimit-limit: '151' 28 | x-ratelimit-remaining: '150' 29 | x-ratelimit-reset: '0' 30 | x-request-id: 2dfe2dc3-9f75-4b8d-b76d-aa68fb3be3d5 31 | x-route: /api/v1.1/project/:vcs-type/:username/:repo/follow 32 | content-length: '71' 33 | connection: keep-alive 34 | body: 35 | encoding: UTF-8 36 | file: no 37 | string: '{"following":true,"workflow":false,"first_build":null}' 38 | recorded_at: 2020-11-03 11:39:39 GMT 39 | recorded_with: vcr/0.5.4, webmockr/0.7.0 40 | -------------------------------------------------------------------------------- /tests/fixtures/get_circle_user().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: get 4 | uri: https://circleci.com/api/v2/me?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | access-control-allow-origin: '*' 19 | content-encoding: gzip 20 | content-type: application/json;charset=utf-8 21 | date: Tue, 03 Nov 2020 11:39:41 GMT 22 | server: nginx 23 | set-cookie: ring-session=KCsTxxk9lwlIIBwtFFUCenLnQjwzCoLCVgaAwAfuf0CwXmXXm1nmqU%2BNgzst3otfS%2FDahw%2B8OuJdvUl%2B0sniroLaeYtF5aiO5Taa7%2FiG62btkSQnt70JlG2NAqQnl561jrhzyPT2CZDHXXdQf%2BSGmb1BYvaHlp1S7rT2HSoJLQ0qa7BHfUz%2BuNC%2BBoaw19AmVTH%2BrirP2i7Ie4J%2FFDucecGazZY2Huj%2FdqcqnlC0chU%3D--6t2jww05CLJanZfE%2FsRtpOqNUQGrMjTcQUZykYeDYCg%3D;Path=/;HttpOnly;Expires=Tue, 24 | 02 Nov 2021 15:19:02 +0000;Max-Age=1209600;Secure 25 | strict-transport-security: max-age=15724800 26 | vary: Origin 27 | x-circleci-identity: circle-www-api-v1-777c756979-dq2m7 28 | x-frame-options: DENY 29 | x-ratelimit-limit: '151' 30 | x-ratelimit-remaining: '150' 31 | x-ratelimit-reset: '0' 32 | x-request-id: 5a657624-5954-4826-bdd0-3e2d858642b7 33 | x-route: /api/v2/me 34 | content-length: '101' 35 | connection: keep-alive 36 | body: 37 | encoding: UTF-8 38 | file: no 39 | string: '{"name":"Patrick Schratz","login":"pat-s","id":"9c373331-d0f7-45e1-afe6-4a5c75e00d10"}' 40 | recorded_at: 2020-11-03 11:39:41 GMT 41 | recorded_with: vcr/0.5.4, webmockr/0.7.0 42 | -------------------------------------------------------------------------------- /R/helpers-github.R: -------------------------------------------------------------------------------- 1 | #' @title Github information 2 | #' 3 | #' @description 4 | #' Retrieves metadata about a Git repository from GitHub. 5 | #' 6 | #' `github_info()` returns a list as obtained from the GET "/repos/:repo" API. 7 | #' 8 | #' @param path `[string]`\cr 9 | #' The path to a GitHub-enabled Git repository (or a subdirectory thereof). 10 | #' @template token 11 | #' @template remote 12 | #' @return Object of class `gh_response` (list type) with information about the 13 | #' queried repository. 14 | #' @keywords internal 15 | github_info <- function(path = ".", 16 | remote = "origin", 17 | .token = NULL) { 18 | remote_url <- get_remote_url(path, remote) 19 | repo <- extract_repo(remote_url) 20 | get_repo_data(repo, .token) 21 | } 22 | 23 | get_repo_data <- function(repo, .token = NULL) { 24 | req <- gh::gh("/repos/:repo", repo = repo, .token = .token) 25 | return(req) 26 | } 27 | 28 | get_remote_url <- function(path, remote) { 29 | remote_names <- gert::git_remote_list(path) 30 | if (!length(remote_names)) { # nocov start 31 | stop("Failed to lookup git remotes") 32 | } # nocov end 33 | remote_name <- remote 34 | if (!(remote_name %in% remote_names)) { # nocov start 35 | stop(sprintf( 36 | "No remote named '%s' found in remotes: '%s'.", 37 | remote_name, remote_names 38 | )) 39 | } # nocov end 40 | return(remote_names[remote_names$name == remote]$url) 41 | } 42 | 43 | extract_repo <- function(url) { 44 | # Borrowed from gh:::github_remote_parse 45 | re <- "github[^/:]*[/:]([^/]+)/(.*?)(?:\\.git)?$" 46 | m <- regexec(re, url) 47 | match <- regmatches(url, m)[[1]] 48 | 49 | if (length(match) == 0) { # nocov start 50 | stop("Unrecognized repo format: ", url) 51 | } # nocov end 52 | 53 | paste0(match[2], "/", match[3]) 54 | } 55 | -------------------------------------------------------------------------------- /tests/fixtures/get_env_vars().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: get 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/envvar?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | access-control-allow-origin: '*' 19 | content-encoding: gzip 20 | content-type: application/json;charset=utf-8 21 | date: Tue, 03 Nov 2020 11:39:40 GMT 22 | server: nginx 23 | set-cookie: ring-session=9K%2FLdob6RzkqWMOQ3ka5w9%2F4mi7%2FATI17oacP3MmqkVbvpU%2FKHJdH0Z4MVRKLKncQp6Lr7%2Fb6UnY%2BT%2BDfHbcX9lA1HY%2B7bNd6AUTDMcWn6JOrXEGImEOypyV9YYVyQJPzxeNK3h58pK2j%2BZq6agbTDekqVinjF777%2BdHKZj5mTjM%2BtIbxpGom8zwWu9%2Bm1rAl0L%2Bfpu01Yf12rGHfjGKZT3VJ7x3yIWZ%2FfplNNRc47c%3D--cP8efX9P9mo%2FuglWxNtPhTJcb6rFTRGSLcaV%2BMnyGtM%3D;Path=/;HttpOnly;Expires=Tue, 24 | 02 Nov 2021 15:14:47 +0000;Max-Age=1209600;Secure 25 | strict-transport-security: max-age=15724800 26 | vary: Origin 27 | x-circleci-identity: circle-www-api-v1-777c756979-2mkkb 28 | x-frame-options: DENY 29 | x-ratelimit-limit: '151' 30 | x-ratelimit-remaining: '150' 31 | x-ratelimit-reset: '0' 32 | x-request-id: 4066cbdf-d774-422e-8b8c-0d8899907385 33 | x-route: /api/v2/project/:project-slug/envvar 34 | content-length: '123' 35 | connection: keep-alive 36 | body: 37 | encoding: UTF-8 38 | file: no 39 | string: '{"next_page_token":null,"items":[{"name":"CIRCLE_CHECKOUT_KEY","value":"xxxx9e9f"},{"name":"R_CIRCLE","value":"xxxx892f"},{"name":"foo","value":"xxxxst"}]}' 40 | recorded_at: 2020-11-03 11:39:41 GMT 41 | recorded_with: vcr/0.5.4, webmockr/0.7.0 42 | -------------------------------------------------------------------------------- /man/circle.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/circle.R 3 | \name{circle} 4 | \alias{circle} 5 | \title{Circle CI HTTP Requests} 6 | \usage{ 7 | circle( 8 | verb = "GET", 9 | path = "", 10 | query = list(), 11 | body = "", 12 | api_version = "v2", 13 | encode = "json" 14 | ) 15 | } 16 | \arguments{ 17 | \item{verb}{\verb{[character]}\cr 18 | A character string containing an HTTP verb, defaulting to \code{GET}.} 19 | 20 | \item{path}{\verb{[character]}\cr 21 | A character string with the API endpoint (should begin with a slash).} 22 | 23 | \item{query}{\verb{[character]}\cr 24 | A list specifying any query string arguments to pass to the API. 25 | This is used to pass the API token.} 26 | 27 | \item{body}{\verb{[character]}\cr 28 | A named list or array of what should be passed in the 29 | request. 30 | Corresponds to the "-d" argument of the \code{curl} command.} 31 | 32 | \item{api_version}{\verb{[character]}\cr 33 | A character string specifying the Circle CI API version. 34 | This usually does not need to be changed by the user.} 35 | 36 | \item{encode}{\verb{[character]}\cr 37 | Encoding format. See \link[httr:POST]{httr::POST}.} 38 | } 39 | \value{ 40 | An object of class \code{circle_api} with the following elements 41 | \itemize{ 42 | \item \code{content} (queried content) 43 | \item \code{path} (API request) 44 | \item \code{response} (HTTP response information) 45 | } 46 | } 47 | \description{ 48 | Workhorse function for executing API requests to 49 | Circle CI. 50 | } 51 | \details{ 52 | In almost all cases, users should not need to execute API calls 53 | directly. However, if desired this functions makes it possible to issue 54 | any API request. If you experience calling a custom request heavily, 55 | consider opening a feature request on GitHub. 56 | } 57 | \examples{ 58 | \dontrun{ 59 | circle(verb = "GET", path = "/project/gh/ropensci/circle/checkout-key") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/testthat/test-general.R: -------------------------------------------------------------------------------- 1 | vcr::use_cassette("get_circle_user()", { 2 | test_that("get_circle_user() works", { 3 | skip_on_cran() 4 | 5 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 6 | # fail to lookup the git repo when running code coverage 7 | 8 | resp <- get_circle_user() 9 | 10 | expect_s3_class(resp, "circle_user") 11 | expect_equal(status_code(resp$response), 200) 12 | }) 13 | }) 14 | 15 | vcr::use_cassette("list_projects()", { 16 | test_that("list_projects() works", { 17 | skip_on_cran() 18 | 19 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 20 | # fail to lookup the git repo when running code coverage 21 | resp <- list_projects( 22 | repo = Sys.getenv("CIRCLE_REPO"), 23 | user = Sys.getenv("CIRCLE_OWNER") 24 | ) 25 | 26 | expect_equal(status_code(resp$response), 200) 27 | expect_gte(length(resp$content), 1) 28 | expect_s3_class(resp, "circle_api") 29 | }) 30 | }) 31 | 32 | vcr::use_cassette("new_build()", { 33 | test_that("triggering a new build works", { 34 | skip_on_cran() 35 | 36 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 37 | # fail to lookup the git repo when running code coverage 38 | resp <- new_build( 39 | repo = "circle", user = "ropensci" 40 | ) 41 | 42 | expect_equal(status_code(resp$response), 201) 43 | expect_match(resp[["content"]][["state"]], "pending") 44 | expect_s3_class( 45 | new_build( 46 | repo = Sys.getenv("CIRCLE_REPO"), 47 | user = Sys.getenv("CIRCLE_OWNER") 48 | ), 49 | "circle_api" 50 | ) 51 | }) 52 | }) 53 | 54 | vcr::use_cassette("has_checkout_key()", { 55 | test_that("checking the existence of checkout keys works", { 56 | skip_on_cran() 57 | 58 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 59 | # fail to lookup the git repo when running code coverage 60 | resp <- has_checkout_key( 61 | repo = Sys.getenv("CIRCLE_REPO"), 62 | user = Sys.getenv("CIRCLE_OWNER") 63 | ) 64 | 65 | expect_true(resp) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /tests/testthat/test-2-print.R: -------------------------------------------------------------------------------- 1 | vcr::use_cassette("s3-print-circle_pipeline", { 2 | test_that("s3 print method for 'circle_pipeline' works", { 3 | skip_on_cran() 4 | 5 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 6 | # fail to lookup the git repo when running code coverage 7 | 8 | out <- get_pipelines( 9 | repo = Sys.getenv("CIRCLE_REPO"), 10 | user = Sys.getenv("CIRCLE_OWNER") 11 | ) 12 | capture.output(expect_message( 13 | print(out), 14 | "A collection of 20 Circle CI pipelines" 15 | )) 16 | }) 17 | }) 18 | 19 | vcr::use_cassette("s3-print-circle_collection", { 20 | test_that("s3 print method for 'circle_collection' works", { 21 | skip_on_cran() 22 | 23 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 24 | # fail to lookup the git repo when running code coverage 25 | 26 | out <- get_workflows( 27 | repo = Sys.getenv("CIRCLE_REPO"), 28 | user = Sys.getenv("CIRCLE_OWNER") 29 | ) 30 | capture.output(expect_message( 31 | print(out), 32 | "A collection of 10 Circle CI workflows" 33 | )) 34 | }) 35 | }) 36 | 37 | vcr::use_cassette("s3-print-circle_workflow", { 38 | test_that("s3 print method for 'circle_workflow' works", { 39 | skip_on_cran() 40 | 41 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 42 | # fail to lookup the git repo when running code coverage 43 | 44 | wf <- get_workflows( 45 | repo = Sys.getenv("CIRCLE_REPO"), 46 | user = Sys.getenv("CIRCLE_OWNER") 47 | ) 48 | capture.output(expect_message( 49 | print(wf[[1]]), 50 | "A Circle CI workflow:" 51 | )) 52 | }) 53 | }) 54 | 55 | vcr::use_cassette("s3-print-circle_job", { 56 | test_that("s3 print method for 'circle_job' works", { 57 | skip_on_cran() 58 | 59 | # 'repo' and 'user' need to be set explicitly because `github_info()` will 60 | # fail to lookup the git repo when running code coverage 61 | 62 | job <- get_jobs( 63 | repo = Sys.getenv("CIRCLE_REPO"), 64 | user = Sys.getenv("CIRCLE_OWNER") 65 | ) 66 | capture.output(expect_message( 67 | print(job[[1]]), 68 | "A Circle CI job:" 69 | )) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /tests/fixtures/create_checkout_key().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: post 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/checkout-key?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '{"type":"user-key"}' 8 | headers: 9 | Content-Type: application/json 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 201 14 | category: Success 15 | reason: Created 16 | message: 'Success: (201) Created' 17 | headers: 18 | content-type: application/json;charset=utf-8 19 | date: Tue, 03 Nov 2020 11:39:40 GMT 20 | server: nginx 21 | set-cookie: ring-session=ebRom%2B5XCErze6AUlm0owxEGXqucuOZthFveRQvWl8b0sNR27ydy5qjWpqSqgR0rWCblVvZCsQp%2B8FvilxQfLBxWQvxLxB4CQQnCdjzLitd97Wc%2FKEhsLpMBv7%2BjhJMmdmx%2BFMDvfBTLTZGvG0cGBJKzm8uxnxq2kg47Gv97y4Z2d0nh4cDeILM%2Bt2OlfTK0zSyifm4xk7GPAcU%2FMf5RDkOa%2BPwf7b%2Bi9pDQAsJTGeA%3D--M1pnwTxbWVxIFwSQjApdD3tkyEa%2F6ejv1InkC%2B0NazM%3D;Path=/;HttpOnly;Expires=Wed, 22 | 03 Nov 2021 10:48:41 +0000;Max-Age=1209600;Secure 23 | strict-transport-security: max-age=15724800 24 | x-circleci-identity: circle-www-api-v1-777c756979-49rr9 25 | x-frame-options: DENY 26 | x-ratelimit-limit: '151' 27 | x-ratelimit-remaining: '150' 28 | x-ratelimit-reset: '0' 29 | x-request-id: 88bf4a33-109f-44a7-ac91-03f2fbcc4a7c 30 | x-route: /api/v2/project/:project-slug/checkout-key 31 | content-length: '546' 32 | connection: keep-alive 33 | body: 34 | encoding: UTF-8 35 | file: no 36 | string: '{"type":"github-user-key","preferred":true,"created_at":"2020-11-03T11:39:40.233Z","public_key":"ssh-rsa 37 | AAAAB3NzaC1yc2EAAAADAQABAAABAQCzwHNSKi2R+vZ7t4luyGbo36pCZSU/eV9pV368Gx+p2uuJo/jt4lRyXhXvmoIxdBec+yGPzZKq9ktKDqRPFV3DIdWyCL0vCq1AbPXuE2ldL+xgurqIW6bb/WPzY2rhG/LFB0qpyls5RXLbYaHzaHhydTv0ZltlpQ5lBMUBt0jfBc+nbinwZtJw0sD6rtGize7ngt1s+VbmixkpVaTgd3vjibzcKKwKvrFlBQfgcPKbZdLR/fzU+ssdIYQuq3OYMGeU2GiWkaBR6h8ehXwvhSmI2aQCxo8Br+fFDMyCrEYgOzaakvzkqdTcbEfxfyiql/T0x8e7WNT/8b4toX9TanKD 38 | \n","fingerprint":"d3:28:47:34:73:54:db:88:58:ba:16:65:a5:03:79:4f"}' 39 | recorded_at: 2020-11-03 11:39:40 GMT 40 | recorded_with: vcr/0.5.4, webmockr/0.7.0 41 | -------------------------------------------------------------------------------- /man/env_var.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/env-var.R 3 | \name{env_var} 4 | \alias{env_var} 5 | \alias{get_env_vars} 6 | \alias{set_env_var} 7 | \alias{delete_env_var} 8 | \title{Interact with Environment Variable(s) on Circle CI} 9 | \usage{ 10 | get_env_vars( 11 | name = NULL, 12 | repo = github_info()$name, 13 | user = github_info()$owner$login, 14 | vcs_type = "gh", 15 | api_version = "v2" 16 | ) 17 | 18 | set_env_var( 19 | var, 20 | repo = github_info()$name, 21 | user = github_info()$owner$login, 22 | vcs_type = "gh", 23 | api_version = "v2", 24 | quiet = FALSE 25 | ) 26 | 27 | delete_env_var( 28 | var, 29 | repo = github_info()$name, 30 | user = github_info()$owner$login, 31 | vcs_type = "gh", 32 | api_version = "v2", 33 | quiet = FALSE 34 | ) 35 | } 36 | \arguments{ 37 | \item{name}{\verb{[character]}\cr 38 | Name of a specific environment variable. 39 | If not set, all env vars are returned.} 40 | 41 | \item{repo}{\verb{[character]}\cr 42 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 43 | 44 | \item{user}{\verb{[character]}\cr 45 | The username for the repository. By default queried using \code{get_user()}.} 46 | 47 | \item{vcs_type}{\verb{[character]}\cr The version control system to use. 48 | Defaults to "gh" (Github).} 49 | 50 | \item{api_version}{\verb{[character]}\cr 51 | A character string specifying the Circle CI API version. 52 | This usually does not need to be changed by the user.} 53 | 54 | \item{var}{\verb{[list]}\cr 55 | A list containing key-value pairs of environment variable and its value.} 56 | 57 | \item{quiet}{\verb{[logical]}\cr 58 | If \code{TRUE}, console output is suppressed.} 59 | } 60 | \value{ 61 | An object of class \code{circle_api} with the following elements 62 | \itemize{ 63 | \item \code{content} (queried content) 64 | \item \code{path} (API request) 65 | \item \code{response} (HTTP response information) 66 | } 67 | } 68 | \description{ 69 | Add, get or set Circle CI environment variable(s) for a repo on 70 | Circle CI. 71 | } 72 | \examples{ 73 | \dontrun{ 74 | # get env var 75 | get_env_vars() 76 | 77 | # set env var 78 | set_env_var(var = list("foo" = "123")) 79 | 80 | # delete env var 81 | delete_env_var("foo") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /man/circle-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/circle-package.R 3 | \docType{package} 4 | \name{circle-package} 5 | \alias{circle-package} 6 | \title{Circle CI API Client} 7 | \description{ 8 | This package provides functionality for interacting with the 9 | Circle CI API. \href{https://circleci.com}{Circle CI} is a continuous 10 | integration provider which allows for 11 | automated testing of software each time that software is publicly committed 12 | to a repository on GitHub. 13 | 14 | This package interacts with the Circle CI REST API and allows to execute 15 | tasks in R without visiting the the website. This includes monitoring 16 | builds, modifying build environment settings and environment variables, and 17 | cancelling or restarting builds. 18 | 19 | Use of this package requires a Circle API key. Unless a key is already set, 20 | users will be guided through the creation of a key, 21 | API keys are disposable, but should still be treated securely. 22 | 23 | The following functions simplify integrating R package testing and 24 | deployment with GitHub and Circle CI: 25 | \itemize{ 26 | \item \code{\link[=enable_repo]{enable_repo()}} enables Circle CI for your repository, 27 | \item \code{\link[=use_circle_deploy]{use_circle_deploy()}} installs a public deploy key on GitHub and the 28 | corresponding private key on Circle CI to simplify deployments to GitHub 29 | from Circle CI. 30 | } 31 | } 32 | \examples{ 33 | \dontrun{ 34 | # check to see if you've authenticated correctly 35 | get_circle_user() 36 | } 37 | 38 | } 39 | \seealso{ 40 | Useful links: 41 | \itemize{ 42 | \item \url{https://docs.ropensci.org/circle/} 43 | \item \url{https://github.com/ropensci/circle} 44 | \item Report bugs at \url{https://github.com/ropensci/circle/issues} 45 | } 46 | 47 | } 48 | \author{ 49 | \strong{Maintainer}: Patrick Schratz \email{patrick.schratz@gmail.com} (\href{https://orcid.org/0000-0003-0748-6624}{ORCID}) 50 | 51 | Other contributors: 52 | \itemize{ 53 | \item Max Joseph (Max reviewed the package for ropensci, see ) [reviewer] 54 | \item Sharla Gelfand (Sharla reviewed the package for ropensci, see ) [reviewer] 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /tests/fixtures/has_checkout_key().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: get 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/checkout-key?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | access-control-allow-origin: '*' 19 | content-encoding: gzip 20 | content-type: application/json;charset=utf-8 21 | date: Tue, 03 Nov 2020 11:39:43 GMT 22 | server: nginx 23 | set-cookie: ring-session=CBbuaNtcZAgMyW0ifW%2Fih0e%2BEcTXRXNwINhX6OIfas6eYWBG9IMUT%2Fm7qLUZ7n5Yhhc3%2FxtUXwgjXNLpq5kYuouzvJ8oJZk2S%2BRQr%2BkT9mlCqGBeZdU4excYQY9PjUp39Bc52IrWXQteT8559IYxYuHCpGKgu78T3H0%2BLMOREiwZJUpr9V70giv1%2FIuP%2B3Wx2Iz3qBCbvv0oq2WhY2DQuAbdD07g5XST3zQ20mR1Zmg%3D--ntKm54uxvssOGBrVYeJurAyNPIXPP0QMkEvRpniwUFg%3D;Path=/;HttpOnly;Expires=Tue, 24 | 02 Nov 2021 15:16:45 +0000;Max-Age=1209600;Secure 25 | strict-transport-security: max-age=15724800 26 | vary: Origin 27 | x-circleci-identity: circle-www-api-v1-777c756979-zzd2d 28 | x-frame-options: DENY 29 | x-ratelimit-limit: '151' 30 | x-ratelimit-remaining: '150' 31 | x-ratelimit-reset: '0' 32 | x-request-id: a9d71a5b-8ca5-47f6-9b1f-985732d3f858 33 | x-route: /api/v2/project/:project-slug/checkout-key 34 | content-length: '496' 35 | connection: keep-alive 36 | body: 37 | encoding: UTF-8 38 | file: no 39 | string: '{"next_page_token":null,"items":[{"type":"github-user-key","preferred":true,"created_at":"2020-10-08T08:13:39.632Z","public_key":"ssh-rsa 40 | AAAAB3NzaC1yc2EAAAADAQABAAABAQChH8hA0L+vEmFxCyTwFii98G0HzfntiD9WelvOSnqopjPNtvp4f7nT4uXwr7PNLitbMIYMFxJyse8U4spAlSnFCHhYcf2ckA/nNX21CzSigGL6TOUcctIJcuwlbFMYfcTUipHUyWEz2lnGoVe3udEZL9JCWJfJdi/afZhjQDfhOH/Gmy/F3HQ6LlXgn5EHoExsOKRERQteAebl8B1i6mulGYqSjj4JBE5UWyz5w74V6l9QjUO5xngVZ0KtVu5bhmzwsC9OIQK9BjBZ4imicFQMi2FtgXeiogN5h9BVPVbfsh+c9TvpbzxaODJLOGJ7liCMq2o0ICioCCXUYUJQH8yh 41 | \n","fingerprint":"4e:50:77:79:59:1a:ef:27:41:3f:b4:2f:61:74:ca:d9"}]}' 42 | recorded_at: 2020-11-03 11:39:43 GMT 43 | recorded_with: vcr/0.5.4, webmockr/0.7.0 44 | -------------------------------------------------------------------------------- /R/use-circle-deploy.R: -------------------------------------------------------------------------------- 1 | #' @title Set Up Build Deployment Between Circle CI And Github 2 | #' @description Creates a Circle CI "user-key" (= SSH key pair) if none exists 3 | #' yet to enable deployment from Circle CI builds to GitHub. 4 | #' @template repo 5 | #' @template user 6 | #' @template quiet 7 | #' 8 | #' @importFrom gh gh 9 | #' 10 | #' @details 11 | #' The easiest way to achieve a deployment from Circle CI builds to a Github 12 | #' repo is by creating a so called "user-key" (i.e. an SSH key pair) on 13 | #' Circle CI. 14 | #' 15 | #' `use_circle_deploy()` tries to be smart by exiting early if such a key is 16 | #' already present. 17 | #' 18 | #' If the repo has not been enabled yet on Circle CI, please run `enable_repo()` 19 | #' first. 20 | #' Also to be able to authenticate to Github in the first place a personal 21 | #' access token needs to be set (via env var `GITHUB_TOKEN`). 22 | #' `usethis::github_token()` can be used to check if one is already set. 23 | #' If none is set, this function will prompt you to create one. 24 | #' 25 | #' @return No return value, called for side effects. 26 | #' @examples 27 | #' \dontrun{ 28 | #' use_circle_deploy() 29 | #' } 30 | #' @export 31 | use_circle_deploy <- function(repo = github_info()$name, 32 | user = github_info()$owner$login, 33 | quiet = FALSE) { 34 | 35 | # all of this functionality is tested in single parts, therefore setting 36 | # "nocov" here 37 | 38 | # nocov start 39 | 40 | # authenticate on github and circle and set up keys/vars 41 | token <- gh::gh_token() 42 | if (token == "") { 43 | cli::cli_alert_info( 44 | "No Github token found. Opening a browser window to create one." 45 | ) 46 | usethis::create_github_token() 47 | stop("Circle: Please restart your R session after setting the token and try again.") # nolint 48 | } 49 | 50 | if (has_checkout_key(preferred = TRUE)) { 51 | cli::cli_alert_info( 52 | "A {.field user-key} already exists and is set as 'preferred' in your 53 | Circle CI settings. 54 | You are all set for build deployment.", 55 | wrap = TRUE 56 | ) 57 | } else { 58 | create_checkout_key(user = user, repo = repo, type = "user-key") 59 | cli::rule() 60 | if (!quiet) { 61 | cli::cli_alert_success( 62 | "Added a {.field user-key} to project .field {{user}/{repo}} on 63 | Circle CI. 64 | This enables deployment from builds.", 65 | wrap = TRUE 66 | ) 67 | } 68 | } 69 | } # nocov end 70 | -------------------------------------------------------------------------------- /R/circle.R: -------------------------------------------------------------------------------- 1 | #' @title Circle CI HTTP Requests 2 | #' 3 | #' @description Workhorse function for executing API requests to 4 | #' Circle CI. 5 | #' 6 | #' @import httr 7 | #' @importFrom jsonlite fromJSON 8 | #' 9 | #' @details In almost all cases, users should not need to execute API calls 10 | #' directly. However, if desired this functions makes it possible to issue 11 | #' any API request. If you experience calling a custom request heavily, 12 | #' consider opening a feature request on GitHub. 13 | #' 14 | #' @param verb `[character]`\cr 15 | #' A character string containing an HTTP verb, defaulting to `GET`. 16 | #' @param path `[character]`\cr 17 | #' A character string with the API endpoint (should begin with a slash). 18 | #' @param query `[character]`\cr 19 | #' A list specifying any query string arguments to pass to the API. 20 | #' This is used to pass the API token. 21 | #' @param body `[character]`\cr 22 | #' A named list or array of what should be passed in the 23 | #' request. 24 | #' Corresponds to the "-d" argument of the `curl` command. 25 | #' @param encode `[character]`\cr 26 | #' Encoding format. See [httr::POST]. 27 | #' @template api_version 28 | #' 29 | #' @return An object of class `circle_api` with the following elements 30 | #' - `content` (queried content) 31 | #' - `path` (API request) 32 | #' - `response` (HTTP response information) 33 | #' 34 | #' @export 35 | #' @examples 36 | #' \dontrun{ 37 | #' circle(verb = "GET", path = "/project/gh/ropensci/circle/checkout-key") 38 | #' } 39 | circle <- function(verb = "GET", 40 | path = "", 41 | query = list(), 42 | body = "", 43 | api_version = "v2", 44 | encode = "json") { 45 | 46 | url <- paste0("https://circleci.com/api/", api_version, path) 47 | 48 | # check for api key 49 | query$"circle-token" <- circle_check_api_key() # nolint 50 | 51 | # set user agent 52 | ua <- user_agent("http://github.com/ropensci/circle") 53 | 54 | resp <- VERB( 55 | verb = verb, url = url, body = body, 56 | query = query, encode = encode, ua, accept_json() 57 | ) 58 | 59 | if (http_type(resp) != "application/json") { 60 | stop("API did not return json", call. = FALSE) # nocov 61 | } 62 | 63 | # parse response into readable object 64 | parsed <- fromJSON(content(resp, "text", encoding = "UTF-8"), 65 | simplifyVector = FALSE 66 | ) 67 | 68 | structure( 69 | list( 70 | content = parsed, 71 | path = path, 72 | response = resp 73 | ), 74 | class = "circle_api" 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /man/builds.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/builds.R 3 | \name{builds} 4 | \alias{builds} 5 | \alias{get_pipelines} 6 | \alias{get_workflows} 7 | \alias{get_jobs} 8 | \alias{retry_workflow} 9 | \title{Retrieve Metadata from Circle CI Builds} 10 | \usage{ 11 | get_pipelines( 12 | repo = github_info()$name, 13 | user = github_info()$owner$login, 14 | limit = 30, 15 | vcs_type = "gh", 16 | api_version = "v2" 17 | ) 18 | 19 | get_workflows( 20 | pipeline_id = NULL, 21 | repo = github_info()$name, 22 | user = github_info()$owner$login 23 | ) 24 | 25 | get_jobs( 26 | workflow_id = NULL, 27 | repo = github_info()$name, 28 | user = github_info()$owner$login, 29 | vcs_type = "gh" 30 | ) 31 | 32 | retry_workflow(workflow_id = NULL) 33 | } 34 | \arguments{ 35 | \item{repo}{\verb{[character]}\cr 36 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 37 | 38 | \item{user}{\verb{[character]}\cr 39 | The username for the repository. By default queried using \code{get_user()}.} 40 | 41 | \item{limit}{\verb{[integer]}\cr 42 | How many builds should be returned? Maximum allowed by Circle is 43 | 30.} 44 | 45 | \item{vcs_type}{\verb{[character]}\cr The version control system to use. 46 | Defaults to "gh" (Github).} 47 | 48 | \item{api_version}{\verb{[character]}\cr 49 | A character string specifying the Circle CI API version. 50 | This usually does not need to be changed by the user.} 51 | 52 | \item{pipeline_id}{\verb{[character]}\cr 53 | A Circle CI pipeline ID.} 54 | 55 | \item{workflow_id}{\verb{[character]}\cr 56 | A Circle CI workflow ID.} 57 | } 58 | \value{ 59 | An object of class \code{circle_collection} containing list 60 | information on the queried Circle CI pipelines/workflows/jobs. 61 | } 62 | \description{ 63 | Query information about pipelines, workflows or jobs on 64 | Circle CI. 65 | The S3 \code{print()} method for these functions returns the respective 66 | pipeline IDs. 67 | To inspect the details of each pipeline, save the return value in an object 68 | and inspect the respective sub-lists. 69 | 70 | If no pipeline or workflow is supplied to \code{get_workflows()}/\code{get_jobs()}, 71 | the ten most recent pipelines/jobs are queried, respectively. 72 | } 73 | \details{ 74 | While the \verb{get_*()} functions query information about the respective 75 | build level details (pipeline - workflow - job), \code{retry_workflow()} let's 76 | users rerun a specific workflow. 77 | By default, the workflow from the most recent pipeline will be rerun if 78 | no pipeline ID was supplied. 79 | } 80 | \examples{ 81 | \dontrun{ 82 | pipelines <- get_pipelines() 83 | 84 | workflows <- get_workflows() 85 | 86 | jobs <- get_jobs() 87 | 88 | # rerun most recent workflow 89 | retry_workflow() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Renviron 2 | .Rproj.user 3 | .Rhistory 4 | .RData 5 | .httr-oauth 6 | tests/testthat/travis-testthat 7 | inst/doc 8 | 9 | # Created by https://www.toptal.com/developers/gitignore/api/r 10 | # Edit at https://www.toptal.com/developers/gitignore?templates=r 11 | ### R ### 12 | # History files 13 | .Rapp.history 14 | # Session Data files 15 | # User-specific files 16 | .Ruserdata 17 | # Example code in package build process 18 | *-Ex.R 19 | # Output files from R CMD build 20 | /*.tar.gz 21 | # Output files from R CMD check 22 | /*.Rcheck/ 23 | # RStudio files 24 | .Rproj.user/ 25 | # produced vignettes 26 | vignettes/*.html 27 | vignettes/*.pdf 28 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 29 | # knitr and R markdown default cache directories 30 | *_cache/ 31 | /cache/ 32 | # Temporary files created by R markdown 33 | *.utf8.md 34 | *.knit.md 35 | # R Environment Variables 36 | ### R.Bookdown Stack ### 37 | # R package: bookdown caching files 38 | /*_files/ 39 | # End of https://www.toptal.com/developers/gitignore/api/r 40 | docs/ 41 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode 42 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode 43 | ### VisualStudioCode ### 44 | .vscode/* 45 | !.vscode/settings.json 46 | !.vscode/tasks.json 47 | !.vscode/launch.json 48 | !.vscode/extensions.json 49 | *.code-workspace 50 | ### VisualStudioCode Patch ### 51 | # Ignore all local history of files 52 | .history 53 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode 54 | # Created by https://www.toptal.com/developers/gitignore/api/macos 55 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos 56 | ### macOS ### 57 | # General 58 | .DS_Store 59 | .AppleDouble 60 | .LSOverride 61 | # Icon must end with two \r 62 | Icon 63 | # Thumbnails 64 | ._* 65 | # Files that might appear in the root of a volume 66 | .DocumentRevisions-V100 67 | .fseventsd 68 | .Spotlight-V100 69 | .TemporaryItems 70 | .Trashes 71 | .VolumeIcon.icns 72 | .com.apple.timemachine.donotpresent 73 | # Directories potentially created on remote AFP share 74 | .AppleDB 75 | .AppleDesktop 76 | Network Trash Folder 77 | Temporary Items 78 | .apdisk 79 | # End of https://www.toptal.com/developers/gitignore/api/macos 80 | # Created by https://www.toptal.com/developers/gitignore/api/windows 81 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows 82 | ### Windows ### 83 | # Windows thumbnail cache files 84 | Thumbs.db 85 | Thumbs.db:encryptable 86 | ehthumbs.db 87 | ehthumbs_vista.db 88 | # Dump file 89 | *.stackdump 90 | # Folder config file 91 | [Dd]esktop.ini 92 | # Recycle Bin used on file shares 93 | $RECYCLE.BIN/ 94 | # Windows Installer files 95 | *.cab 96 | *.msi 97 | *.msix 98 | *.msm 99 | *.msp 100 | # Windows shortcuts 101 | *.lnk 102 | # End of https://www.toptal.com/developers/gitignore/api/windows 103 | debug.R 104 | launch.json 105 | CRAN-RELEASE 106 | 107 | .envrc -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | ### Fixing typos 4 | 5 | Small typos or grammatical errors in documentation may be edited directly using 6 | the GitHub web interface, so long as the changes are made in the _source_ file. 7 | 8 | - YES: you edit a roxygen comment in a `.R` file below `R/`. 9 | - NO: you edit an `.Rd` file below `man/`. 10 | 11 | ### Prerequisites 12 | 13 | Before you make a substantial pull request, you should always file an issue and 14 | make sure someone from the team agrees that it’s a problem. If you’ve found a 15 | bug, create an associated issue and illustrate the bug with a minimal 16 | [reprex](https://www.tidyverse.org/help/#reprex). 17 | 18 | ### Pull request process 19 | 20 | - We recommend that you create a Git branch for each pull request (PR). 21 | - Look at the Travis and AppVeyor build status before and after making changes. 22 | The `README` should contain badges for any continuous integration services used 23 | by the package. 24 | - We recommend the tidyverse [style guide](http://style.tidyverse.org). 25 | You can use the [styler](https://CRAN.R-project.org/package=styler) package to 26 | apply these styles, but please don't restyle code that has nothing to do with 27 | your PR. 28 | - We use [roxygen2](https://cran.r-project.org/package=roxygen2). 29 | - We use [testthat](https://cran.r-project.org/package=testthat). Contributions 30 | with test cases included are easier to accept. 31 | - For user-facing changes, add a bullet to the top of `NEWS.md` below the 32 | current development version header describing the changes made followed by your 33 | GitHub username, and links to relevant issue(s)/PR(s). 34 | 35 | ### Testing the package 36 | 37 | To test the package locally, the following conditions need to be met: 38 | 39 | 1. A Circle CI API Token stored in an env var named `R_CIRCLE` 40 | 1. An active repository on Circle CI with access rights 41 | 42 | Alternatively changes can be pushed to GitHub and being tested by the CI runner. 43 | The CI runner operates on the `ropensci/circle` repo with access to the respective secrets during the run. 44 | 45 | ### Code of Conduct 46 | 47 | Please note that the circle project is released with a 48 | [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this 49 | project you agree to abide by its terms. 50 | 51 | ### See rOpenSci [contributing guide](https://devguide.ropensci.org/contributingguide.html) 52 | 53 | for further details. 54 | 55 | ### Discussion forum 56 | 57 | Check out our [discussion forum](https://discuss.ropensci.org) if 58 | 59 | - you have a question, an use case, or otherwise not a bug or feature request for the software itself. 60 | - you think your issue requires a longer form discussion. 61 | 62 | ### Prefer to Email? 63 | 64 | Email the person listed as maintainer in the `DESCRIPTION` file of this repo. 65 | 66 | Though note that private discussions over email don't help others - of course email is totally warranted if it's a sensitive problem of any kind. 67 | 68 | ### Thanks for contributing! 69 | 70 | This contributing guide is adapted from the tidyverse contributing guide available at https://raw.githubusercontent.com/r-lib/usethis/master/inst/templates/tidy-contributing.md 71 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # circle 0.7.3 4 | 5 | - Account for deprecated `usethis` functions 6 | - Fix validation for Circle CI API token 7 | - Roxygen updates 8 | 9 | 10 | # circle 0.7.2 11 | 12 | - CRAN documentation fixes 13 | 14 | 15 | # circle 0.7.1 16 | 17 | - Initial CRAN release 18 | 19 | 20 | # circle 0.7.0 21 | 22 | Implement feedback from [ropensci review](https://github.com/ropensci/software-review/issues/356#): 23 | 24 | - Document return values of all functions 25 | - Refine {cli} console messages 26 | - Most functions gained a `quiet` argument to silence {cli} messages 27 | - Be more chatty for side-effect functions 28 | - Always return a `circle_api` object for consistency 29 | - Switch main branch from `master` to `main` 30 | - Escape examples 31 | - Require {usethis} >= 2.0.0 32 | - New vignette ["Using {circle} with {tic}"](https://docs.ropensci.org/circle/articles/tic.html) 33 | 34 | 35 | # circle 0.6.0 36 | 37 | - Copy over GitHub auth and SSH helpers from {travis} 38 | - Print informative message when creating a user key errors with status code 500 39 | - `*_env_var()`: Use owner info instead of user info to query repo 40 | - Use {vcr} for http testing 41 | - Add pkgdown reference structure 42 | - Added pre-commit hooks 43 | - Added codemeta 44 | - Use roxygen markdown 45 | - Added parameter types to help pages 46 | 47 | # circle 0.5.0 48 | 49 | ## Major 50 | 51 | - Add new authentication mechanism: `browse_circle_token()` to to query the API token and store it in an env variable `R_CIRCLE` as an alternative method to store it in `~/.circleci/cli.yml` 52 | - Remove `auth_travis()` 53 | - Rename `circleHTTP()` to `circle()` 54 | - add `github_repo()` 55 | - `get_pipelines()`, `get_workflows()` and `get_jobs()` are now formatted as class `circle_builds`, `circle_collection()` and have a somewhat pretty print output 56 | - `*_checkout_key()`: Optimize printer, catch errors, add info messages, add test 57 | - make `get_pipelines()`, `get_workflows()` and `get_jobs()` work with API v2 58 | - rename `list_artifacts()` -> `get_build_artifacts()` 59 | 60 | ## Bugfixes 61 | 62 | - Pipelines without a workflow ID caused `get_builds()` to error. Now pipelines without a workflow ID are removed internally before continuing. 63 | - setting env vars now works 64 | - make `create_checkout_key()` work with API v2 65 | 66 | # circle 0.4.0 67 | 68 | - update "cache" function with new user/owner logic from v0.3.0 69 | - new `has_checkout_key()` to check if a specific checkout key exists in the project 70 | 71 | # circle 0.3.0 72 | 73 | - Rename argument `project` to `repo` to stay consistent with _travis_ pkg. 74 | 75 | - Add Github helper functions to easily query owners and users for the repository operating on. This change requires the _git2r_ package from now on. 76 | 77 | # circle 0.2.0 78 | 79 | - Fix `api_version` in `create_ssh_key()` 80 | - rename `ssh_key*` functions to `checkout_key*` 81 | - `create_checkout_key()` change default for arg `type` from "github-user-key" to "deploy-key" 82 | - add argument `encode` to `circleHTTP()` 83 | - add `use_circle_deploy()` 84 | 85 | # circleci 0.1.0 86 | 87 | - First working version 88 | -------------------------------------------------------------------------------- /man/checkout_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checkout-key.R 3 | \name{checkout_key} 4 | \alias{checkout_key} 5 | \alias{create_checkout_key} 6 | \alias{get_checkout_keys} 7 | \alias{delete_checkout_key} 8 | \alias{has_checkout_key} 9 | \title{Interact with "Checkout Keys" on Circle CI} 10 | \usage{ 11 | create_checkout_key( 12 | repo = github_info()$name, 13 | user = github_info()$owner$login, 14 | type = "user-key", 15 | api_version = "v2", 16 | vcs_type = "gh", 17 | quiet = FALSE 18 | ) 19 | 20 | get_checkout_keys( 21 | repo = github_info()$name, 22 | user = github_info()$owner$login, 23 | vcs_type = "gh", 24 | api_version = "v2" 25 | ) 26 | 27 | delete_checkout_key( 28 | fingerprint = NULL, 29 | repo = github_info()$name, 30 | user = github_info()$owner$login, 31 | type = "user-key", 32 | api_version = "v2", 33 | vcs_type = "gh" 34 | ) 35 | 36 | has_checkout_key( 37 | repo = github_info()$name, 38 | user = github_info()$owner$login, 39 | type = "github-user-key", 40 | vcs_type = "gh", 41 | preferred = TRUE 42 | ) 43 | } 44 | \arguments{ 45 | \item{repo}{\verb{[character]}\cr 46 | The repository slug to use. Must follow the "\code{user/repo}" structure.} 47 | 48 | \item{user}{\verb{[character]}\cr 49 | The username for the repository. By default queried using \code{get_user()}.} 50 | 51 | \item{type}{\verb{[character]}\cr 52 | Type of key to add. Options are "user-key" and "deploy-key".} 53 | 54 | \item{api_version}{\verb{[character]}\cr 55 | A character string specifying the Circle CI API version. 56 | This usually does not need to be changed by the user.} 57 | 58 | \item{vcs_type}{\verb{[character]}\cr The version control system to use. 59 | Defaults to "gh" (Github).} 60 | 61 | \item{quiet}{\verb{[logical]}\cr 62 | If \code{TRUE}, console output is suppressed.} 63 | 64 | \item{fingerprint}{\verb{[character]}\cr 65 | The fingerprint of the checkout key which should be deleted.} 66 | 67 | \item{preferred}{\verb{[logical]}\cr 68 | Checks whether the requested type is the "preferred" key.} 69 | } 70 | \value{ 71 | An object of class \code{circle_api} with the following elements 72 | \itemize{ 73 | \item \code{content} (queried content) 74 | \item \code{path} (API request) 75 | \item \code{response} (HTTP response information) 76 | } 77 | } 78 | \description{ 79 | Create, delete, query or check different types of checkout keys 80 | for a specific Circle CI project. 81 | Valid values for argument \code{type} are \code{"user-key"} or \code{"deploy-key"}. 82 | 83 | A "Checkout Key" on Circle CI is a specific SSH key which is used to checkout 84 | repositories into a Circle CI build and possible deploy changes to the 85 | repository. 86 | Circle CI subdivides "Checkout Keys" into "user-key" and "deploy-key". 87 | 88 | Please see "Deployment" section in the "Getting Started" vignette for more 89 | information. 90 | } 91 | \examples{ 92 | \dontrun{ 93 | # by default a "user-key" will be created which can also be used for build 94 | # deployments 95 | create_checkout_key() 96 | 97 | # A "deploy-key" can only be used to checkout code from the repository into 98 | # a Circle CI build 99 | create_checkout_key(type = "deploy-key") 100 | } 101 | \dontrun{ 102 | get_checkout_keys() 103 | } 104 | \dontrun{ 105 | delete_checkout_key() 106 | } 107 | \dontrun{ 108 | has_checkout_key() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/fixtures/new_build().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: post 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/pipeline?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '{"branch":"master"}' 8 | headers: 9 | Content-Type: application/json 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 201 14 | category: Success 15 | reason: Created 16 | message: 'Success: (201) Created' 17 | headers: 18 | content-type: application/json;charset=utf-8 19 | date: Tue, 03 Nov 2020 11:39:42 GMT 20 | server: nginx 21 | set-cookie: ring-session=HGikj1CWdqtrFfkLGBr0oVg%2FG9vPKBZ%2ByTcIBX5tv1eBqkd4VY%2FU3YCRbNmg5TKzrcyNhLj4P8UfbH9GVwZ7HoM9v3abiRgjrhG%2BlBvt7B2Q8FUpdDaPSKljlk98sYEmJ9AY7gPyzY0gjaZ7c0PHx4LvruD3fUYXFHqk1GoszsIAg4MoIQpri2kFYQ6gBpdCtI4%2BTvZGp9KthPhUT1199HqUsdgHC0kqzXRCnkSMEyg%3D--38pnTckZi9uqT3Zij7oINARfhpMT%2FE7Mgl2PgFf%2BdhA%3D;Path=/;HttpOnly;Expires=Tue, 22 | 02 Nov 2021 15:10:08 +0000;Max-Age=1209600;Secure 23 | strict-transport-security: max-age=15724800 24 | x-circleci-identity: circle-www-api-v1-777c756979-vnm26 25 | x-frame-options: DENY 26 | x-ratelimit-limit: '151' 27 | x-ratelimit-remaining: '150' 28 | x-ratelimit-reset: '0' 29 | x-request-id: e76201be-24df-48f0-aec3-2d42eba32047 30 | x-route: /api/v2/project/:project-slug/pipeline 31 | content-length: '116' 32 | connection: keep-alive 33 | body: 34 | encoding: UTF-8 35 | file: no 36 | string: '{"number":470,"state":"pending","id":"6cecb5f1-fc2f-4aeb-9427-64414ec92354","created_at":"2020-11-03T11:39:42.442Z"}' 37 | recorded_at: 2020-11-03 11:39:43 GMT 38 | recorded_with: vcr/0.5.4, webmockr/0.7.0 39 | - request: 40 | method: post 41 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/pipeline?circle-token=<<>> 42 | body: 43 | encoding: '' 44 | string: '{"branch":"master"}' 45 | headers: 46 | Content-Type: application/json 47 | Accept: application/json 48 | response: 49 | status: 50 | status_code: 201 51 | category: Success 52 | reason: Created 53 | message: 'Success: (201) Created' 54 | headers: 55 | content-type: application/json;charset=utf-8 56 | date: Tue, 03 Nov 2020 11:39:42 GMT 57 | server: nginx 58 | set-cookie: ring-session=bWTgir85mJDOuJsrJ274i2zWA062490w4FG3sknluWneDlkPeU6JyAUAQRlur17A6Qx%2BJq6dy%2BB2ippkUokmXNoMzu6ZTBuMFWD%2FZsktO1s3l5TaxHD8jW5wwWde%2BmXu2K6ow4%2F6wo7WgydRSXWKH8rJwNb5gDOaUvw9d0YZw0MUJcIbJvPFeW2lTdcoqlIRDrqBbfKNTWajRTumd1k%2B3jiPgeSbHmk1SToR0LuRYuc%3D--5IS2osmCzCOtFYTGzGfk7RnvAPbNMcS9Y2PmQ1KP%2BEk%3D;Path=/;HttpOnly;Expires=Tue, 59 | 02 Nov 2021 15:16:36 +0000;Max-Age=1209600;Secure 60 | strict-transport-security: max-age=15724800 61 | x-circleci-identity: circle-www-api-v1-777c756979-8dq84 62 | x-frame-options: DENY 63 | x-ratelimit-limit: '151' 64 | x-ratelimit-remaining: '150' 65 | x-ratelimit-reset: '0' 66 | x-request-id: 644ef9fe-054b-4632-b846-5c00749dab41 67 | x-route: /api/v2/project/:project-slug/pipeline 68 | content-length: '116' 69 | connection: keep-alive 70 | body: 71 | encoding: UTF-8 72 | file: no 73 | string: '{"number":471,"state":"pending","id":"a1e30a6b-5747-43fa-a415-3c6f5d9debda","created_at":"2020-11-03T11:39:42.944Z"}' 74 | recorded_at: 2020-11-03 11:39:43 GMT 75 | recorded_with: vcr/0.5.4, webmockr/0.7.0 76 | -------------------------------------------------------------------------------- /R/auth.R: -------------------------------------------------------------------------------- 1 | #' Authenticate to Circle CI 2 | #' @description 3 | #' A Circle CI API token is needed to interact with the Circle CI API. 4 | #' `browse_circle_token()` opens a browser window for the respective Circle CI 5 | #' endpoint to retrieve the key. 6 | #' 7 | #' @import cli 8 | #' @section Store API token: 9 | #' 10 | #' `circle` supports two ways of storing the Circle API tokens: 11 | #' 12 | #' - via env vars `R_CIRCLE` 13 | #' - via `~/.circleci/cli.yml` 14 | #' 15 | #' The latter should already be present if you already used the `circle` CLI 16 | #' tool at some point in the past. If not, its up to your preference which 17 | #' approach to use. 18 | #' 19 | #' The following instructions should help to set up `~/.circleci/cli.yml` 20 | #' correctly: 21 | #' 1. Copy the token from the browser after having called 22 | #' `browse_circle_token()`. You can use 23 | #' `edit_circle_config()` to open `~/.circleci/cli.yml`. 24 | #' 2. The token should be stored using the following structure 25 | #' 26 | #' ```sh 27 | #' host: https://circleci.com 28 | #' endpoint: graphql-unstable 29 | #' token: 30 | #' ``` 31 | #' @export 32 | #' @return Returns `TRUE` (invisibly). 33 | #' @examples 34 | #' \dontrun{ 35 | #' browse_circle_token() 36 | #' 37 | #' edit_circle_config() 38 | #' } 39 | browse_circle_token <- function() { # nocov start 40 | 41 | cli_alert("Querying API token...") 42 | cli_text("Opening URL {.url 43 | https://circleci.com/account/api}.") 44 | utils::browseURL("https://circleci.com/account/api") 45 | cli_alert("Call {.fun circle::edit_circle_config} to open 46 | {.file ~/.circleci/cli.yml} or {.fun edit_r_environ} to open 47 | {.file ~/.Renviron}, depending on how 48 | you want to store the API key. See {.code ?browse_circle_token()} for 49 | details.", wrap = TRUE) 50 | return(invisible(TRUE)) 51 | } # nocov end 52 | 53 | #' @title Open circle Configuration file 54 | #' @description 55 | #' Opens `~/.circleci/cli.yml`. 56 | #' @return No return value, called for side effects. 57 | #' @export 58 | edit_circle_config <- function() { # nocov start 59 | usethis::edit_file("~/.circleci/cli.yml") 60 | } # nocov end 61 | 62 | # check if API key is stored in ~/.circleci/cli.yml 63 | circle_check_api_key <- function() { 64 | if (!Sys.getenv("R_CIRCLE") == "") { 65 | token <- Sys.getenv("R_CIRCLE") 66 | 67 | stopifnot(is_token(token)) 68 | 69 | return(token) 70 | } else { # nocov start 71 | 72 | # some checks for ~/.circleci/cli.yml 73 | 74 | if (!file.exists("~/.circleci/cli.yml")) { 75 | cli::cli_alert_danger("To interact with the Circle CI API, an API token is 76 | required. Please call {.fun browse_circle_token} first. 77 | Alternatively, set the API key via env vars {.var R_CIRCLE}.", 78 | wrap = TRUE 79 | ) 80 | stop("Circle API key missing.", call. = FALSE) 81 | } else { 82 | yml <- readLines("~/.circleci/cli.yml") 83 | if (!any(grepl("token", yml))) { 84 | cli::cli_alert_danger("No circle API key found. 85 | Please call {.code browse_circle_token()} first.", wrap = TRUE) 86 | stop("Circle API key missing.", call. = FALSE) 87 | } 88 | } 89 | token <- read_token() 90 | stopifnot(is_token(token)) 91 | 92 | return(token) 93 | } 94 | } # nocov end 95 | 96 | is_token <- function(token) { 97 | grepl("^[A-Za-z0-9_]+_[0-9a-f]{40}$", token) 98 | } 99 | 100 | read_token <- function() { # nocov start 101 | yml <- readLines("~/.circleci/cli.yml") 102 | endpoint_line <- which(grepl("token", yml)) 103 | token <- yml[endpoint_line] 104 | token <- strsplit(token, " ")[[1]][2] 105 | return(token) 106 | } # nocov end 107 | -------------------------------------------------------------------------------- /R/env-var.R: -------------------------------------------------------------------------------- 1 | #' @title Interact with Environment Variable(s) on Circle CI 2 | #' @description Add, get or set Circle CI environment variable(s) for a repo on 3 | #' Circle CI. 4 | #' @param name `[character]`\cr 5 | #' Name of a specific environment variable. 6 | #' If not set, all env vars are returned. 7 | #' @template repo 8 | #' @template user 9 | #' @template vcs 10 | #' @param var `[list]`\cr 11 | #' A list containing key-value pairs of environment variable and its value. 12 | #' @template api_version 13 | #' @template quiet 14 | #' @name env_var 15 | #' @return An object of class `circle_api` with the following elements 16 | #' - `content` (queried content) 17 | #' - `path` (API request) 18 | #' - `response` (HTTP response information) 19 | #' @export 20 | #' @examples 21 | #' \dontrun{ 22 | #' # get env var 23 | #' get_env_vars() 24 | #' 25 | #' # set env var 26 | #' set_env_var(var = list("foo" = "123")) 27 | #' 28 | #' # delete env var 29 | #' delete_env_var("foo") 30 | #' } 31 | get_env_vars <- function(name = NULL, 32 | repo = github_info()$name, 33 | user = github_info()$owner$login, 34 | vcs_type = "gh", 35 | api_version = "v2") { 36 | 37 | if (!is.null(name)) { # nocov start 38 | req <- circle("GET", 39 | path = sprintf( 40 | "/project/%s/%s/%s/envvar/%s", 41 | vcs_type, user, repo, name 42 | ), 43 | api_version = api_version 44 | ) # nocov end 45 | 46 | } else { 47 | req <- circle("GET", 48 | path = sprintf( 49 | "/project/%s/%s/%s/envvar", 50 | vcs_type, user, repo 51 | ), 52 | api_version = api_version 53 | ) 54 | } 55 | 56 | stop_for_status( 57 | req$response, 58 | sprintf("get env vars for repo %s/%s on Circle CI.", user, repo) 59 | ) 60 | 61 | return(req) 62 | 63 | } 64 | 65 | #' @rdname env_var 66 | #' @export 67 | set_env_var <- function(var, 68 | repo = github_info()$name, 69 | user = github_info()$owner$login, 70 | vcs_type = "gh", 71 | api_version = "v2", 72 | quiet = FALSE) { 73 | 74 | if (length(var) != 1) { 75 | stop("Please supply only one environment variable at a time.") # nocov 76 | } 77 | # format into correct format for "body" part 78 | var <- list( 79 | name = names(var), 80 | value = var[[1]] 81 | ) 82 | 83 | req <- circle("POST", 84 | path = sprintf( 85 | "/project/%s/%s/%s/envvar", 86 | vcs_type, user, repo 87 | ), 88 | api_version = api_version, 89 | body = var, 90 | encode = "json" 91 | ) 92 | 93 | stop_for_status( 94 | req$response, 95 | sprintf("set env vars for repo '%s/%s' on Circle CI", user, repo) 96 | ) 97 | 98 | if (!quiet) { # nocov start 99 | cli_alert_success("Added environment variable {.var {var[['name']]}} for 100 | {.code {user}/{repo}} on Circle CI.", wrap = TRUE) 101 | } # nocov end 102 | 103 | return(invisible(req)) 104 | } 105 | 106 | #' @rdname env_var 107 | #' @export 108 | delete_env_var <- function(var, repo = github_info()$name, 109 | user = github_info()$owner$login, 110 | vcs_type = "gh", 111 | api_version = "v2", 112 | quiet = FALSE) { 113 | 114 | req <- circle("DELETE", 115 | path = sprintf( 116 | "/project/%s/%s/%s/envvar/%s", 117 | vcs_type, user, repo, var 118 | ), 119 | api_version = api_version 120 | ) 121 | 122 | stop_for_status( 123 | req$response, 124 | sprintf("delete env vars for repo '%s/%s' on Circle CI", user, repo) 125 | ) 126 | 127 | if (!quiet) { # nocov start 128 | cli_alert_success("Deleted environment variable {.var {var}} for 129 | {.code {user}/{repo}} on Circle CI.", wrap = TRUE) 130 | } # nocov end 131 | 132 | return(invisible(req)) 133 | 134 | } 135 | -------------------------------------------------------------------------------- /R/format-builds-helpers.R: -------------------------------------------------------------------------------- 1 | new_circle_pipelines <- function(x) { 2 | new_circle_collection( 3 | lapply(x[["items"]], new_circle_pipeline), 4 | circle_attr(x) 5 | ) 6 | } 7 | 8 | new_circle_workflows <- function(x) { 9 | new_circle_collection( 10 | lapply(x[["items"]], new_circle_workflow), 11 | circle_attr(x) 12 | ) 13 | } 14 | 15 | new_circle_jobs <- function(x) { 16 | new_circle_collection( 17 | lapply(x[["items"]], new_circle_job), 18 | circle_attr(x) 19 | ) 20 | } 21 | 22 | # ------------------------------------------------------------------------------ 23 | 24 | new_circle_pipeline <- function(x, attr, subclass) { 25 | new_circle_object(x, "pipeline") 26 | } 27 | 28 | new_circle_workflow <- function(x, attr, subclass) { 29 | new_circle_object(x, "workflow") 30 | } 31 | 32 | new_circle_job <- function(x, attr, subclass) { 33 | new_circle_object(x, "job") 34 | } 35 | 36 | # ------------------------------------------------------------------------------ 37 | 38 | new_circle <- function(x, attr, subclass) { 39 | attr[["names"]] <- names(x) 40 | attributes(x) <- attr 41 | structure(x, class = c(paste0("circle_", subclass), "circle")) 42 | } 43 | 44 | new_circle_object <- function(x, subclass) { 45 | new_circle(circle_no_attr(x), circle_attr(x), subclass) 46 | } 47 | 48 | new_circle_collection <- function(x, attr) { 49 | new_circle(x, attr, "collection") 50 | } 51 | 52 | circle_attr <- function(x) { 53 | is_attr <- grepl("^@", names(x)) 54 | x[is_attr] 55 | } 56 | 57 | circle_no_attr <- function(x) { 58 | is_attr <- grepl("^@", names(x)) 59 | x[!is_attr] 60 | } 61 | 62 | #' @exportS3Method circle::format 63 | format.circle_collection <- function(x, ...) { 64 | if (length(x) == 0) { 65 | return(invisible(x)) # nocov 66 | } else { 67 | if (!class(x[[1]])[1] == "circle_pipeline") { 68 | text <- vapply(seq_along(x), function(y) { 69 | paste0("- id: ", x[[y]]$id, 70 | "\n status: ", x[[y]]$status, 71 | "\n stopped at: ", x[[y]]$stopped_at, "\n", 72 | collapse = "" 73 | ) 74 | }, character(1)) 75 | } else { 76 | text <- vapply(seq_along(x), function(y) { 77 | paste0("- id: ", x[[y]]$id, "\n", 78 | collapse = "" 79 | ) 80 | }, character(1)) 81 | } 82 | 83 | cli::cli_text("") 84 | cli_text("A collection of {length(x)} Circle CI 85 | {strsplit(class(x[[1]]), '_')[[1]][2]}s:\n\n") 86 | # cli_par() 87 | 88 | return(text) 89 | } 90 | } 91 | 92 | #' @exportS3Method circle::format 93 | format.circle_workflow <- function(x, ...) { 94 | 95 | kv <- key_value(shorten(x, n_max = 10)) 96 | 97 | cli_text("A Circle CI workflow:\n\n{bullets(kv)}") 98 | 99 | } 100 | 101 | #' @exportS3Method circle::format 102 | format.circle_job <- function(x, ...) { 103 | 104 | kv <- key_value(shorten(x, n_max = 10)) 105 | 106 | cli_text("A Circle CI job:\n\n{bullets(kv)}") 107 | 108 | } 109 | 110 | # ------------------------------------------------------------------------------ 111 | 112 | bullets <- function(x) { 113 | if (length(x) == 0) { 114 | return(character()) # nocov 115 | } 116 | paste0("- ", x, "\n\n", collapse = "") 117 | } 118 | 119 | shorten <- function(x, n_max = 6) { 120 | 121 | if (length(x) > n_max) { 122 | c(x[seq_len(n_max - 1)], "...") # nocov 123 | } else { 124 | x 125 | } 126 | } 127 | 128 | key_value <- function(x) { 129 | paste0( 130 | ifelse(names(x) == "", "", paste0(names(x), ": ")), 131 | x 132 | ) 133 | } 134 | 135 | # ------------------------------------------------------------------------------ 136 | 137 | #' @export 138 | print.circle_job <- function(x, ...) { 139 | cat(format(x)) 140 | invisible(x) 141 | } 142 | 143 | #' @export 144 | print.circle_workflow <- function(x, ...) { 145 | cat(format(x)) 146 | invisible(x) 147 | } 148 | 149 | #' @export 150 | print.circle_collection <- function(x, ...) { 151 | cat(format(x), sep = "") 152 | 153 | cli::cli_text("") 154 | cli::cli_alert_info("To view the details for a single item, print the 155 | returned list item(s), e.g. {.code get_jobs()[[2]]} would show the 156 | information for the second last job.", wrap = TRUE) 157 | invisible(x) 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![tic](https://github.com/ropensci/circle/workflows/tic/badge.svg)](https://github.com/ropensci/circle/actions) 4 | [![CircleCI](https://img.shields.io/circleci/build/gh/ropensci/circle/main?label=Linux&logo=circle&logoColor=green&style=flat-square)](https://app.circleci.com/pipelines/github/ropensci/circle) 5 | [![CRAN Status](https://www.r-pkg.org/badges/version-ago/circle)](https://cran.r-project.org/package=circle) 6 | [![Lifecycle:maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://lifecycle.r-lib.org/articles/stages.html) 7 | [![](https://badges.ropensci.org/356_status.svg)](https://github.com/ropensci/software-review/issues/356) 8 | 9 | 10 | 11 | # circle 12 | 13 | R client package for the Continuous Integration (CI) provider 'Circle CI'. 14 | [Circle CI](https://circleci.com/) stands in line with [GitHub Actions](https://github.com/features/actions), [Travis CI](https://www.travis-ci.com/), [AppVeyor](https://ci.appveyor.com/login) and many more CI providers. 15 | [Circle CI](https://circleci.com/) heavily relies on Docker containers for runner execution. 16 | 17 | Continuous Integration (CI) / Continuous Deployment (CD) is heavily used in the IT world to automatically perform certain actions after a specific trigger (e.g. after each commit). 18 | When developing R packages the most common uses cases are to check the package on each commit for CRAN eligibility (by running `R CMD Check`) and to deploy a [{pkgdown}](https://github.com/r-lib/pkgdown) documentation page for the package. 19 | 20 | This package aims help to set up CI/CD with the service provider [Circle CI](https://circleci.com/) and provides R functions to execute CI specific tasks such as build restarts, log queries or setting environment variables from within R. 21 | It also simplifies the setup process for build deployments via [`use_circle_deploy()`](https://docs.ropensci.org/circle/reference/use_circle_deploy.html). 22 | All functionality relies on calls to the [Circle CI REST API](https://circleci.com/docs/api/v2/#circleci-api). 23 | 24 | There are two ways to use this package: 25 | 26 | - Via the high-level functions of this package which wrap common API calls: 27 | - `get_pipelines()` 28 | - `get_checkout_keys()` 29 | - `set_env_var()` 30 | - etc. 31 | - Via direct API calls through the workhorse function [`circle()`](https://docs.ropensci.org/circle/reference/circle.html). 32 | 33 | {circle} does not come with an option to setup Circle CI YAML files. 34 | Please see the related [{tic}](https://github.com/ropensci/tic) package for such functionality and more CI workflow related tools. 35 | {circle} aims to provide a handy and flexible high-level interface to the [Circle CI API](https://circleci.com/docs/api/v2/) without shipping opinionated workflow functionality. 36 | 37 | ## API versions 38 | 39 | All functionality uses the Circle CI [API v2](https://github.com/CircleCI-Archived/api-preview-docs) which follows the **pipelines** -> **workflows** -> **jobs** approach. 40 | This API version is still in beta and might undergo some changes in the near future. 41 | 42 | Some functions/endpoints can also be used via API versions v1.1 and v1 by setting the `api_version` argument. 43 | However, this will only work if the respective API endpoint is available for the chosen API version. 44 | Usually, there should be no need in practice to fall back to API version < 2. 45 | 46 | For more information on the differences between the [Circle CI API](https://circleci.com/docs/api/v2/) versions, have a look at the [document explaining changes between v1.1 and v2](https://github.com/CircleCI-Archived/api-preview-docs/blob/master/docs/api-changes.md). 47 | 48 | ## Installation 49 | 50 | Development Version: 51 | 52 | ```r 53 | remotes::install_github("ropensci/circle") 54 | ``` 55 | 56 | ## Get Started 57 | 58 | See the [Getting Started](https://ropensci.github.io/circle/articles/circle.html) vignette for an introduction. 59 | 60 | ## Note to Developers 61 | 62 | This packages relies on private API keys for local testing. 63 | See [CONTRIBUTING.md#testing-the-package](https://github.com/ropensci/circle/blob/main/.github/CONTRIBUTING.md#testing-the-package) for detailed instructions. 64 | 65 | # Acknowledgments 66 | 67 | This package was inspired by the work of [Thomas J. Leeper](https://github.com/leeper) on the (discontinued) [cloudyr/circleci](https://github.com/cloudyr/circleci) package and by the (archived) [ropenscilabs/travis](https://github.com/ropensci-archive/travis) package. 68 | -------------------------------------------------------------------------------- /tests/fixtures/delete_checkout_key().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: get 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/checkout-key?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | access-control-allow-origin: '*' 19 | content-encoding: gzip 20 | content-type: application/json;charset=utf-8 21 | date: Tue, 03 Nov 2020 11:39:40 GMT 22 | server: nginx 23 | set-cookie: ring-session=D%2F9aUJQORAVqh%2FY9Ilj1jrPRhJSxB%2FvV%2BlPtKdyCz%2BFMuBWwL2EvsKFySjid2GjSXz1WE6lqMzC5GMDI0dFY%2BQXEVAUIeI2fjHUdPGqbUJOLMl2FYLnQpToSKmvhyAru4UfSNcXhOCsSlZbaCyFDpeXozmEjotxO%2F4ioHkWkrf%2FZjcq7KacqMxCBZwp5wdM0deM%2Bpzvl90kd6xRVJ%2ByQDh1eBl8T7aHy%2Bm0%2Bu9WfDv0%3D--kmw65ESJ94hd6X7YMtb8YvPKx9mpXuY7tDYg2mGvcPo%3D;Path=/;HttpOnly;Expires=Wed, 24 | 03 Nov 2021 10:09:07 +0000;Max-Age=1209600;Secure 25 | strict-transport-security: max-age=15724800 26 | vary: Origin 27 | x-circleci-identity: circle-www-api-v1-777c756979-t92zf 28 | x-frame-options: DENY 29 | x-ratelimit-limit: '151' 30 | x-ratelimit-remaining: '150' 31 | x-ratelimit-reset: '0' 32 | x-request-id: 06768c72-169d-4774-9657-46ef744a6fd1 33 | x-route: /api/v2/project/:project-slug/checkout-key 34 | content-length: '829' 35 | connection: keep-alive 36 | body: 37 | encoding: UTF-8 38 | file: no 39 | string: '{"next_page_token":null,"items":[{"type":"github-user-key","preferred":true,"created_at":"2020-11-03T11:39:40.233Z","public_key":"ssh-rsa 40 | AAAAB3NzaC1yc2EAAAADAQABAAABAQCzwHNSKi2R+vZ7t4luyGbo36pCZSU/eV9pV368Gx+p2uuJo/jt4lRyXhXvmoIxdBec+yGPzZKq9ktKDqRPFV3DIdWyCL0vCq1AbPXuE2ldL+xgurqIW6bb/WPzY2rhG/LFB0qpyls5RXLbYaHzaHhydTv0ZltlpQ5lBMUBt0jfBc+nbinwZtJw0sD6rtGize7ngt1s+VbmixkpVaTgd3vjibzcKKwKvrFlBQfgcPKbZdLR/fzU+ssdIYQuq3OYMGeU2GiWkaBR6h8ehXwvhSmI2aQCxo8Br+fFDMyCrEYgOzaakvzkqdTcbEfxfyiql/T0x8e7WNT/8b4toX9TanKD 41 | \n","fingerprint":"d3:28:47:34:73:54:db:88:58:ba:16:65:a5:03:79:4f"},{"type":"github-user-key","preferred":false,"created_at":"2020-10-08T08:13:39.632Z","public_key":"ssh-rsa 42 | AAAAB3NzaC1yc2EAAAADAQABAAABAQChH8hA0L+vEmFxCyTwFii98G0HzfntiD9WelvOSnqopjPNtvp4f7nT4uXwr7PNLitbMIYMFxJyse8U4spAlSnFCHhYcf2ckA/nNX21CzSigGL6TOUcctIJcuwlbFMYfcTUipHUyWEz2lnGoVe3udEZL9JCWJfJdi/afZhjQDfhOH/Gmy/F3HQ6LlXgn5EHoExsOKRERQteAebl8B1i6mulGYqSjj4JBE5UWyz5w74V6l9QjUO5xngVZ0KtVu5bhmzwsC9OIQK9BjBZ4imicFQMi2FtgXeiogN5h9BVPVbfsh+c9TvpbzxaODJLOGJ7liCMq2o0ICioCCXUYUJQH8yh 43 | \n","fingerprint":"4e:50:77:79:59:1a:ef:27:41:3f:b4:2f:61:74:ca:d9"}]}' 44 | recorded_at: 2020-11-03 11:39:40 GMT 45 | recorded_with: vcr/0.5.4, webmockr/0.7.0 46 | - request: 47 | method: delete 48 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/checkout-key/d3:28:47:34:73:54:db:88:58:ba:16:65:a5:03:79:4f?circle-token=<<>> 49 | body: 50 | encoding: '' 51 | string: '' 52 | headers: 53 | Content-Type: '' 54 | Accept: application/json 55 | response: 56 | status: 57 | status_code: 200 58 | category: Success 59 | reason: OK 60 | message: 'Success: (200) OK' 61 | headers: 62 | content-encoding: gzip 63 | content-type: application/json;charset=utf-8 64 | date: Tue, 03 Nov 2020 11:39:40 GMT 65 | server: nginx 66 | set-cookie: ring-session=HEInWGciPtGPQ%2B1oyLpzwVYS9tT9%2BtyLKtFGBn2Uy%2Ffwcb132c%2FRzM1W%2FdZrBR5hUMUmc3oiSASh8Ac%2F68%2BZxJqtjnOR0oUBBtPRdpbRtXml8m0dJYHbWvOBSm4bR6aKaFyrfVc3dUIpcdLndJ0wQH%2FzzzVXQU8MblYfLN79RdtmJcloTw0yAHuYeoq2CoFQS8A%2BbKcGwLIcdD2BMWba3GnWplK4CTMzs9MacYn9yrg%3D--xISbWdVDYok%2BywUzj8Vt91PtNCAfCuSqNwby8R%2F2rQY%3D;Path=/;HttpOnly;Expires=Tue, 67 | 02 Nov 2021 15:14:37 +0000;Max-Age=1209600;Secure 68 | strict-transport-security: max-age=15724800 69 | x-circleci-identity: circle-www-api-v1-777c756979-r9t9h 70 | x-frame-options: DENY 71 | x-ratelimit-limit: '151' 72 | x-ratelimit-remaining: '150' 73 | x-ratelimit-reset: '0' 74 | x-request-id: 72a32f09-8d92-4876-a6d9-a0837859599a 75 | x-route: /api/v2/project/:project-slug/checkout-key/:fingerprint 76 | content-length: '55' 77 | connection: keep-alive 78 | body: 79 | encoding: UTF-8 80 | file: no 81 | string: '{"message":"Checkout key deleted."}' 82 | recorded_at: 2020-11-03 11:39:40 GMT 83 | recorded_with: vcr/0.5.4, webmockr/0.7.0 84 | -------------------------------------------------------------------------------- /.github/workflows/tic.yml: -------------------------------------------------------------------------------- 1 | ## tic GitHub Actions template: linux-macos-windows-deploy 2 | ## revision date: 2023-12-15 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | - cran-* 10 | pull_request: 11 | branches: 12 | - main 13 | - master 14 | # for now, CRON jobs only run on the default branch of the repo (i.e. usually on master) 15 | #schedule: 16 | # * is a special character in YAML so you have to quote this string 17 | #- cron: "0 4 * * *" 18 | 19 | name: tic 20 | 21 | jobs: 22 | all: 23 | runs-on: ${{ matrix.config.os }} 24 | 25 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | config: 31 | # use a different tic template type if you do not want to build on all listed platforms 32 | - { os: windows-latest, r: "release" } 33 | - { os: macOS-latest, r: "release", pkgdown: "true", latex: "true" } 34 | - { os: ubuntu-latest, r: "devel" } 35 | - { os: ubuntu-latest, r: "release" } 36 | 37 | env: 38 | # make sure to run `tic::use_ghactions_deploy()` to set up deployment 39 | TIC_DEPLOY_KEY: ${{ secrets.TIC_DEPLOY_KEY }} 40 | # prevent rgl issues because no X11 display is available 41 | RGL_USE_NULL: true 42 | # if you use bookdown or blogdown, replace "PKGDOWN" by the respective 43 | # capitalized term. This also might need to be done in tic.R 44 | BUILD_PKGDOWN: ${{ matrix.config.pkgdown }} 45 | # use GITHUB_TOKEN from GitHub to workaround rate limits in {remotes} 46 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 47 | # [Custom env var] Circle CI API key 48 | R_CIRCLE: ${{ secrets.R_CIRCLE }} 49 | # [Custom env var] Circle CI API key 50 | CIRCLE_CHECKOUT_KEY: ${{ secrets.CIRCLE_CHECKOUT_KEY }} 51 | # [Custom env var] Circle CI GITHUB PAT 52 | CIRCLE_R_PACKAGE_GITHUB_PAT: ${{ secrets.CIRCLE_R_PACKAGE_GITHUB_PAT }} 53 | 54 | steps: 55 | - uses: actions/checkout@v5 56 | 57 | - uses: r-lib/actions/setup-r@v2 58 | with: 59 | r-version: ${{ matrix.config.r }} 60 | Ncpus: 4 61 | 62 | - uses: r-lib/actions/setup-tinytex@v2 63 | if: matrix.config.latex == 'true' 64 | 65 | - uses: r-lib/actions/setup-pandoc@v2 66 | 67 | - name: Install sys deps for Ubuntu 68 | if: runner.os == 'Linux' 69 | run: sudo apt update && sudo apt install -y libgit2-dev libcurl4-openssl-dev 70 | 71 | # set date/week for use in cache creation 72 | # https://github.community/t5/GitHub-Actions/How-to-set-and-access-a-Workflow-variable/m-p/42970 73 | # - cache R packages daily 74 | - name: "[Cache] Prepare daily timestamp for cache" 75 | if: runner.os != 'Windows' 76 | id: date 77 | run: echo "date=$(date '+%d-%m')" >> $GITHUB_OUTPUT 78 | 79 | - name: "[Cache] Restore R package cache" 80 | if: runner.os != 'Windows' 81 | uses: actions/cache/restore@v4 82 | with: 83 | path: ${{ env.R_LIBS_USER }} 84 | key: ${{ runner.os }}-r-${{ matrix.config.r }}-${{steps.date.outputs.date}} 85 | 86 | - name: "[Stage] Install" 87 | run: Rscript -e "install.packages('tic', repos = c('https://ropensci.r-universe.dev', if (grepl('Ubuntu', Sys.info()[['version']])) {sprintf('https://packagemanager.rstudio.com/all/__linux__/%s/latest', system('lsb_release -cs', intern = TRUE))} else {'https://cloud.r-project.org'}))" -e "print(tic::dsl_load())" -e "tic::prepare_all_stages()" -e "tic::before_install()" -e "tic::install()" 88 | 89 | - name: "[Cache] Save R package cache" 90 | if: runner.os != 'Windows' && always() 91 | uses: actions/cache/save@v4 92 | with: 93 | path: ${{ env.R_LIBS_USER }} 94 | key: ${{ runner.os }}-r-${{ matrix.config.r }}-${{steps.date.outputs.date}} 95 | 96 | - name: "[Stage] Script" 97 | run: Rscript -e 'tic::script()' 98 | 99 | - name: "[Stage] After Success" 100 | run: Rscript -e "tic::after_success()" 101 | 102 | - name: "[Stage] Upload R CMD check artifacts" 103 | if: failure() 104 | uses: actions/upload-artifact@v4 105 | with: 106 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 107 | path: check 108 | - name: "[Stage] Before Deploy" 109 | run: | 110 | Rscript -e "tic::before_deploy()" 111 | 112 | - name: "[Stage] Deploy" 113 | run: Rscript -e "tic::deploy()" 114 | 115 | - name: "[Stage] After Deploy" 116 | run: Rscript -e "tic::after_deploy()" 117 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | ## tic Circle CI template: linux-matrix-deploy 2 | ## revision date: 2022-08-28 3 | version: 2.1 4 | jobs: 5 | r-release: 6 | # r-release-env 7 | environment: 8 | docker: 9 | - image: rocker/verse 10 | steps: 11 | - checkout 12 | 13 | # create a unique env var for the cache. Unfortunately normal env vars 14 | # are not picked up by the cache, therefore this workaround is needed. 15 | # See https://discuss.circleci.com/t/cannot-use-circle-yml-environment-variables-in-cache-keys/10994/7 16 | - run: echo "$(date '+%d-%m')-r-release" > /tmp/_tmp_file 17 | - restore_cache: 18 | key: R-package-library-{{ checksum "/tmp/_tmp_file" }} 19 | 20 | # install deps and check pkg --------------------------------------------- 21 | - run: 22 | name: "[r-release] Install dependencies" 23 | command: | 24 | sudo apt update && sudo apt install -y pandoc 25 | R -q -e "install.packages('tic', repos = c('https://ropensci.r-universe.dev', if (grepl('Ubuntu', Sys.info()[['version']])) {sprintf('https://packagemanager.rstudio.com/all/__linux__/%s/latest', system('lsb_release -cs', intern = TRUE))} else {'https://cloud.r-project.org'}))" -e "print(tic::dsl_load())" -e "print(tic::dsl_load())" -e "tic::prepare_all_stages()" -e "tic::before_install()" -e "tic::install()" 26 | 27 | - run: 28 | name: "[r-release] R CMD Check" 29 | no_output_timeout: 60m 30 | command: | 31 | R -q -e 'tic::before_script()' 32 | R -q -e 'tic::script()' 33 | 34 | # save R pkg cache ------------------------------------------------------- 35 | - save_cache: 36 | key: R-package-library-{{ checksum "/tmp/_tmp_file" }} 37 | paths: 38 | - /usr/local/lib/R/site-library 39 | deploy: 40 | # r-deploy-env 41 | environment: 42 | # if you use bookdown or blogdown, replace "PKGDOWN" by the respective 43 | # capitalized term. This also might need to be done in tic.R 44 | BUILD_PKGDOWN: true 45 | docker: 46 | - image: rocker/verse 47 | steps: 48 | - checkout 49 | 50 | - run: echo "$(date '+%d-%m')-r-release" > /tmp/_tmp_file 51 | - restore_cache: 52 | key: R-package-library-{{ checksum "/tmp/_tmp_file" }} 53 | 54 | - run: 55 | name: Deploy 56 | command: | 57 | sudo apt update && sudo apt install -y pandoc 58 | R -q -e "install.packages('tic', repos = c('https://ropensci.r-universe.dev', if (grepl('Ubuntu', Sys.info()[['version']])) {sprintf('https://packagemanager.rstudio.com/all/__linux__/%s/latest', system('lsb_release -cs', intern = TRUE))} else {'https://cloud.r-project.org'}))" -e "print(tic::dsl_load())" -e "print(tic::dsl_load())" -e "tic::prepare_all_stages()" -e "tic::before_install()" -e "tic::install()" 59 | R -q -e 'tic::before_deploy()' 60 | R -q -e 'tic::deploy()' 61 | R -q -e 'tic::after_deploy()' 62 | 63 | r-devel: 64 | # r-devel-env 65 | environment: 66 | docker: 67 | - image: rocker/verse:devel 68 | steps: 69 | - checkout 70 | 71 | # create a unique env var for the cache. Unfortunately normal env vars 72 | # are not picked up by the cache, therefore this workaround is needed. 73 | # See https://discuss.circleci.com/t/cannot-use-circle-yml-environment-variables-in-cache-keys/10994/7 74 | - run: echo "$(date '+%d-%m')-r-devel" > /tmp/_tmp_file 75 | - restore_cache: 76 | key: R-package-library-{{ checksum "/tmp/_tmp_file" }} 77 | 78 | # install deps and check pkg --------------------------------------------- 79 | - run: 80 | name: "[r-devel] Install dependencies" 81 | command: | 82 | sudo apt update && sudo apt install -y pandoc 83 | R -q -e "install.packages('tic', repos = c('https://ropensci.r-universe.dev', if (grepl('Ubuntu', Sys.info()[['version']])) {sprintf('https://packagemanager.rstudio.com/all/__linux__/%s/latest', system('lsb_release -cs', intern = TRUE))} else {'https://cloud.r-project.org'}))" -e "print(tic::dsl_load())" -e "print(tic::dsl_load())" -e "tic::prepare_all_stages()" -e "tic::before_install()" -e "tic::install()" 84 | 85 | - run: 86 | name: "[r-devel] R CMD Check" 87 | no_output_timeout: 60m 88 | command: | 89 | R -q -e 'tic::before_script()' 90 | R -q -e 'tic::script()' 91 | 92 | # save R pkg cache ------------------------------------------------------- 93 | - save_cache: 94 | key: R-package-library-{{ checksum "/tmp/_tmp_file" }} 95 | paths: 96 | - /usr/local/lib/R/site-library 97 | 98 | workflows: 99 | build-and-deploy: 100 | jobs: 101 | - r-release 102 | - r-devel 103 | - deploy: 104 | requires: 105 | - r-release 106 | 107 | # CRON job daily at 4 am in the morning 108 | # - runs the "build" job on the master branch and builds package cache 109 | nightly: 110 | triggers: 111 | - schedule: 112 | cron: "0 4 * * *" 113 | filters: 114 | branches: 115 | only: 116 | - master 117 | - main 118 | - cran-* 119 | jobs: 120 | - r-release 121 | - r-devel 122 | -------------------------------------------------------------------------------- /R/builds.R: -------------------------------------------------------------------------------- 1 | #' @title Retrieve Metadata from Circle CI Builds 2 | #' @description Query information about pipelines, workflows or jobs on 3 | #' Circle CI. 4 | #' The S3 `print()` method for these functions returns the respective 5 | #' pipeline IDs. 6 | #' To inspect the details of each pipeline, save the return value in an object 7 | #' and inspect the respective sub-lists. 8 | #' 9 | #' If no pipeline or workflow is supplied to `get_workflows()`/`get_jobs()`, 10 | #' the ten most recent pipelines/jobs are queried, respectively. 11 | #' 12 | #' @details While the `get_*()` functions query information about the respective 13 | #' build level details (pipeline - workflow - job), `retry_workflow()` let's 14 | #' users rerun a specific workflow. 15 | #' By default, the workflow from the most recent pipeline will be rerun if 16 | #' no pipeline ID was supplied. 17 | #' 18 | #' @template repo 19 | #' @template user 20 | #' @template vcs 21 | #' @param limit `[integer]`\cr 22 | #' How many builds should be returned? Maximum allowed by Circle is 23 | #' 30. 24 | #' @template api_version 25 | #' @return An object of class `circle_collection` containing list 26 | #' information on the queried Circle CI pipelines/workflows/jobs. 27 | #' 28 | #' @name builds 29 | #' @export 30 | #' @examples 31 | #' \dontrun{ 32 | #' pipelines <- get_pipelines() 33 | #' 34 | #' workflows <- get_workflows() 35 | #' 36 | #' jobs <- get_jobs() 37 | #' 38 | #' # rerun most recent workflow 39 | #' retry_workflow() 40 | #' } 41 | get_pipelines <- function(repo = github_info()$name, 42 | user = github_info()$owner$login, 43 | limit = 30, 44 | vcs_type = "gh", 45 | api_version = "v2") { 46 | 47 | if (is.null(repo)) { 48 | repo <- github_info()$name # nocov 49 | } 50 | resp <- circle("GET", 51 | path = sprintf( 52 | "/project/%s/%s/%s/pipeline", 53 | vcs_type, user, repo 54 | ), 55 | api_version = api_version, 56 | query = list(limit = limit) 57 | ) 58 | 59 | stop_for_status( 60 | resp$response, 61 | sprintf("get pipelines for repo %s/%s on Circle CI", user, repo) 62 | ) 63 | 64 | return(new_circle_pipelines(content(resp$response))) 65 | } 66 | 67 | #' @param pipeline_id `[character]`\cr 68 | #' A Circle CI pipeline ID. 69 | #' @rdname builds 70 | #' @export 71 | get_workflows <- function(pipeline_id = NULL, 72 | repo = github_info()$name, 73 | user = github_info()$owner$login) { 74 | 75 | if (is.null(pipeline_id)) { 76 | cli::cli_ul("{.fun get_pipelines}: ID not given, querying {.field 10} most recent 77 | pipelines.") 78 | 79 | pipeline_id <- vapply(seq_along(1:10), function(x) { 80 | get_pipelines(repo = repo, user = user)[[x]]$id 81 | }, FUN.VALUE = character(1)) 82 | } 83 | 84 | resp <- lapply(pipeline_id, function(x) { 85 | resp <- circle("GET", 86 | path = sprintf( 87 | "/pipeline/%s/workflow", 88 | x 89 | ), 90 | api_version = "v2" 91 | ) 92 | stop_for_status( 93 | resp$response, 94 | sprintf( 95 | "get workflows for pipeline '%s' for repo %s/%s on Circle CI.", 96 | x, user, repo 97 | ) 98 | ) 99 | new_circle_workflows(content(resp$response)) 100 | }) 101 | 102 | resp <- unlist(resp, recursive = FALSE) 103 | class(resp) <- c("circle_collection", "circle") 104 | return(resp) 105 | } 106 | 107 | #' @param workflow_id `[character]`\cr 108 | #' A Circle CI workflow ID. 109 | #' @rdname builds 110 | #' @export 111 | get_jobs <- function(workflow_id = NULL, 112 | repo = github_info()$name, 113 | user = github_info()$owner$login, 114 | vcs_type = "gh") { 115 | 116 | if (is.null(workflow_id)) { 117 | cli::cli_ul("{.fun get_workflows}: ID not given, querying {.field 10} most 118 | recent workflows.") 119 | 120 | workflow_id <- get_workflows(repo = repo, user = user) 121 | workflow_id <- vapply(workflow_id, function(x) x$id, 122 | FUN.VALUE = character(1) 123 | ) 124 | } 125 | 126 | resp <- lapply(workflow_id, function(x) { 127 | resp <- circle("GET", 128 | path = sprintf( 129 | "/workflow/%s/job", 130 | x 131 | ), 132 | api_version = "v2" 133 | ) 134 | stop_for_status( 135 | resp$response, 136 | sprintf( 137 | "get jobs for workflow '%s' for repo %s/%s on Circle CI.", 138 | x, user, repo 139 | ) 140 | ) 141 | new_circle_jobs(content(resp$response)) 142 | }) 143 | 144 | resp <- unlist(resp, recursive = FALSE) 145 | class(resp) <- c("circle_collection", "circle") 146 | return(resp) 147 | } 148 | 149 | #' @param workflow_id `[character]`\cr 150 | #' A Circle CI workflow ID. 151 | #' @rdname builds 152 | #' @export 153 | retry_workflow <- function(workflow_id = NULL) { 154 | 155 | if (is.null(workflow_id)) { # nocov start 156 | cli::cli_ul("{.fun retry_workflow}: ID not given, getting the last workflow.") 157 | 158 | workflow_id <- get_workflows( 159 | repo = github_info()$name, 160 | user = github_info()$owner$login 161 | )[[1]]$id 162 | } # nocov end 163 | 164 | resp <- circle("POST", 165 | path = sprintf( 166 | "/workflow/%s/rerun", 167 | workflow_id 168 | ) 169 | ) 170 | stop_for_status( 171 | resp$response, 172 | sprintf("restarting workflow '%s' on Circle CI", workflow_id) 173 | ) 174 | 175 | return(resp) 176 | } 177 | -------------------------------------------------------------------------------- /R/general.R: -------------------------------------------------------------------------------- 1 | #' @title Get Circle CI user 2 | #' @description Retrieve details about the authenticated Circle CI user. 3 | #' @return A named vector of class `circle_user` containing information about 4 | #' about the authenticated user: 5 | #' 6 | #' - Full name 7 | #' - Username 8 | #' - ID 9 | #' - API endpoint 10 | #' @examples 11 | #' \dontrun{ 12 | #' get_circle_user() 13 | #' } 14 | #' @export 15 | get_circle_user <- function() { 16 | # GET: /me 17 | # Provides information about the signed in user. 18 | resp <- circle("GET", path = "/me") 19 | out <- structure(resp, class = "circle_user") 20 | return(out) 21 | } 22 | 23 | #' @title List Circle CI Projects 24 | #' @description Retrieve a list of Circle CI repositories for the authenticated 25 | #' user. 26 | #' @template repo 27 | #' @template user 28 | #' @details Retrieves a very detailed list of repository and repo-related 29 | #' information for all Circle CI repository attached to the current user. 30 | #' 31 | #' This endpoint uses API v1.1 and will probably be removed in the near 32 | #' future. 33 | #' @return An object of class `circle_api` with the following elements 34 | #' - `content` (queried content) 35 | #' - `path` (API request) 36 | #' - `response` (HTTP response information) 37 | #' @seealso [get_pipelines()], [get_workflows()], [get_jobs()] 38 | #' @examples 39 | #' \dontrun{ 40 | #' list_projects() 41 | #' } 42 | #' @export 43 | list_projects <- function(repo = github_info()$name, 44 | user = github_info()$owner$login) { 45 | 46 | # GET: /repos List of all the repos you're following on CircleCI, with build 47 | # information organized by branch. 48 | resp <- circle("GET", path = "/projects", api_version = "v1.1") 49 | 50 | stop_for_status( 51 | resp$response, 52 | sprintf("get projects for repo '%s/%s' on Circle CI.", user, repo) 53 | ) 54 | 55 | return(resp) 56 | } 57 | 58 | #' @title Get Build Artifacts of a Specific Job 59 | #' @description Retrieve artifacts from a specific build. 60 | #' @param job_id `[character]`\cr 61 | #' A Circle CI job id. 62 | #' @template repo 63 | #' @template user 64 | #' @template vcs 65 | #' @template api_version 66 | #' @return An object of class `circle_api` with the following elements 67 | #' - `content` (queried content) 68 | #' - `path` (API request) 69 | #' - `response` (HTTP response information) 70 | #' @examples 71 | #' \dontrun{ 72 | #' job_id <- get_jobs()[[1]]$id 73 | #' get_build_artifacts(job_id) 74 | #' } 75 | #' @export 76 | get_build_artifacts <- function(job_id = NULL, 77 | repo = github_info()$name, 78 | user = github_info()$owner$login, 79 | vcs_type = "gh", 80 | api_version = "v2") { 81 | if (is.null(job_id)) { 82 | job_id <- get_jobs(repo = repo, user = user)[[1]]$job_number 83 | } 84 | 85 | resp <- circle("GET", path = sprintf( 86 | "/project/%s/%s/%s/%s/artifacts", 87 | vcs_type, user, repo, job_id 88 | ), api_version = api_version) 89 | 90 | stop_for_status( 91 | resp$response, 92 | sprintf("getting build artifacts for job '%s' on Circle CI", job_id) 93 | ) 94 | 95 | return(resp) 96 | } 97 | 98 | #' @title Trigger a New Build on Circle CI 99 | #' @description Triggers a new build for a specific repo branch. 100 | #' @details Trigger a new Circle CI build for a specific repo branch. 101 | #' @template repo 102 | #' @template user 103 | #' @template vcs 104 | #' @template quiet 105 | #' @return An object of class `circle_api` with the following elements 106 | #' - `content` (queried content) 107 | #' - `path` (API request) 108 | #' - `response` (HTTP response information) 109 | #' @param branch A character string specifying the repository branch. 110 | #' @seealso [retry_workflow()] 111 | #' @examples 112 | #' \dontrun{ 113 | #' new_build() 114 | #' } 115 | #' @export 116 | new_build <- function(repo = github_info()$name, 117 | user = github_info()$owner$login, 118 | vcs_type = "gh", 119 | branch = "master", 120 | quiet = FALSE) { 121 | 122 | resp <- circle("POST", 123 | path = sprintf( 124 | "/project/%s/%s/%s/pipeline", 125 | vcs_type, 126 | user, 127 | repo 128 | ), 129 | body = list(branch = branch) 130 | ) 131 | 132 | stop_for_status( 133 | resp$response, 134 | sprintf("start a new build for repo %s/%s on Circle CI", user, repo) 135 | ) 136 | 137 | if (!quiet) { # nocov start 138 | cli_alert_success("Successfully started a new build for project 139 | {.field {user}/{repo}}.", wrap = TRUE) 140 | } # nocov end 141 | 142 | return(resp) 143 | } 144 | 145 | #' @title Enable a repo on Circle CI 146 | #' @description "Follows" a repo on Circle CI so that builds can be triggered. 147 | #' @importFrom cli cli_text 148 | #' @template repo 149 | #' @template user 150 | #' @template vcs 151 | #' @template api_version 152 | #' @template quiet 153 | #' @return An object of class `circle_api` with the following elements 154 | #' - `content` (queried content) 155 | #' - `path` (API request) 156 | #' - `response` (HTTP response information) 157 | #' @examples 158 | #' \dontrun{ 159 | #' enable_repo() 160 | #' } 161 | #' @export 162 | enable_repo <- function(repo = github_info()$name, 163 | user = github_info()$owner$login, 164 | vcs_type = "gh", 165 | api_version = "v1.1", 166 | quiet = FALSE) { 167 | 168 | resp <- circle("POST", 169 | path = sprintf( 170 | "/project/%s/%s/%s/follow", 171 | vcs_type, 172 | user, 173 | repo 174 | ), 175 | api_version = api_version 176 | ) 177 | 178 | stop_for_status( 179 | resp$response, 180 | sprintf("enable repo %s on Circle CI", repo) 181 | ) 182 | 183 | if (!quiet) { # nocov start 184 | cli_alert_success("Successfully enabled repo '{user}/{repo}' on Circle CI.", 185 | wrap = TRUE 186 | ) 187 | } # nocov end 188 | 189 | return(invisible(resp)) 190 | } 191 | -------------------------------------------------------------------------------- /vignettes/tic.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Using {circle} with {tic}" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{tic} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | This vignette explains how {circle} can be used in conjunction with {tic} to set up a working CI environment to check an R package and build a pkgdown site. 18 | 19 | All following points assume you are in the project root of the R package. 20 | 21 | ## Enabling the repository on Circle CI 22 | 23 | The first step is to enable/register your repository on Circle CI. 24 | To do this, `enable_repo()` can be called. 25 | Assuming you already have an account on Circle CI (authenticating with GitHub is recommended), this "follows" your repository on Circle CI so that builds can be triggered by pushes to the repository. 26 | 27 | ## Creating the Circle CI YAML configuration file 28 | 29 | Next, a YAML file (`.circleci/config.yml`) needs to be created which lists the tasks which should be executed on Circle CI after a commit to the repository. 30 | 31 | This is where the [ropensci/tic](https://github.com/ropensci/tic) package comes into play which provides YAML templates for Circle CI. 32 | There are two ways to get the template via {tic}: 33 | 34 | - by going through the chatty `tic::use_tic()` wizard which asks some questions related to configuration and then writes/initiates multiple CI providers (based on the choices made). 35 | This is a good choice if you want to understand in greater detail what {tic} is doing. 36 | - by calling `tic::use_circle_yml()` which (by default) writes a Circle CI configuration file that checks the R package via {rcmdcheck} and deploys a {pkgdown} site to the `gh-pages` branch of the repository. 37 | 38 | In addition some files will be added to `.Rbuildignore` and `.gitignore`. 39 | Also the CI-agnostic `tic.R` file will be created which lists the steps/macros that will be executed in a domain-specific language syntax. 40 | Please have a look at [the introduction vignette of the tic package](https://docs.ropensci.org/tic/articles/tic.html) to understand the role of `tic.R` in more detail. 41 | 42 | ## Enabling deployment from builds 43 | 44 | To be able to push to the GitHub repository some setup work is required. 45 | Deployment is often done by creating a SSH key pair of which one part is stored on Circle CI and the other one on GitHub. 46 | To prevent having to add the SSH key parts to Circle CI and GitHub manually, `use_circle_deploy()` can be called to do all of this programmatically. 47 | See [the section on "Deployment" in the "Getting Started" vignette](https://docs.ropensci.org/circle/articles/circle.html#deployment-1) for more details on this process. 48 | 49 | ## Understanding the YAML file 50 | 51 | The config file of this repo at [.circleci/config.yml](https://github.com/ropensci/circle/blob/main/.circleci/config.yml) has also been set up with {tic}. 52 | 53 | Let's walk through it step by step to understand what is happening: 54 | 55 | ```yml 56 | jobs: 57 | r-release: 58 | # r-release-env 59 | environment: 60 | docker: 61 | - image: rocker/verse 62 | steps: 63 | - checkout 64 | ``` 65 | 66 | In this part we specify to use the [rocker/verse](https://hub.docker.com/r/rocker/verse) docker image as the base for the job. 67 | The first step is "checkout" which means the repository is cloned. 68 | 69 | --- 70 | 71 | ```yml 72 | # create a unique env var for the cache. Unfortunately normal env vars 73 | # are not picked up by the cache, therefore this workaround is needed. 74 | # See https://discuss.circleci.com/t/cannot-use-circle-yml-environment-variables-in-cache-keys/10994/7 75 | - run: echo "$(date '+%d-%m')-r-release" > /tmp/_tmp_file 76 | - restore_cache: 77 | key: R-package-library-{{ checksum "/tmp/_tmp_file" }} 78 | ``` 79 | 80 | Next, an action related to caching R packages is initiated. 81 | This saves some time in the future because once all R packages which the package needs have been installed once for a given day, they will be re-used in future builds of the day without having to be installed again. 82 | 83 | --- 84 | 85 | ```yml 86 | # install deps and check pkg --------------------------------------------- 87 | - run: 88 | name: "[r-release] Install dependencies" 89 | command: | 90 | sudo apt update && sudo apt install -y ccache libgit2-dev libharfbuzz-dev libfribidi-dev 91 | echo -e 'options(Ncpus = 4, repos = structure(c(CRAN = "https://cloud.r-project.org/")))' > $HOME/.Rprofile 92 | R -q -e 'install.packages("remotes")' 93 | R -q -e 'if (getRversion() < "3.2" && !requireNamespace("curl")) install.packages("curl")' 94 | R -q -e 'remotes::install_github("ropensci/tic", upgrade = "always"); print(tic::dsl_load()); tic::prepare_all_stages()' 95 | R -q -e 'tic::before_install()' 96 | R -q -e 'tic::install()' 97 | ``` 98 | 99 | Next, the {tic} package is installed and certain [tic steps](https://docs.ropensci.org/tic/articles/build-lifecycle.html) are run. 100 | These take care of installing the dependencies of the R package to be checked and prepare the environment of other subsequent steps. 101 | 102 | --- 103 | 104 | ```yml 105 | - run: 106 | name: "[r-release] R CMD Check" 107 | no_output_timeout: 60m 108 | command: | 109 | R -q -e 'tic::before_script()' 110 | R -q -e 'tic::script()' 111 | ``` 112 | 113 | This step checks the package for CRAN eligibility by making use of `rcmdcheck::rcmdcheck()` inside the `tic::script()` call. 114 | 115 | --- 116 | 117 | ```yml 118 | # save R pkg cache ------------------------------------------------------- 119 | - save_cache: 120 | key: R-package-library-{{ checksum "/tmp/_tmp_file" }} 121 | paths: 122 | - /usr/local/lib/R/site-library 123 | ``` 124 | 125 | Finally, the R package which was initiated earlier is saved. 126 | 127 | --- 128 | 129 | The `deploy:` step following next is in most parts executing the same steps as just shown. 130 | In the end however, `tic::deploy()` is called which internally will build a {pkgdown} site of the package and then deploy this site to the `gh-pages` branch of the repository. 131 | 132 | ## The first build 133 | 134 | After `.circleci/config.yml` and `tic.R` have been commited and pushed, the first build will start on Circle CI. 135 | 136 | Calling one of `get_pipelines()`, `get_workflows()` or `get_jobs()` should now return some content. 137 | 138 | In addition, you can directly browse the builds in the Circle CI web interface by calling `usethis::browse_circleci()`. 139 | -------------------------------------------------------------------------------- /vignettes/circle.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Getting Started" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{circle} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | # Introduction 18 | 19 | To be able to run builds from a repository, it needs to be enabled on Circle CI first. 20 | This package heavily relies on GitHub for automatic user information extraction and other providers such as GitLab or Bitbucket are not supported. 21 | In general Circle CI builds can be run from any Git hosting provider. 22 | 23 | # Authentication with GitHub 24 | 25 | To get started with Circle CI on GitHub, make sure to install the [Circle CI GitHub App](https://GitHub.com/marketplace/circleci) and configure the "Free" plan for your account. 26 | 27 | The first time any {circle} function is called, it will check for the existence of a Circle API key. 28 | This API key is needed to securely talk to the Circle CI API and make requests on behalf of a user. 29 | There are two ways the API key can be set: 30 | 31 | - (**recommended**) Via environment variable `R_CIRCLE` in `~/.Renviron` (e.g. `R_CIRCLE = `) 32 | - In `~/.circleci/cli.yml` via the following pattern 33 | 34 | ```yml 35 | host: https://circleci.com 36 | endpoint: graphql-unstable 37 | token: 38 | ``` 39 | 40 | To create an API key from the R console, `browse_circle_token()` can be used. 41 | 42 | {circle} scrapes information about the current repository by making API calls to GitHub. 43 | To be able to do this, a `GitHub_TOKEN` is needed (similar to the API requests for Circle CI). 44 | It is good practice to have such a token set for many purposes when using R with GitHub. 45 | Invoking `usethis::create_github_token()` is an easy way to create one if none exists yet. 46 | 47 | # First steps 48 | 49 | By querying information about the user and enabling a project one can check if everything was set up correctly. 50 | 51 | `get_circle_user()` makes uses of the Circle CI API key. 52 | 53 | 54 | ```r 55 | circle::get_circle_user() 56 | 57 | $content 58 | $content$name 59 | [1] "Patrick Schratz" 60 | 61 | $content$login 62 | [1] "pat-s" 63 | 64 | $content$id 65 | [1] "9c373331-d0f7-45e1-afe6-4a5c75e00d10" 66 | 67 | 68 | $path 69 | [1] "/me" 70 | 71 | $response 72 | Response [https://circleci.com/api/v2/me?circle-token=39d697f345d8d8a92ab07c333405d9b0092d116c] 73 | Date: 2021-01-07 20:07 74 | Status: 200 75 | Content-Type: application/json;charset=utf-8 76 | Size: 86 B 77 | 78 | 79 | attr(,"class") 80 | [1] "circle_user" 81 | ``` 82 | 83 | `enable_repo()` also uses the GitHub token. 84 | 85 | ```r 86 | circe::enable_repo() 87 | ✔ Successfully enabled repo 'ropensci/circle' on Circle CI. 88 | ``` 89 | 90 | After the repo has been enabled, it should be returned in `circle::list_projects()`. 91 | 92 | # Deployment 93 | 94 | Deployments refers to the practice to push (modified) files during a build to a repository. 95 | Configuring build deployments can be a bit tedious with respect to permissions. 96 | Here `circle::use_circle_deploy()` helps as it creates a SSH key pair which will enable deployment. 97 | The private key will be stored in GitHub (under "Settings -> SSH and GPG keys") and the public key on Circle CI (on the respective project page in "SSH Keys"). 98 | 99 | ```r 100 | circle::use_circle_deploy() 101 | ✖ No 'user-key' found. 102 | ────────────────────────────────────────────────────────────────────────────────────────────────── 103 | ✔ Added a 'user key' to project '' on Circle CI. 104 | This enables deployment from builds. 105 | ``` 106 | 107 | To double-check, there should now be a "user-key" in your Circle CI repo settings under the menu point "SSH keys". 108 | It does not matter if multiple "deploy key" or "user-key"s exists. 109 | The important point is that one "user key" is set as "preferred". 110 | 111 | ```{r, echo=FALSE} 112 | knitr::include_graphics("../man/figures/user-key.png") 113 | ``` 114 | 115 | As an alternative to `circle::use_circle_deploy()` one could also click the "Add user key" button that would appear if no user key has been set yet. 116 | 117 | ## Deployment Keys 118 | 119 | With respect to SSH keys, there two different types of keys on Circle CI: 120 | 121 | - Deploy key 122 | 123 | - User key 124 | 125 | "Deploy keys" are used to checkout your repository so that the build is starting. 126 | These kind of keys how only "read" permissions but are not allow to "write" to your repository. 127 | If you have connected Circle CI to GitHub already, you will have a "deploy key" stored in every repository to be able to checkout the code. 128 | The name "deploy" key here is a bit misleading since they **cannot** be used for deploying from builds. 129 | 130 | To enable deployment to a repository however, a "user-key" is needed. 131 | This key type also has "write" permissions and can be added using `use_circle_deploy()`. 132 | `use_circle_deploy()` will add a so called "user key" to the settings of the repo on Circle CI. 133 | The private key will be added to your GitHub profile under the "SSH and GPG keys" section with the title pointing to the respective repo. 134 | See also [the Circle CI section about deployment](https://circleci.com/docs/deployment-integrations/). 135 | 136 | (If for some reasons you do not want to use `use_circle_deploy()` and go the manual way of adding a SSH key to Circle CI, please be aware of [this issue](https://discuss.circleci.com/t/adding-ssh-keys-fails/7747).) 137 | 138 | # Starting a Build 139 | 140 | As with almost every CI provider, a YAML configuration files is required to specify the tasks that should be executed during a build. 141 | Currently Circle CI does not come with official support for the R language but you can add [your vote here](https://ideas.circleci.com/images/p/add-official-support-for-r). 142 | Since Circle CI is heavily based on docker this is not really a problem. 143 | One can simply use the [rocker](https://github.com/rocker-org/rocker) R images to have first-class support with respect to R containers. 144 | Official support for R would mean that the images are cached on Circle CIs side and be directly available on build start. 145 | Currently, `rocker` images need to be downloaded in every build again. 146 | 147 | {circle} does not come with a template for running R builds as it focuses on API and build metadata functionality. 148 | Instead, have a look at [ropensci/tic](https://docs.ropensci.org/tic/) and its functions `tic::use_tic()` and `tic::use_circle_yml()` to quickly get R builds running on Circle CI. 149 | Alternatively you can borrow the config that is being used [in this repo](https://github.com/ropensci/circle/blob/main/.circleci/config.yml) - which was also created via {tic}. 150 | -------------------------------------------------------------------------------- /R/checkout-key.R: -------------------------------------------------------------------------------- 1 | #' @title Interact with "Checkout Keys" on Circle CI 2 | #' @description 3 | #' Create, delete, query or check different types of checkout keys 4 | #' for a specific Circle CI project. 5 | #' Valid values for argument `type` are `"user-key"` or `"deploy-key"`. 6 | #' 7 | #' A "Checkout Key" on Circle CI is a specific SSH key which is used to checkout 8 | #' repositories into a Circle CI build and possible deploy changes to the 9 | #' repository. 10 | #' Circle CI subdivides "Checkout Keys" into "user-key" and "deploy-key". 11 | #' 12 | #' Please see "Deployment" section in the "Getting Started" vignette for more 13 | #' information. 14 | #' @template repo 15 | #' @template user 16 | #' @template vcs 17 | #' @template quiet 18 | #' @param type `[character]`\cr 19 | #' Type of key to add. Options are "user-key" and "deploy-key". 20 | #' @param fingerprint `[character]`\cr 21 | #' The fingerprint of the checkout key which should be deleted. 22 | #' @template api_version 23 | #' @name checkout_key 24 | #' @return An object of class `circle_api` with the following elements 25 | #' - `content` (queried content) 26 | #' - `path` (API request) 27 | #' - `response` (HTTP response information) 28 | #' @export 29 | #' @examples 30 | #' \dontrun{ 31 | #' # by default a "user-key" will be created which can also be used for build 32 | #' # deployments 33 | #' create_checkout_key() 34 | #' 35 | #' # A "deploy-key" can only be used to checkout code from the repository into 36 | #' # a Circle CI build 37 | #' create_checkout_key(type = "deploy-key") 38 | #' } 39 | create_checkout_key <- function(repo = github_info()$name, 40 | user = github_info()$owner$login, 41 | type = "user-key", 42 | api_version = "v2", 43 | vcs_type = "gh", 44 | quiet = FALSE) { 45 | 46 | if (type == "deploy-key") { # nocov start 47 | cli_alert_warning("Note that, despite the name, a 'deploy-key' does not 48 | grant permissions for build deployments to Github. Use a 'user-key' 49 | instead.") 50 | } # nocov end 51 | 52 | resp <- circle("POST", 53 | path = sprintf("/project/%s/%s/%s/checkout-key", vcs_type, user, repo), 54 | body = list(type = type), api_version = api_version 55 | ) 56 | 57 | if (status_code(resp$response) == 500) { # nocov start 58 | cli_alert_warning("This error usually occurs if authorization with GitHub 59 | was not yet granted for Circle CI projects. 60 | In your Circle CI project page (web), go to {.field 'Project Settings' 61 | -> 'SSH Keys' -> 'User Key' -> 'Authorize with GitHub'}. 62 | This should only be required once. 63 | If you already added a user key once and see this error, remove all user 64 | keys and re-authenticate on the web page. 65 | See {.url https://discuss.circleci.com/t/rest-api-cant-create-a-project-user-checkout-key/934/4} 66 | for more information.", wrap = TRUE) 67 | } # nocov end 68 | 69 | stop_for_status( 70 | resp$response, 71 | sprintf("create checkout keys for repo %s/%s on Circle CI", user, repo) 72 | ) 73 | 74 | if (!quiet) { # nocov start 75 | cli_alert_success("Successfully added a {.field {type}} to project 76 | {.field {user}/{repo}}.", wrap = TRUE) 77 | } # nocov end 78 | 79 | return(invisible(resp)) 80 | } 81 | 82 | #' @rdname checkout_key 83 | #' @export 84 | #' @examples 85 | #' \dontrun{ 86 | #' get_checkout_keys() 87 | #' } 88 | get_checkout_keys <- function(repo = github_info()$name, 89 | user = github_info()$owner$login, 90 | vcs_type = "gh", 91 | api_version = "v2") { 92 | 93 | resp <- circle("GET", 94 | path = sprintf( 95 | "/project/%s/%s/%s/checkout-key", 96 | vcs_type, user, repo 97 | ), 98 | api_version = api_version 99 | ) 100 | 101 | stop_for_status( 102 | resp$response, 103 | sprintf("get checkout keys for repo %s/%s on Circle CI", user, repo) 104 | ) 105 | 106 | return(resp) 107 | } 108 | 109 | #' @rdname checkout_key 110 | #' @export 111 | #' @examples 112 | #' \dontrun{ 113 | #' delete_checkout_key() 114 | #' } 115 | delete_checkout_key <- function(fingerprint = NULL, 116 | repo = github_info()$name, 117 | user = github_info()$owner$login, 118 | type = "user-key", 119 | api_version = "v2", 120 | vcs_type = "gh") { 121 | 122 | if (is.null(fingerprint)) { # nocov start 123 | stop("Please provide the fingerprint of the key which should be deleted.") 124 | } # nocov end 125 | 126 | resp <- circle("DELETE", 127 | path = sprintf( 128 | "/project/%s/%s/%s/checkout-key/%s", 129 | vcs_type, user, repo, fingerprint 130 | ), 131 | api_version = api_version 132 | ) 133 | 134 | stop_for_status( 135 | resp$response, 136 | sprintf("get checkout keys for repo %s/%s on Circle CI", user, repo) 137 | ) 138 | 139 | # We cannot delete the corresponding key on Github because we do not have 140 | # the fingerprint or any other matching information. Issuing a warning.. 141 | if (type == "deploy-key") { # nocov start 142 | cli_alert_warning("Make sure to also delete the corresponding SSH key on 143 | Github at {.url https://github.com/{user}/{repo}/settings/keys}!", 144 | wrap = TRUE 145 | ) 146 | } else { 147 | cli_alert_warning("Make sure to also delete the corresponding SSH key on 148 | Github at {.url https://github.com/settings/keys}!", 149 | wrap = TRUE 150 | ) 151 | } # nocov end 152 | 153 | return(resp) 154 | } 155 | 156 | #' @param preferred `[logical]`\cr 157 | #' Checks whether the requested type is the "preferred" key. 158 | #' @rdname checkout_key 159 | #' @export 160 | #' @examples 161 | #' \dontrun{ 162 | #' has_checkout_key() 163 | #' } 164 | has_checkout_key <- function(repo = github_info()$name, 165 | user = github_info()$owner$login, 166 | type = "github-user-key", 167 | vcs_type = "gh", 168 | preferred = TRUE) { 169 | 170 | info <- get_checkout_keys(repo = repo, user = user, vcs_type = vcs_type) 171 | key <- any(grepl(type, info)) 172 | 173 | if (!key) { # nocov start 174 | cat_bullet( 175 | bullet = "cross", bullet_col = "red", 176 | sprintf("No '%s' found.", type) 177 | ) 178 | return(FALSE) 179 | } # nocov end 180 | 181 | if (preferred) { 182 | preferred <- "true" 183 | } else { 184 | preferred <- "false" 185 | } 186 | 187 | key_name <- purrr::map_chr(info$content$items, ~ .x$type) 188 | is_pref <- purrr::map_lgl(info$content$items, ~ .x$preferred) 189 | 190 | df <- data.frame(key_name, is_pref) 191 | 192 | if (preferred) { 193 | ind <- which(df$is_pref == TRUE) 194 | return(df[ind, "key_name"] == type) 195 | } else { 196 | return(TRUE) # nocov 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /codemeta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://doi.org/10.5063/schema/codemeta-2.0", 3 | "@type": "SoftwareSourceCode", 4 | "identifier": "circle", 5 | "description": "Tools for interacting with the 'Circle CI' API (). Besides executing common tasks such as querying build logs and restarting builds, this package also helps setting up permissions to deploy from builds.", 6 | "name": "circle: R Client Package for Circle CI", 7 | "relatedLink": "https://docs.ropensci.org/circle/", 8 | "codeRepository": "https://github.com/ropensci/circle", 9 | "issueTracker": "https://github.com/ropensci/circle/issues", 10 | "license": "https://spdx.org/licenses/GPL-3.0", 11 | "version": "0.7.2", 12 | "programmingLanguage": { 13 | "@type": "ComputerLanguage", 14 | "name": "R", 15 | "url": "https://r-project.org" 16 | }, 17 | "runtimePlatform": "R version 4.2.1 (2022-06-23)", 18 | "provider": { 19 | "@id": "https://cran.r-project.org", 20 | "@type": "Organization", 21 | "name": "Comprehensive R Archive Network (CRAN)", 22 | "url": "https://cran.r-project.org" 23 | }, 24 | "author": [ 25 | { 26 | "@type": "Person", 27 | "givenName": "Patrick", 28 | "familyName": "Schratz", 29 | "email": "patrick.schratz@gmail.com", 30 | "@id": "https://orcid.org/0000-0003-0748-6624" 31 | } 32 | ], 33 | "maintainer": [ 34 | { 35 | "@type": "Person", 36 | "givenName": "Patrick", 37 | "familyName": "Schratz", 38 | "email": "patrick.schratz@gmail.com", 39 | "@id": "https://orcid.org/0000-0003-0748-6624" 40 | } 41 | ], 42 | "softwareSuggestions": [ 43 | { 44 | "@type": "SoftwareApplication", 45 | "identifier": "clipr", 46 | "name": "clipr", 47 | "provider": { 48 | "@id": "https://cran.r-project.org", 49 | "@type": "Organization", 50 | "name": "Comprehensive R Archive Network (CRAN)", 51 | "url": "https://cran.r-project.org" 52 | }, 53 | "sameAs": "https://CRAN.R-project.org/package=clipr" 54 | }, 55 | { 56 | "@type": "SoftwareApplication", 57 | "identifier": "covr", 58 | "name": "covr", 59 | "provider": { 60 | "@id": "https://cran.r-project.org", 61 | "@type": "Organization", 62 | "name": "Comprehensive R Archive Network (CRAN)", 63 | "url": "https://cran.r-project.org" 64 | }, 65 | "sameAs": "https://CRAN.R-project.org/package=covr" 66 | }, 67 | { 68 | "@type": "SoftwareApplication", 69 | "identifier": "gert", 70 | "name": "gert", 71 | "provider": { 72 | "@id": "https://cran.r-project.org", 73 | "@type": "Organization", 74 | "name": "Comprehensive R Archive Network (CRAN)", 75 | "url": "https://cran.r-project.org" 76 | }, 77 | "sameAs": "https://CRAN.R-project.org/package=gert" 78 | }, 79 | { 80 | "@type": "SoftwareApplication", 81 | "identifier": "gh", 82 | "name": "gh", 83 | "provider": { 84 | "@id": "https://cran.r-project.org", 85 | "@type": "Organization", 86 | "name": "Comprehensive R Archive Network (CRAN)", 87 | "url": "https://cran.r-project.org" 88 | }, 89 | "sameAs": "https://CRAN.R-project.org/package=gh" 90 | }, 91 | { 92 | "@type": "SoftwareApplication", 93 | "identifier": "knitr", 94 | "name": "knitr", 95 | "provider": { 96 | "@id": "https://cran.r-project.org", 97 | "@type": "Organization", 98 | "name": "Comprehensive R Archive Network (CRAN)", 99 | "url": "https://cran.r-project.org" 100 | }, 101 | "sameAs": "https://CRAN.R-project.org/package=knitr" 102 | }, 103 | { 104 | "@type": "SoftwareApplication", 105 | "identifier": "openssl", 106 | "name": "openssl", 107 | "provider": { 108 | "@id": "https://cran.r-project.org", 109 | "@type": "Organization", 110 | "name": "Comprehensive R Archive Network (CRAN)", 111 | "url": "https://cran.r-project.org" 112 | }, 113 | "sameAs": "https://CRAN.R-project.org/package=openssl" 114 | }, 115 | { 116 | "@type": "SoftwareApplication", 117 | "identifier": "purrr", 118 | "name": "purrr", 119 | "provider": { 120 | "@id": "https://cran.r-project.org", 121 | "@type": "Organization", 122 | "name": "Comprehensive R Archive Network (CRAN)", 123 | "url": "https://cran.r-project.org" 124 | }, 125 | "sameAs": "https://CRAN.R-project.org/package=purrr" 126 | }, 127 | { 128 | "@type": "SoftwareApplication", 129 | "identifier": "rmarkdown", 130 | "name": "rmarkdown", 131 | "provider": { 132 | "@id": "https://cran.r-project.org", 133 | "@type": "Organization", 134 | "name": "Comprehensive R Archive Network (CRAN)", 135 | "url": "https://cran.r-project.org" 136 | }, 137 | "sameAs": "https://CRAN.R-project.org/package=rmarkdown" 138 | }, 139 | { 140 | "@type": "SoftwareApplication", 141 | "identifier": "testthat", 142 | "name": "testthat", 143 | "version": ">= 3.0.0", 144 | "provider": { 145 | "@id": "https://cran.r-project.org", 146 | "@type": "Organization", 147 | "name": "Comprehensive R Archive Network (CRAN)", 148 | "url": "https://cran.r-project.org" 149 | }, 150 | "sameAs": "https://CRAN.R-project.org/package=testthat" 151 | }, 152 | { 153 | "@type": "SoftwareApplication", 154 | "identifier": "usethis", 155 | "name": "usethis", 156 | "version": ">= 2.0.0", 157 | "provider": { 158 | "@id": "https://cran.r-project.org", 159 | "@type": "Organization", 160 | "name": "Comprehensive R Archive Network (CRAN)", 161 | "url": "https://cran.r-project.org" 162 | }, 163 | "sameAs": "https://CRAN.R-project.org/package=usethis" 164 | }, 165 | { 166 | "@type": "SoftwareApplication", 167 | "identifier": "utils", 168 | "name": "utils" 169 | }, 170 | { 171 | "@type": "SoftwareApplication", 172 | "identifier": "vcr", 173 | "name": "vcr", 174 | "provider": { 175 | "@id": "https://cran.r-project.org", 176 | "@type": "Organization", 177 | "name": "Comprehensive R Archive Network (CRAN)", 178 | "url": "https://cran.r-project.org" 179 | }, 180 | "sameAs": "https://CRAN.R-project.org/package=vcr" 181 | }, 182 | { 183 | "@type": "SoftwareApplication", 184 | "identifier": "withr", 185 | "name": "withr", 186 | "provider": { 187 | "@id": "https://cran.r-project.org", 188 | "@type": "Organization", 189 | "name": "Comprehensive R Archive Network (CRAN)", 190 | "url": "https://cran.r-project.org" 191 | }, 192 | "sameAs": "https://CRAN.R-project.org/package=withr" 193 | } 194 | ], 195 | "softwareRequirements": { 196 | "1": { 197 | "@type": "SoftwareApplication", 198 | "identifier": "cli", 199 | "name": "cli", 200 | "version": ">= 2.0.0", 201 | "provider": { 202 | "@id": "https://cran.r-project.org", 203 | "@type": "Organization", 204 | "name": "Comprehensive R Archive Network (CRAN)", 205 | "url": "https://cran.r-project.org" 206 | }, 207 | "sameAs": "https://CRAN.R-project.org/package=cli" 208 | }, 209 | "2": { 210 | "@type": "SoftwareApplication", 211 | "identifier": "httr", 212 | "name": "httr", 213 | "provider": { 214 | "@id": "https://cran.r-project.org", 215 | "@type": "Organization", 216 | "name": "Comprehensive R Archive Network (CRAN)", 217 | "url": "https://cran.r-project.org" 218 | }, 219 | "sameAs": "https://CRAN.R-project.org/package=httr" 220 | }, 221 | "3": { 222 | "@type": "SoftwareApplication", 223 | "identifier": "jsonlite", 224 | "name": "jsonlite", 225 | "provider": { 226 | "@id": "https://cran.r-project.org", 227 | "@type": "Organization", 228 | "name": "Comprehensive R Archive Network (CRAN)", 229 | "url": "https://cran.r-project.org" 230 | }, 231 | "sameAs": "https://CRAN.R-project.org/package=jsonlite" 232 | }, 233 | "SystemRequirements": null 234 | }, 235 | "fileSize": "1288.526KB" 236 | } 237 | -------------------------------------------------------------------------------- /tests/fixtures/s3-print-circle_pipeline.yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: get 4 | uri: https://circleci.com/api/v2/project/gh/ropensci/circle/pipeline?limit=30&circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | access-control-allow-origin: '*' 19 | content-encoding: gzip 20 | content-type: application/json;charset=utf-8 21 | date: Tue, 03 Nov 2020 11:39:21 GMT 22 | server: nginx 23 | set-cookie: ring-session=TDIQHcD2tUb4bgY4lvAG4hn08qy%2FgeMSGcKZvrZ%2BT69MFQcp6BnmtSHEJLEGXp4VCvkrls1eeV9qL5sMb7hzS2Kao%2F%2FFyPVn8a2UxmT2hoSnwnoDApYATovU2QzyOIBO5Mo0ukULN9uHGh05qzGFf4%2FZS9pnrhQ8fVnQNdJVSq05ela81w9zXx7TrtloTNO4uTPAlH0BBW2Jl6uRUcRH89rRMc0Il%2FhMVCvsuE2IjoU%3D--0obpYnTSSDCB0lQDnp%2F8Pn5tjekSuWjag4ll0R43QFk%3D;Path=/;HttpOnly;Expires=Tue, 24 | 02 Nov 2021 15:10:22 +0000;Max-Age=1209600;Secure 25 | strict-transport-security: max-age=15724800 26 | vary: Origin 27 | x-circleci-identity: circle-www-api-v1-777c756979-z4hc8 28 | x-frame-options: DENY 29 | x-ratelimit-limit: '151' 30 | x-ratelimit-remaining: '150' 31 | x-ratelimit-reset: '0' 32 | x-request-id: 63352d9e-03a7-4dd0-a36e-19748680fabd 33 | x-route: /api/v2/project/:project-slug/pipeline 34 | transfer-encoding: chunked 35 | connection: keep-alive 36 | body: 37 | encoding: UTF-8 38 | file: no 39 | string: '{"next_page_token":"AARLwwWZ6tqJF3j7pwktR3P7T4EVovCzFuhrgrh5CFIX7cqIIe2ftqC5q3mHh2MKJKxQ1Ls6qdFGaQyACbclBzBkjc15KWY52rhYBX7egsZlKvHASg_lp0dGliraZ0_6U4U0c7FAMIiH","items":[{"id":"9c053b8f-67fc-4543-a6a9-51b9540692f4","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T11:35:07.224Z","number":469,"state":"created","created_at":"2020-11-03T11:35:07.224Z","trigger":{"received_at":"2020-11-03T11:35:07.173Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"e1f64ee88ff28ee54849fc78d477a8a312fc19b9","provider_name":"GitHub","commit":{"body":"","subject":"update 40 | vcr cassettes"},"branch":"master"}},{"id":"ed7335bf-d605-401e-88c0-4cfe57c4af4f","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T11:04:49.739Z","number":468,"state":"created","created_at":"2020-11-03T11:04:49.739Z","trigger":{"received_at":"2020-11-03T11:04:49.683Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"b30f76f4e2eaf553dc37e5ebe067b5b821310805","provider_name":"GitHub","commit":{"body":"","subject":"Increase 41 | code coverage and remove git2r"},"branch":"master"}},{"id":"111fb318-b3e9-4c7b-89c9-c475acaffa29","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T10:54:23.294Z","number":467,"state":"created","created_at":"2020-11-03T10:54:23.294Z","trigger":{"received_at":"2020-11-03T10:54:23.249Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"f06ff0efea65ab6cbd72678de5c27369585aae12","provider_name":"GitHub","commit":{"body":"","subject":"fix 42 | path"},"branch":"codecov"}},{"id":"6aa98dbf-68fd-4696-a5eb-6f87d2724fca","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T10:49:20.887Z","number":466,"state":"created","created_at":"2020-11-03T10:49:20.887Z","trigger":{"received_at":"2020-11-03T10:49:20.841Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"6b523905f047c3da5433d83cc229944b6225d604","provider_name":"GitHub","commit":{"body":"","subject":"use 43 | gert::git_clone"},"branch":"codecov"}},{"id":"2b7c55eb-3648-45c0-9d5c-1730a07b3f9f","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T10:23:41.378Z","number":465,"state":"created","created_at":"2020-11-03T10:23:41.378Z","trigger":{"received_at":"2020-11-03T10:23:41.338Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"2fcfc53afd6a6b98915d512862bb0aa29ed74917","provider_name":"GitHub","commit":{"body":"","subject":"pass 44 | gh token to create_from_github explicitly"},"branch":"codecov"}},{"id":"c7287fec-3a2b-4cc0-be22-bbffceb9cc9f","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T10:03:56.341Z","number":464,"state":"created","created_at":"2020-11-03T10:03:56.341Z","trigger":{"received_at":"2020-11-03T10:03:56.297Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"938100642973496fff3836206e4b55aefe491f68","provider_name":"GitHub","commit":{"body":"","subject":"get 45 | GH Token on CI conditionally"},"branch":"codecov"}},{"id":"04aba538-5c6b-437b-ac00-bba435bc0e0a","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T09:45:20.588Z","number":463,"state":"created","created_at":"2020-11-03T09:45:20.588Z","trigger":{"received_at":"2020-11-03T09:45:20.537Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"11af1c77fe233d824d890377c706924b6ab71719","provider_name":"GitHub","commit":{"body":"","subject":"cleanup"},"branch":"codecov"}},{"id":"abfa7482-ad8d-431d-9287-1603580e256d","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T09:29:58.460Z","number":462,"state":"created","created_at":"2020-11-03T09:29:58.460Z","trigger":{"received_at":"2020-11-03T09:29:58.408Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"aff9bc3d2a2ee71f930fe084e5f1d53207ae2cb6","provider_name":"GitHub","commit":{"body":"","subject":"add 46 | full coverage"},"branch":"codecov"}},{"id":"cc9f4ccb-ccb0-41a0-b4a0-16ca60f33586","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-03T04:00:43.140Z","number":461,"state":"created","created_at":"2020-11-03T04:00:43.140Z","trigger":{"received_at":"2020-11-03T04:00:42.592Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"1ae27c25-b610-4e43-bcb7-578ca0a680d6","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-02T19:45:34.774Z","number":460,"state":"created","created_at":"2020-11-02T19:45:34.774Z","trigger":{"received_at":"2020-11-02T19:45:34.736Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"82a0e3eb20c2602186b4ce27e72b2eab88187a89","provider_name":"GitHub","commit":{"body":"","subject":"make 47 | codecov work"},"branch":"codecov"}},{"id":"1e9b92c9-aa21-46f0-821b-ba460bd30098","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-02T18:38:28.586Z","number":459,"state":"created","created_at":"2020-11-02T18:38:28.586Z","trigger":{"received_at":"2020-11-02T18:38:28.524Z","type":"webhook","actor":{"login":"pat-s","avatar_url":"https://avatars3.githubusercontent.com/u/8430564?v=4"}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"698710848b767ecb11a4e6d73819a1babfd6f7c4","provider_name":"GitHub","commit":{"body":"","subject":"run 48 | codecov"},"branch":"codecov"}},{"id":"5ada2fc7-1ee6-45b0-8bd2-2598823d94b6","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-02T04:00:38.120Z","number":458,"state":"created","created_at":"2020-11-02T04:00:38.120Z","trigger":{"received_at":"2020-11-02T04:00:38.114Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"8e9fc756-ae18-49c6-af6c-9925addf8d6b","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-11-01T04:00:35.613Z","number":457,"state":"created","created_at":"2020-11-01T04:00:35.613Z","trigger":{"received_at":"2020-11-01T04:00:35.607Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"aedfe76a-ddc7-4cc0-9a22-02cd41995f5c","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-10-31T04:00:15.224Z","number":456,"state":"created","created_at":"2020-10-31T04:00:15.224Z","trigger":{"received_at":"2020-10-31T04:00:15.219Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"d2fcdd5d-8319-4a1e-a346-b8b8b6c3b98d","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-10-30T04:00:15.882Z","number":455,"state":"created","created_at":"2020-10-30T04:00:15.882Z","trigger":{"received_at":"2020-10-30T04:00:15.877Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"a8790a20-8983-4d0e-ac50-981974580300","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-10-29T04:00:47.952Z","number":454,"state":"created","created_at":"2020-10-29T04:00:47.952Z","trigger":{"received_at":"2020-10-29T04:00:47.948Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"a9c46710-8ca8-4a51-ac87-2616fa2e3cea","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-10-28T04:00:48.318Z","number":453,"state":"created","created_at":"2020-10-28T04:00:48.318Z","trigger":{"received_at":"2020-10-28T04:00:48.310Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"2d32987f-2075-4779-a61c-19c6fd70b109","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-10-27T04:00:13.254Z","number":452,"state":"created","created_at":"2020-10-27T04:00:13.254Z","trigger":{"received_at":"2020-10-27T04:00:13.250Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"ee2c6cd0-bb53-47ee-971b-10ef966f5cfd","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-10-26T04:00:47.176Z","number":451,"state":"created","created_at":"2020-10-26T04:00:47.176Z","trigger":{"received_at":"2020-10-26T04:00:47.171Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}},{"id":"cf1ffa66-6c33-4618-9999-9b9cb8593112","errors":[],"project_slug":"gh/ropensci/circle","updated_at":"2020-10-25T04:00:15.433Z","number":450,"state":"created","created_at":"2020-10-25T04:00:15.433Z","trigger":{"received_at":"2020-10-25T04:00:15.428Z","type":"schedule","actor":{"login":"Scheduled","avatar_url":null}},"vcs":{"origin_repository_url":"https://github.com/ropensci/circle","target_repository_url":"https://github.com/ropensci/circle","revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","provider_name":"GitHub","branch":"master"}}]}' 49 | recorded_at: 2020-11-03 11:39:21 GMT 50 | recorded_with: vcr/0.5.4, webmockr/0.7.0 51 | -------------------------------------------------------------------------------- /tests/fixtures/list_projects().yml: -------------------------------------------------------------------------------- 1 | http_interactions: 2 | - request: 3 | method: get 4 | uri: https://circleci.com/api/v1.1/projects?circle-token=<<>> 5 | body: 6 | encoding: '' 7 | string: '' 8 | headers: 9 | Content-Type: '' 10 | Accept: application/json 11 | response: 12 | status: 13 | status_code: 200 14 | category: Success 15 | reason: OK 16 | message: 'Success: (200) OK' 17 | headers: 18 | access-control-allow-origin: '*' 19 | content-encoding: gzip 20 | content-type: application/json;charset=utf-8 21 | date: Tue, 03 Nov 2020 11:39:41 GMT 22 | server: nginx 23 | set-cookie: ring-session=%2BiY2fls810krOpesn%2BxhTgCCdwHLOb279My1drBjzZpql8VEje7xU34QKJ%2FfNDpjo60ndlHGYUK%2BmYg5iSMYiR7dT6dA01y%2B2ka%2F8pMY6Qd8H1SRTuu3biA2Y%2BLrx%2B%2FBhRns18nSU%2BLDG%2FosgOFGS2oZPAXN7h9V3zcfkXOtZIVZbgUofaIemjQrh60DdOajpc2mD0C8ZmCWaZ%2B%2Ba7UuM3HS%2BugLbiwYuebTp02YcjE%3D--qIwDhB5DmIPut8%2BqcEQC%2F%2FaXWy0AHvC%2BfAqwe590nII%3D;Path=/;HttpOnly;Expires=Tue, 24 | 02 Nov 2021 15:14:49 +0000;Max-Age=1209600;Secure 25 | strict-transport-security: max-age=15724800 26 | vary: Origin 27 | x-circleci-identity: circle-www-api-v1-777c756979-28xrw 28 | x-frame-options: DENY 29 | x-ratelimit-limit: '151' 30 | x-ratelimit-remaining: '150' 31 | x-ratelimit-reset: '0' 32 | x-request-id: 151a33e4-92cd-4e44-9f21-4214de3acc6d 33 | x-route: /api/v1.1/projects 34 | transfer-encoding: chunked 35 | connection: keep-alive 36 | body: 37 | encoding: UTF-8 38 | file: no 39 | string: '[{"branches":{"master":{"latest_workflows":{"Build%20Error":{"status":"failed","created_at":"2020-08-12T12:41:49.827Z","id":"b0f8a18a-77be-47bb-8c6b-b5e738b0773d"},"build":{"status":"failed","created_at":"2020-07-26T18:55:54.707Z","id":"82e57c3b-076e-491f-b3e3-b787e30ee43e"},"nightly":{"status":"failed","created_at":"2020-08-05T03:00:09.829Z","id":"e1c1583a-1f97-4c09-84f8-61228c9d9c2b"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"failed","status":"failed","build_num":252,"vcs_revision":"11b65f5239f7a19f1764124e128578c5a57e6cf9","pushed_at":"2020-08-12T12:41:49.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-08-12T12:42:00.307Z"},{"outcome":"failed","status":"failed","build_num":251,"vcs_revision":"c954e8467b5aad647174383165163198ede813d7","pushed_at":"2020-08-12T09:19:59.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-08-12T09:21:00.727Z"},{"outcome":"failed","status":"failed","build_num":250,"vcs_revision":"749d5059c65fee0f5e60cb0db18e75f432aa05f3","pushed_at":"2020-08-12T09:19:17.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-08-12T09:19:23.249Z"},{"outcome":"failed","status":"failed","build_num":249,"vcs_revision":"767575ab73285bbb4740e8119deae6d8d1f56cb4","pushed_at":"2020-08-12T08:38:36.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-08-12T08:38:42.569Z"},{"outcome":"failed","status":"failed","build_num":248,"vcs_revision":"9fe93ea8f0c74df4db686d4855cc3e478e04ba33","pushed_at":"2020-08-05T16:02:36.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-08-05T16:02:46.095Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":252,"vcs_revision":"11b65f5239f7a19f1764124e128578c5a57e6cf9","pushed_at":"2020-08-12T12:41:49.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-08-12T12:42:00.307Z"},"latest_completed_workflows":{"Build%20Error":{"status":"failed","created_at":"2020-08-12T12:41:49.827Z","id":"b0f8a18a-77be-47bb-8c6b-b5e738b0773d"},"build":{"status":"failed","created_at":"2020-07-26T18:55:54.707Z","id":"82e57c3b-076e-491f-b3e3-b787e30ee43e"},"nightly":{"status":"failed","created_at":"2020-08-05T03:00:09.829Z","id":"e1c1583a-1f97-4c09-84f8-61228c9d9c2b"}},"last_success":{"outcome":"success","status":"success","build_num":15,"vcs_revision":"2183d6c1a340bf90ebf629996991046f9fdded81","pushed_at":"2020-02-23T15:57:15.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-02-23T16:38:27.675Z"},"is_using_workflows":true}},"oss":true,"reponame":"2019-feature-selection","parallel":1,"username":"pat-s","has_usable_key":false,"vcs_type":"github","language":null,"vcs_url":"https://github.com/pat-s/2019-feature-selection","following":true,"default_branch":"master"},{"branches":{"gh-pages":{"latest_workflows":{"Build%20Error":{"status":"running","created_at":"2020-11-03T04:19:54.888Z","id":"928024ce-c9a5-453e-8f8a-9ad51aa07a69"}},"pusher_logins":["krlmlr","pat-s"],"is_using_workflows":true},"smart-use-tic":{"latest_workflows":{"build":{"status":"success","created_at":"2019-11-27T11:03:40.840Z","id":"c0729ffc-b704-4357-86d5-e3d36d068e2c"}},"latest_completed_workflows":{"build":{"status":"success","created_at":"2019-11-27T11:03:40.840Z","id":"c0729ffc-b704-4357-86d5-e3d36d068e2c"}},"is_using_workflows":true},"travis-maco%2Blinux":{"latest_workflows":{"build":{"status":"success","created_at":"2020-01-07T20:03:47.221Z","id":"10083620-e563-4109-af78-55ee96c4ca27"}},"latest_completed_workflows":{"build":{"status":"success","created_at":"2020-01-07T20:03:47.221Z","id":"10083620-e563-4109-af78-55ee96c4ca27"}},"is_using_workflows":true},"b-254-add-inst-doc-ignore":{"latest_completed_workflows":{"build":{"status":"failed","created_at":"2020-05-27T03:21:58.199Z","id":"09e3ae4d-9f39-42bd-9f5b-f52c05030e6c"}},"latest_workflows":{"build":{"status":"failed","created_at":"2020-05-27T03:21:58.199Z","id":"09e3ae4d-9f39-42bd-9f5b-f52c05030e6c"}},"is_using_workflows":true},"auto-detect-libs":{"latest_workflows":{"build":{"status":"failed","created_at":"2020-10-07T09:40:18.617Z","id":"ea3afaf1-6660-45e5-b8da-8e6c91480fcb"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"failed","status":"failed","build_num":1364,"vcs_revision":"c6d62d4e53ee65ab284b142a629f793444a2f7a3","pushed_at":"2020-10-07T09:40:18.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-07T09:41:55.001Z"},{"outcome":"failed","status":"failed","build_num":1363,"vcs_revision":"8cf1282d556d6d0e362f5332c8832fc2f422277e","pushed_at":"2020-10-07T09:35:58.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-07T09:38:31.546Z"},{"outcome":"failed","status":"failed","build_num":1362,"vcs_revision":"565db5f9c0bac217d387074f08ed50c69e63d977","pushed_at":"2020-10-07T09:21:08.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-07T09:23:01.713Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":1364,"vcs_revision":"c6d62d4e53ee65ab284b142a629f793444a2f7a3","pushed_at":"2020-10-07T09:40:18.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-07T09:41:55.001Z"},"latest_completed_workflows":{"build":{"status":"failed","created_at":"2020-10-07T09:40:18.617Z","id":"ea3afaf1-6660-45e5-b8da-8e6c91480fcb"}},"is_using_workflows":true},"docker":{"latest_workflows":{"build":{"status":"success","created_at":"2020-07-25T04:31:35.144Z","id":"32500bd5-8fe7-45dd-9c05-f4402eae1d14"}},"pusher_logins":["pat-s","krlmlr"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":1172,"vcs_revision":"eddde3ce76f631bffab910dacaef87592efb6281","pushed_at":"2020-07-25T04:31:35.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-25T04:34:37.782Z"},{"outcome":"success","status":"success","build_num":1138,"vcs_revision":"5403a67160f4be246a48c858d2e425c29c4d6d78","pushed_at":"2020-07-12T02:26:43.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-12T02:35:26.328Z"},{"outcome":"success","status":"success","build_num":1137,"vcs_revision":"68dff6bfc1d8dda742325aab025596b26b5d7f1b","pushed_at":"2020-07-11T17:29:25.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-11T17:33:42.686Z"},{"outcome":"success","status":"success","build_num":1132,"vcs_revision":"dff53013402c77e4af048a4e390e27ed37bb689e","pushed_at":"2020-07-10T09:12:58.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-10T09:15:24.374Z"},{"outcome":"success","status":"success","build_num":1131,"vcs_revision":"c12a5ff53900a3df527d7a727aae5d5c773ed87b","pushed_at":"2020-07-10T07:53:25.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-10T07:56:01.264Z"}],"last_success":{"outcome":"success","status":"success","build_num":1172,"vcs_revision":"eddde3ce76f631bffab910dacaef87592efb6281","pushed_at":"2020-07-25T04:31:35.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-25T04:34:37.782Z"},"latest_completed_workflows":{"build":{"status":"success","created_at":"2020-07-25T04:31:35.144Z","id":"32500bd5-8fe7-45dd-9c05-f4402eae1d14"}},"is_using_workflows":true},"no-ahead-behind":{"latest_completed_workflows":{"build":{"status":"failed","created_at":"2020-05-30T12:52:10.180Z","id":"6c3a568d-c827-41a0-b9f0-14803d76aaac"}},"latest_workflows":{"build":{"status":"failed","created_at":"2020-05-30T12:52:10.180Z","id":"6c3a568d-c827-41a0-b9f0-14803d76aaac"}},"is_using_workflows":true},"drat-archive":{"latest_workflows":{"build":{"status":"failed","created_at":"2020-05-19T21:51:34.563Z","id":"c04fa22f-f120-43af-b9a9-d5b95e552bd0"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"failed","status":"failed","build_num":834,"vcs_revision":"9568c97a0c4b87df4a16a6e2195f5d732cf9dc8e","pushed_at":"2020-05-19T21:51:34.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-05-19T21:52:50.597Z"},{"outcome":"failed","status":"failed","build_num":833,"vcs_revision":"2dc8c8df25b4844a55065eac84966a1548341109","pushed_at":"2020-05-19T21:34:54.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-05-19T21:36:23.532Z"},{"outcome":"failed","status":"failed","build_num":832,"vcs_revision":"3dc6cc5fbccffa3388fbec8ccac7fc040dae8d12","pushed_at":"2020-05-19T20:29:19.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-05-19T20:30:41.980Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":834,"vcs_revision":"9568c97a0c4b87df4a16a6e2195f5d732cf9dc8e","pushed_at":"2020-05-19T21:51:34.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-05-19T21:52:50.597Z"},"latest_completed_workflows":{"build":{"status":"failed","created_at":"2020-05-19T21:51:34.563Z","id":"c04fa22f-f120-43af-b9a9-d5b95e552bd0"}},"is_using_workflows":true},"f-git-identity":{"latest_workflows":{"build":{"status":"failed","created_at":"2020-07-25T04:21:35.605Z","id":"2a95530f-e435-4a02-9b98-20c41dc4b9ac"}},"pusher_logins":["krlmlr"],"running_builds":[],"recent_builds":[{"outcome":"failed","status":"failed","build_num":1170,"vcs_revision":"2bd24152778f48bb1574b4a7d8c23673000e6b8b","pushed_at":"2020-07-25T04:21:35.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-25T04:23:51.416Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":1170,"vcs_revision":"2bd24152778f48bb1574b4a7d8c23673000e6b8b","pushed_at":"2020-07-25T04:21:35.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-25T04:23:51.416Z"},"latest_completed_workflows":{"build":{"status":"failed","created_at":"2020-07-25T04:21:35.605Z","id":"2a95530f-e435-4a02-9b98-20c41dc4b9ac"}},"is_using_workflows":true},"renv":{"latest_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-10-04T08:08:06.000Z","id":"e2d94600-e4f9-48e1-b27c-15c6a9a25d12"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"failed","status":"failed","build_num":4,"vcs_revision":"2fdf715b1dc4a5a23ddec45c45bc3d6806e1f2c1","pushed_at":"2019-10-04T08:08:05.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-10-04T08:08:19.452Z"},{"outcome":"failed","status":"failed","build_num":3,"vcs_revision":"92297d6c872bc832cc2de8c4e01ef82e2adc3997","pushed_at":"2019-10-04T08:06:46.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-10-04T08:06:52.633Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":4,"vcs_revision":"2fdf715b1dc4a5a23ddec45c45bc3d6806e1f2c1","pushed_at":"2019-10-04T08:08:05.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-10-04T08:08:19.452Z"},"latest_completed_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-10-04T08:08:06.000Z","id":"e2d94600-e4f9-48e1-b27c-15c6a9a25d12"}},"is_using_workflows":true},"master":{"latest_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-10-08T18:46:34.247Z","id":"606ef3dd-c1bf-4c4a-a72d-fcc1e158491c"},"build":{"status":"success","created_at":"2020-10-15T04:14:16.285Z","id":"ad2fa5b5-9763-4015-b943-c5e288a13c40"},"nightly":{"status":"success","created_at":"2020-11-03T04:00:50.804Z","id":"72bce87d-e2d4-41e5-a1db-3b52d3588a41"}},"pusher_logins":["sckott","krlmlr","pat-s"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":1421,"vcs_revision":"77ad45afe798835b0393cdb35374c8e373751f72","pushed_at":"2020-11-03T04:00:45.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T04:05:10.784Z"},{"outcome":"success","status":"success","build_num":1419,"vcs_revision":"77ad45afe798835b0393cdb35374c8e373751f72","pushed_at":"2020-11-02T04:00:40.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-02T04:06:56.857Z"},{"outcome":"success","status":"success","build_num":1417,"vcs_revision":"77ad45afe798835b0393cdb35374c8e373751f72","pushed_at":"2020-11-01T04:00:40.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-01T04:05:29.769Z"},{"outcome":"success","status":"success","build_num":1415,"vcs_revision":"77ad45afe798835b0393cdb35374c8e373751f72","pushed_at":"2020-10-31T04:00:25.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-31T04:05:07.079Z"},{"outcome":"success","status":"success","build_num":1413,"vcs_revision":"77ad45afe798835b0393cdb35374c8e373751f72","pushed_at":"2020-10-30T04:00:28.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-30T04:04:46.010Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":1387,"vcs_revision":"77ad45afe798835b0393cdb35374c8e373751f72","pushed_at":"2020-10-17T04:00:03.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-17T04:03:38.950Z"},"latest_completed_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-10-08T18:46:34.247Z","id":"606ef3dd-c1bf-4c4a-a72d-fcc1e158491c"},"build":{"status":"success","created_at":"2020-10-15T04:14:16.285Z","id":"ad2fa5b5-9763-4015-b943-c5e288a13c40"},"nightly":{"status":"success","created_at":"2020-11-03T04:00:50.804Z","id":"72bce87d-e2d4-41e5-a1db-3b52d3588a41"}},"last_success":{"outcome":"success","status":"success","build_num":1421,"vcs_revision":"77ad45afe798835b0393cdb35374c8e373751f72","pushed_at":"2020-11-03T04:00:45.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T04:05:10.784Z"},"is_using_workflows":true}},"oss":true,"reponame":"tic","parallel":1,"username":"ropensci","has_usable_key":false,"vcs_type":"github","language":null,"vcs_url":"https://github.com/ropensci/tic","following":true,"default_branch":"master"},{"branches":{"master":{"latest_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-09-30T18:45:51.686Z","id":"eeb36d21-e84e-41a3-a8f5-f3a2b6eab82c"},"build":{"status":"success","created_at":"2020-08-12T16:25:01.988Z","id":"0f6422e1-5c1a-49e7-8de7-a7246ea58a19"},"nightly":{"status":"success","created_at":"2020-11-03T04:00:49.114Z","id":"80ee9fd0-3764-4c0d-aa09-0c6de6f32b89"},"build_and_deploy":{"status":"success","created_at":"2020-02-15T16:10:30.085Z","id":"06a30a11-434d-48dc-9d0a-0fa323d99909"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":1217,"vcs_revision":"c399a3a640617bd47eb4e299befb5efc53c38c41","pushed_at":"2020-11-03T04:00:45.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T04:03:18.984Z"},{"outcome":"success","status":"success","build_num":1216,"vcs_revision":"c399a3a640617bd47eb4e299befb5efc53c38c41","pushed_at":"2020-11-02T04:00:40.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-02T04:03:01.936Z"},{"outcome":"success","status":"success","build_num":1215,"vcs_revision":"c399a3a640617bd47eb4e299befb5efc53c38c41","pushed_at":"2020-11-01T04:00:39.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-01T04:03:00.291Z"},{"outcome":"success","status":"success","build_num":1214,"vcs_revision":"c399a3a640617bd47eb4e299befb5efc53c38c41","pushed_at":"2020-10-31T04:00:24.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-31T04:02:32.042Z"},{"outcome":"success","status":"success","build_num":1212,"vcs_revision":"c399a3a640617bd47eb4e299befb5efc53c38c41","pushed_at":"2020-10-30T04:00:28.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-30T04:02:30.633Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":987,"vcs_revision":"11ae15d2162fcc0ee558580569ba1f77385e8ea8","pushed_at":"2020-07-13T04:00:30.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-13T04:01:24.330Z"},"latest_completed_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-09-30T18:45:51.686Z","id":"eeb36d21-e84e-41a3-a8f5-f3a2b6eab82c"},"build":{"status":"success","created_at":"2020-08-12T16:25:01.988Z","id":"0f6422e1-5c1a-49e7-8de7-a7246ea58a19"},"nightly":{"status":"success","created_at":"2020-11-03T04:00:49.114Z","id":"80ee9fd0-3764-4c0d-aa09-0c6de6f32b89"},"build_and_deploy":{"status":"success","created_at":"2020-02-15T16:10:30.085Z","id":"06a30a11-434d-48dc-9d0a-0fa323d99909"}},"last_success":{"outcome":"success","status":"success","build_num":1217,"vcs_revision":"c399a3a640617bd47eb4e299befb5efc53c38c41","pushed_at":"2020-11-03T04:00:45.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T04:03:18.984Z"},"is_using_workflows":true},"gh-pages":{"latest_workflows":{"Build%20Error":{"status":"running","created_at":"2020-10-30T04:15:41.103Z","id":"11f198c2-0694-4a28-aa68-0c4e7f8aec91"}},"pusher_logins":["pat-s"],"is_using_workflows":true},"gh_actions":{"latest_workflows":{"build_and_deploy":{"status":"success","created_at":"2020-02-17T23:34:42.268Z","id":"83f90dc2-acff-48ce-99da-e692306cdd28"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":509,"vcs_revision":"d67709d069d6f2aa9924334619b897dbff3c1bca","pushed_at":"2020-02-17T23:34:42.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-02-17T23:37:48.886Z"},{"outcome":"success","status":"success","build_num":507,"vcs_revision":"d67709d069d6f2aa9924334619b897dbff3c1bca","pushed_at":"2020-02-17T23:34:42.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-02-17T23:36:34.832Z"},{"outcome":"success","status":"success","build_num":508,"vcs_revision":"f00563a77eb7c59848f4b6f1e9b61422a97b208f","pushed_at":"2020-02-17T23:33:22.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-02-17T23:36:32.972Z"},{"outcome":"success","status":"success","build_num":506,"vcs_revision":"f00563a77eb7c59848f4b6f1e9b61422a97b208f","pushed_at":"2020-02-17T23:33:22.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-02-17T23:34:42.649Z"},{"outcome":"success","status":"success","build_num":505,"vcs_revision":"d25e535a4ab36b333ddc8dc289906bb6cbdaa540","pushed_at":"2020-02-17T21:48:49.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-02-17T21:51:42.187Z"}],"last_success":{"outcome":"success","status":"success","build_num":509,"vcs_revision":"d67709d069d6f2aa9924334619b897dbff3c1bca","pushed_at":"2020-02-17T23:34:42.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-02-17T23:37:48.886Z"},"latest_completed_workflows":{"build_and_deploy":{"status":"success","created_at":"2020-02-17T23:34:42.268Z","id":"83f90dc2-acff-48ce-99da-e692306cdd28"}},"is_using_workflows":true},"debug-rdev":{"latest_workflows":{"build":{"status":"success","created_at":"2020-03-18T09:11:28.670Z","id":"3b11ef4a-3df5-4b18-8dfe-f24a6714bd18"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":692,"vcs_revision":"4583a18330e7c89b61554586afec3778c491c1c4","pushed_at":"2020-03-18T09:11:28.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-03-18T09:12:50.278Z"},{"outcome":"success","status":"success","build_num":691,"vcs_revision":"777fc17f91ec2adca461d3df3b4140ea8635c56a","pushed_at":"2020-03-18T08:44:39.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-03-18T08:46:03.012Z"},{"outcome":"success","status":"success","build_num":690,"vcs_revision":"23f73863566085f06c395f0d41d041e7b9fb1b45","pushed_at":"2020-03-18T08:34:19.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-03-18T08:35:45.016Z"}],"last_success":{"outcome":"success","status":"success","build_num":692,"vcs_revision":"4583a18330e7c89b61554586afec3778c491c1c4","pushed_at":"2020-03-18T09:11:28.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-03-18T09:12:50.278Z"},"latest_completed_workflows":{"build":{"status":"success","created_at":"2020-03-18T09:11:28.670Z","id":"3b11ef4a-3df5-4b18-8dfe-f24a6714bd18"}},"is_using_workflows":true},"test-no-ccache":{"latest_workflows":{"build":{"status":"success","created_at":"2020-05-28T10:35:20.823Z","id":"138d5e83-25de-4755-8583-fe09dfecd2a2"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":882,"vcs_revision":"8d9b74db7ec6debe2bbafd33fec4b86fa1f12775","pushed_at":"2020-05-28T10:35:20.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-05-28T10:36:51.207Z"}],"last_success":{"outcome":"success","status":"success","build_num":882,"vcs_revision":"8d9b74db7ec6debe2bbafd33fec4b86fa1f12775","pushed_at":"2020-05-28T10:35:20.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-05-28T10:36:51.207Z"},"latest_completed_workflows":{"build":{"status":"success","created_at":"2020-05-28T10:35:20.823Z","id":"138d5e83-25de-4755-8583-fe09dfecd2a2"}},"is_using_workflows":true},"test-docker":{"latest_workflows":{"build":{"status":"success","created_at":"2020-07-08T14:18:54.164Z","id":"2c855f27-084d-4c70-aacc-27184654b80c"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":978,"vcs_revision":"29069939809a9b43819ea58f2c81615f7baa097f","pushed_at":"2020-07-08T14:18:54.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-08T14:20:25.717Z"},{"outcome":"success","status":"success","build_num":977,"vcs_revision":"858a7607f789997e18f5b2fdeae6606cbbc0f7f5","pushed_at":"2020-07-08T14:18:08.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-08T14:20:37.568Z"},{"outcome":"success","status":"success","build_num":976,"vcs_revision":"2dd0fffc7efb541780c188a3fd21a18be6ef320f","pushed_at":"2020-07-08T14:16:56.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-08T14:18:01.256Z"},{"outcome":"success","status":"success","build_num":975,"vcs_revision":"80d41c5c8a777ade6f69eea53e8c2e1160347f47","pushed_at":"2020-07-08T14:15:35.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-08T14:17:14.627Z"},{"outcome":"success","status":"success","build_num":974,"vcs_revision":"7117e2b6b79a28cb754c1a167b461fa9665c4988","pushed_at":"2020-07-08T14:04:12.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-08T14:05:26.935Z"}],"last_success":{"outcome":"success","status":"success","build_num":978,"vcs_revision":"29069939809a9b43819ea58f2c81615f7baa097f","pushed_at":"2020-07-08T14:18:54.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-07-08T14:20:25.717Z"},"latest_completed_workflows":{"build":{"status":"success","created_at":"2020-07-08T14:18:54.164Z","id":"2c855f27-084d-4c70-aacc-27184654b80c"}},"is_using_workflows":true},"cran-0%2E0%2E1":{"latest_completed_workflows":{"build":{"status":"failed","created_at":"2020-08-07T11:51:42.356Z","id":"2bc7b6a4-5dd2-49c1-a87f-d250f27400c2"}},"latest_workflows":{"build":{"status":"failed","created_at":"2020-08-07T11:51:42.356Z","id":"2bc7b6a4-5dd2-49c1-a87f-d250f27400c2"}},"is_using_workflows":true}},"oss":true,"reponame":"tic.package","parallel":1,"username":"ropensci","has_usable_key":false,"vcs_type":"github","language":null,"vcs_url":"https://github.com/ropensci/tic.package","following":true,"default_branch":"master"},{"branches":{"master":{"latest_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-10-14T19:28:28.426Z","id":"9c8808fa-f043-416c-a8e1-4fc86bc19ac0"},"build_and_deploy":{"status":"success","created_at":"2020-08-29T20:21:14.587Z","id":"8b780a64-7d7b-410d-b114-88a1f25f9c0f"},"nightly":{"status":"success","created_at":"2020-11-03T04:00:51.542Z","id":"4db4e6b0-837d-4593-acf9-7e292983af04"},"build":{"status":"success","created_at":"2020-11-03T11:35:07.299Z","id":"fc3a4ac2-8cf7-44c6-bda0-284592a4bccc"}},"pusher_logins":["pat-s","sckott"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":699,"vcs_revision":"e1f64ee88ff28ee54849fc78d477a8a312fc19b9","pushed_at":"2020-11-03T11:35:07.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T11:36:35.478Z"},{"outcome":"success","status":"success","build_num":698,"vcs_revision":"b30f76f4e2eaf553dc37e5ebe067b5b821310805","pushed_at":"2020-11-03T11:04:49.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T11:06:40.256Z"},{"outcome":"success","status":"success","build_num":691,"vcs_revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","pushed_at":"2020-11-03T04:00:42.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T04:06:28.303Z"},{"outcome":"success","status":"success","build_num":688,"vcs_revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","pushed_at":"2020-11-02T04:00:38.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-02T04:06:06.827Z"},{"outcome":"success","status":"success","build_num":687,"vcs_revision":"74174fead6bf47d9c06ebefdbff70558f780d90c","pushed_at":"2020-11-01T04:00:35.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-01T04:05:55.325Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":660,"vcs_revision":"ca195c52c366ddc04d392576fcb80e3e7762eba5","pushed_at":"2020-10-08T04:00:03.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-08T04:00:54.182Z"},"latest_completed_workflows":{"Build%20Error":{"status":"failed","created_at":"2019-10-14T19:28:28.426Z","id":"9c8808fa-f043-416c-a8e1-4fc86bc19ac0"},"build_and_deploy":{"status":"success","created_at":"2020-08-29T20:21:14.587Z","id":"8b780a64-7d7b-410d-b114-88a1f25f9c0f"},"nightly":{"status":"success","created_at":"2020-11-03T04:00:51.542Z","id":"4db4e6b0-837d-4593-acf9-7e292983af04"},"build":{"status":"success","created_at":"2020-11-03T11:35:07.299Z","id":"fc3a4ac2-8cf7-44c6-bda0-284592a4bccc"}},"last_success":{"outcome":"success","status":"success","build_num":699,"vcs_revision":"e1f64ee88ff28ee54849fc78d477a8a312fc19b9","pushed_at":"2020-11-03T11:35:07.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T11:36:35.478Z"},"is_using_workflows":true},"gh-pages":{"latest_workflows":{"Build%20Error":{"status":"running","created_at":"2020-08-29T20:25:40.105Z","id":"b1d753b9-7c4a-48d6-b885-eda33505a991"}},"pusher_logins":["pat-s"],"is_using_workflows":true},"tests":{"latest_workflows":{"build_and_deploy":{"status":"failed","created_at":"2019-11-04T22:36:38.819Z","id":"9ddcbc07-ac82-48e4-81ef-518ddb5693a3"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"failed","status":"failed","build_num":100,"vcs_revision":"091da546c524a504cb470024f6c45bf637e884d2","pushed_at":"2019-11-04T22:36:38.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-11-04T22:37:55.688Z"},{"outcome":"failed","status":"failed","build_num":99,"vcs_revision":"f653586fae33e801a0321297dae98c3910312d93","pushed_at":"2019-11-04T21:11:00.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-11-04T21:13:14.120Z"},{"outcome":"failed","status":"failed","build_num":98,"vcs_revision":"eb43c18d8f0cacff352f7bc76956e55f27a6ab3f","pushed_at":"2019-11-04T21:04:26.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-11-04T21:05:41.281Z"},{"outcome":"failed","status":"failed","build_num":97,"vcs_revision":"d16ac7ecddd801abb866cc42f5dcc39bf3f4170d","pushed_at":"2019-11-04T20:57:57.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-11-04T20:59:41.418Z"},{"outcome":"failed","status":"failed","build_num":96,"vcs_revision":"c8b767dc0d690454d79b756a6878faeea09e7415","pushed_at":"2019-11-04T20:23:37.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-11-04T20:25:19.058Z"}],"last_success":{"outcome":"success","status":"success","build_num":94,"vcs_revision":"f51b07586e6f8b1f3551dbaaa351c27cb9e7ce61","pushed_at":"2019-11-04T19:49:41.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-11-04T19:50:47.250Z"},"latest_completed_workflows":{"build_and_deploy":{"status":"failed","created_at":"2019-11-04T22:36:38.819Z","id":"9ddcbc07-ac82-48e4-81ef-518ddb5693a3"}},"last_non_success":{"outcome":"failed","status":"failed","build_num":100,"vcs_revision":"091da546c524a504cb470024f6c45bf637e884d2","pushed_at":"2019-11-04T22:36:38.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2019-11-04T22:37:55.688Z"},"is_using_workflows":true},"codecov":{"latest_workflows":{"build":{"status":"running","created_at":"2020-11-03T11:39:21.275Z","id":"b379609a-6689-482c-a2c7-5f14d217df67"}},"pusher_logins":["pat-s"],"running_builds":[{"outcome":null,"status":"running","build_num":700,"vcs_revision":"82a0e3eb20c2602186b4ce27e72b2eab88187a89","pushed_at":"2020-11-02T19:45:34.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T11:39:23.727Z"}],"is_using_workflows":true}},"oss":true,"reponame":"circle","parallel":1,"username":"ropensci","has_usable_key":false,"vcs_type":"github","language":null,"vcs_url":"https://github.com/ropensci/circle","following":true,"default_branch":"master"},{"branches":{"master":{"latest_workflows":{"Build%20Error":{"status":"failed","created_at":"2020-10-08T04:18:01.746Z","id":"b78dd313-f4bc-42bd-a0d3-7976d502e6ec"}},"pusher_logins":["pat-s"],"running_builds":[],"recent_builds":[{"outcome":"failed","status":"failed","build_num":1383,"vcs_revision":"8456820944ace5533326d65454dc1e22feb3c59f","pushed_at":"2020-10-08T04:18:01.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-08T04:18:09.949Z"},{"outcome":"failed","status":"failed","build_num":1382,"vcs_revision":"8456820944ace5533326d65454dc1e22feb3c59f","pushed_at":"2020-10-08T04:17:55.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-08T04:18:01.411Z"},{"outcome":"failed","status":"failed","build_num":1379,"vcs_revision":"8456820944ace5533326d65454dc1e22feb3c59f","pushed_at":"2020-10-08T04:17:25.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-08T04:17:40.900Z"},{"outcome":"failed","status":"failed","build_num":1377,"vcs_revision":"8456820944ace5533326d65454dc1e22feb3c59f","pushed_at":"2020-10-08T04:17:01.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-08T04:17:07.040Z"},{"outcome":"failed","status":"failed","build_num":1375,"vcs_revision":"8456820944ace5533326d65454dc1e22feb3c59f","pushed_at":"2020-10-07T13:58:26.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-07T13:58:36.496Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":1383,"vcs_revision":"8456820944ace5533326d65454dc1e22feb3c59f","pushed_at":"2020-10-08T04:18:01.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-08T04:18:09.949Z"},"latest_completed_workflows":{"Build%20Error":{"status":"failed","created_at":"2020-10-08T04:18:01.746Z","id":"b78dd313-f4bc-42bd-a0d3-7976d502e6ec"}},"is_using_workflows":true}},"oss":true,"reponame":"travis-testthat","parallel":1,"username":"pat-s","has_usable_key":false,"vcs_type":"github","language":null,"vcs_url":"https://github.com/pat-s/travis-testthat","following":true,"default_branch":"master"},{"branches":{"master":{"latest_workflows":{"Build%20Error":{"status":"failed","created_at":"2020-01-17T18:05:35.580Z","id":"33935d3f-13e1-4b73-a46d-840f34d48f0f"},"build_and_deploy":{"status":"running","created_at":"2020-10-08T15:52:40.748Z","id":"988094ac-4f7d-4996-a2b5-31532db8969f"},"nightly":{"status":"success","created_at":"2020-11-03T03:00:39.426Z","id":"03b515d8-9c0d-4884-abac-bab884e39885"}},"pusher_logins":["pat-s","RaphaelS1"],"running_builds":[],"recent_builds":[{"outcome":"success","status":"success","build_num":1867,"vcs_revision":"d375e6aab42c5943eaee2ea6481cb37fba59baa5","pushed_at":"2020-11-03T03:00:39.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T03:03:46.905Z"},{"outcome":"success","status":"success","build_num":1866,"vcs_revision":"d375e6aab42c5943eaee2ea6481cb37fba59baa5","pushed_at":"2020-11-03T03:00:39.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T03:02:44.718Z"},{"outcome":"success","status":"success","build_num":1865,"vcs_revision":"d375e6aab42c5943eaee2ea6481cb37fba59baa5","pushed_at":"2020-11-02T03:00:22.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-02T03:03:59.372Z"},{"outcome":"success","status":"success","build_num":1864,"vcs_revision":"d375e6aab42c5943eaee2ea6481cb37fba59baa5","pushed_at":"2020-11-02T03:00:22.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-02T03:02:11.544Z"},{"outcome":"success","status":"success","build_num":1863,"vcs_revision":"d375e6aab42c5943eaee2ea6481cb37fba59baa5","pushed_at":"2020-11-01T03:00:21.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-01T03:03:35.150Z"}],"last_non_success":{"outcome":"failed","status":"failed","build_num":1812,"vcs_revision":"c4ca20ba005ebfc68479994531d1f60a474604c4","pushed_at":"2020-10-08T13:44:59.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-10-08T13:48:06.403Z"},"latest_completed_workflows":{"Build%20Error":{"status":"failed","created_at":"2020-01-17T18:05:35.580Z","id":"33935d3f-13e1-4b73-a46d-840f34d48f0f"},"build_and_deploy":{"status":"success","created_at":"2020-10-08T15:48:10.851Z","id":"e3ff0931-0996-4418-bb06-74d8f01fe3a1"},"nightly":{"status":"success","created_at":"2020-11-03T03:00:39.426Z","id":"03b515d8-9c0d-4884-abac-bab884e39885"}},"last_success":{"outcome":"success","status":"success","build_num":1867,"vcs_revision":"d375e6aab42c5943eaee2ea6481cb37fba59baa5","pushed_at":"2020-11-03T03:00:39.000Z","is_workflow_job":true,"is_2_0_job":true,"added_at":"2020-11-03T03:03:46.905Z"},"is_using_workflows":true}},"oss":true,"reponame":"mlr3learners.drat","parallel":1,"username":"mlr3learners","has_usable_key":false,"vcs_type":"github","language":null,"vcs_url":"https://github.com/mlr3learners/mlr3learners.drat","following":true,"default_branch":"master"}]' 40 | recorded_at: 2020-11-03 11:39:41 GMT 41 | recorded_with: vcr/0.5.4, webmockr/0.7.0 42 | --------------------------------------------------------------------------------