R CI/CD Pipelines Best Practices¶
Objective: Master senior-level R CI/CD pipeline patterns for production systems. When you need to automate testing, building, and deployment, when you want to ensure code quality and reliability, when you need enterprise-grade CI/CD patternsโthese best practices become your weapon of choice.
Core Principles¶
- Automation: Automate all repetitive tasks
- Quality Gates: Implement quality checks at every stage
- Fast Feedback: Provide quick feedback to developers
- Reliability: Ensure consistent and reliable builds
- Security: Integrate security checks throughout the pipeline
GitHub Actions¶
Basic GitHub Actions Setup¶
# R/01-github-actions.R
#' Create comprehensive GitHub Actions workflow
#'
#' @param workflow_config Workflow configuration
#' @return GitHub Actions workflow
create_github_actions_workflow <- function(workflow_config) {
workflow <- list(
name = workflow_config$name %||% "R CI/CD Pipeline",
triggers = create_workflow_triggers(workflow_config),
jobs = create_workflow_jobs(workflow_config)
)
return(workflow)
}
#' Create workflow triggers
#'
#' @param workflow_config Workflow configuration
#' @return Workflow triggers
create_workflow_triggers <- function(workflow_config) {
triggers <- c(
"on:",
" push:",
" branches: [ main, develop ]",
" pull_request:",
" branches: [ main ]",
" schedule:",
" - cron: '0 0 * * *' # Daily at midnight"
)
return(triggers)
}
#' Create workflow jobs
#'
#' @param workflow_config Workflow configuration
#' @return Workflow jobs
create_workflow_jobs <- function(workflow_config) {
jobs <- list(
test = create_test_job(workflow_config),
build = create_build_job(workflow_config),
deploy = create_deploy_job(workflow_config)
)
return(jobs)
}
#' Create test job
#'
#' @param workflow_config Workflow configuration
#' @return Test job configuration
create_test_job <- function(workflow_config) {
test_job <- c(
" test:",
" runs-on: ${{ matrix.config.os }}",
" strategy:",
" matrix:",
" config:",
" - {os: windows-latest, r: 'release'}",
" - {os: macOS-latest, r: 'release'}",
" - {os: ubuntu-latest, r: 'release'}",
" steps:",
" - uses: actions/checkout@v3",
" - uses: r-lib/actions/setup-r@v2",
" with:",
" r-version: ${{ matrix.config.r }}",
" - uses: r-lib/actions/setup-pandoc@v2",
" - name: Install dependencies",
" run: |",
" install.packages(c(\"remotes\", \"rcmdcheck\"))",
" remotes::install_deps(dependencies = TRUE)",
" shell: Rscript {0}",
" - name: Check",
" run: rcmdcheck::rcmdcheck(args = c(\"--no-manual\", \"--as-cran\"), error_on = \"warning\")",
" shell: Rscript {0}"
)
return(test_job)
}
#' Create build job
#'
#' @param workflow_config Workflow configuration
#' @return Build job configuration
create_build_job <- function(workflow_config) {
build_job <- c(
" build:",
" needs: test",
" runs-on: ubuntu-latest",
" steps:",
" - uses: actions/checkout@v3",
" - name: Build package",
" run: |",
" R CMD build .",
" shell: bash",
" - name: Upload build artifacts",
" uses: actions/upload-artifact@v3",
" with:",
" name: r-package",
" path: \"*.tar.gz\""
)
return(build_job)
}
#' Create deploy job
#'
#' @param workflow_config Workflow configuration
#' @return Deploy job configuration
create_deploy_job <- function(workflow_config) {
deploy_job <- c(
" deploy:",
" needs: build",
" runs-on: ubuntu-latest",
" if: github.ref == 'refs/heads/main'",
" steps:",
" - name: Download build artifacts",
" uses: actions/download-artifact@v3",
" with:",
" name: r-package",
" - name: Deploy to CRAN",
" run: |",
" # Deploy to CRAN or other repository",
" echo \"Deploying package...\"",
" shell: bash"
)
return(deploy_job)
}
Advanced GitHub Actions¶
# R/01-github-actions.R (continued)
#' Create advanced GitHub Actions workflow
#'
#' @param workflow_config Workflow configuration
#' @return Advanced GitHub Actions workflow
create_advanced_github_actions_workflow <- function(workflow_config) {
workflow <- list(
name = workflow_config$name %||% "Advanced R CI/CD Pipeline",
triggers = create_advanced_triggers(workflow_config),
jobs = create_advanced_jobs(workflow_config)
)
return(workflow)
}
#' Create advanced triggers
#'
#' @param workflow_config Workflow configuration
#' @return Advanced triggers
create_advanced_triggers <- function(workflow_config) {
triggers <- c(
"on:",
" push:",
" branches: [ main, develop ]",
" paths:",
" - 'R/**'",
" - 'tests/**'",
" - 'DESCRIPTION'",
" pull_request:",
" branches: [ main ]",
" workflow_dispatch:",
" inputs:",
" environment:",
" description: 'Environment to deploy to'",
" required: true",
" default: 'staging'",
" type: choice",
" options:",
" - staging",
" - production"
)
return(triggers)
}
#' Create advanced jobs
#'
#' @param workflow_config Workflow configuration
#' @return Advanced jobs
create_advanced_jobs <- function(workflow_config) {
jobs <- list(
lint = create_lint_job(workflow_config),
test = create_advanced_test_job(workflow_config),
security = create_security_job(workflow_config),
build = create_advanced_build_job(workflow_config),
deploy = create_advanced_deploy_job(workflow_config)
)
return(jobs)
}
#' Create lint job
#'
#' @param workflow_config Workflow configuration
#' @return Lint job configuration
create_lint_job <- function(workflow_config) {
lint_job <- c(
" lint:",
" runs-on: ubuntu-latest",
" steps:",
" - uses: actions/checkout@v3",
" - uses: r-lib/actions/setup-r@v2",
" - name: Install lintr",
" run: install.packages(\"lintr\")",
" shell: Rscript {0}",
" - name: Run lintr",
" run: lintr::lint_package()",
" shell: Rscript {0}"
)
return(lint_job)
}
#' Create advanced test job
#'
#' @param workflow_config Workflow configuration
#' @return Advanced test job configuration
create_advanced_test_job <- function(workflow_config) {
test_job <- c(
" test:",
" needs: lint",
" runs-on: ${{ matrix.config.os }}",
" strategy:",
" matrix:",
" config:",
" - {os: windows-latest, r: 'release'}",
" - {os: macOS-latest, r: 'release'}",
" - {os: ubuntu-latest, r: 'release'}",
" steps:",
" - uses: actions/checkout@v3",
" - uses: r-lib/actions/setup-r@v2",
" with:",
" r-version: ${{ matrix.config.r }}",
" - uses: r-lib/actions/setup-pandoc@v2",
" - name: Install dependencies",
" run: |",
" install.packages(c(\"remotes\", \"rcmdcheck\", \"testthat\"))",
" remotes::install_deps(dependencies = TRUE)",
" shell: Rscript {0}",
" - name: Run tests",
" run: testthat::test_dir(\"tests\")",
" shell: Rscript {0}",
" - name: Check package",
" run: rcmdcheck::rcmdcheck(args = c(\"--no-manual\", \"--as-cran\"), error_on = \"warning\")",
" shell: Rscript {0}",
" - name: Upload test results",
" uses: actions/upload-artifact@v3",
" if: always()",
" with:",
" name: test-results",
" path: test-results/"
)
return(test_job)
}
#' Create security job
#'
#' @param workflow_config Workflow configuration
#' @return Security job configuration
create_security_job <- function(workflow_config) {
security_job <- c(
" security:",
" runs-on: ubuntu-latest",
" steps:",
" - uses: actions/checkout@v3",
" - name: Run security scan",
" run: |",
" # Run security scanning tools",
" echo \"Running security scan...\"",
" shell: bash",
" - name: Check for vulnerabilities",
" run: |",
" # Check for known vulnerabilities",
" echo \"Checking for vulnerabilities...\"",
" shell: bash"
)
return(security_job)
}
GitLab CI/CD¶
GitLab CI Configuration¶
# R/02-gitlab-ci.R
#' Create GitLab CI configuration
#'
#' @param ci_config CI configuration
#' @return GitLab CI configuration
create_gitlab_ci_configuration <- function(ci_config) {
config <- list(
stages = create_ci_stages(ci_config),
variables = create_ci_variables(ci_config),
jobs = create_ci_jobs(ci_config)
)
return(config)
}
#' Create CI stages
#'
#' @param ci_config CI configuration
#' @return CI stages
create_ci_stages <- function(ci_config) {
stages <- c(
"stages:",
" - lint",
" - test",
" - build",
" - security",
" - deploy"
)
return(stages)
}
#' Create CI variables
#'
#' @param ci_config CI configuration
#' @return CI variables
create_ci_variables <- function(ci_config) {
variables <- c(
"variables:",
" R_VERSION: \"4.3.0\"",
" R_OPTIONS: \"--no-restore --no-save\"",
" R_LIBS_USER: \"$CI_PROJECT_DIR/.R/library\"",
" R_REMOTES_NO_ERRORS_FROM_WARNINGS: \"true\""
)
return(variables)
}
#' Create CI jobs
#'
#' @param ci_config CI configuration
#' @return CI jobs
create_ci_jobs <- function(ci_config) {
jobs <- list(
lint = create_gitlab_lint_job(ci_config),
test = create_gitlab_test_job(ci_config),
build = create_gitlab_build_job(ci_config),
security = create_gitlab_security_job(ci_config),
deploy = create_gitlab_deploy_job(ci_config)
)
return(jobs)
}
#' Create GitLab lint job
#'
#' @param ci_config CI configuration
#' @return GitLab lint job
create_gitlab_lint_job <- function(ci_config) {
lint_job <- c(
"lint:",
" stage: lint",
" image: rocker/r-ver:4.3.0",
" before_script:",
" - apt-get update -qq && apt-get install -y -qq git",
" - Rscript -e 'install.packages(c(\"remotes\", \"lintr\"))'",
" script:",
" - Rscript -e 'lintr::lint_package()'",
" artifacts:",
" reports:",
" junit: lint-results.xml"
)
return(lint_job)
}
#' Create GitLab test job
#'
#' @param ci_config CI configuration
#' @return GitLab test job
create_gitlab_test_job <- function(ci_config) {
test_job <- c(
"test:",
" stage: test",
" image: rocker/r-ver:4.3.0",
" before_script:",
" - apt-get update -qq && apt-get install -y -qq git pandoc",
" - Rscript -e 'install.packages(c(\"remotes\", \"testthat\", \"rcmdcheck\"))'",
" - Rscript -e 'remotes::install_deps(dependencies = TRUE)'",
" script:",
" - Rscript -e 'testthat::test_dir(\"tests\")'",
" - Rscript -e 'rcmdcheck::rcmdcheck(args = c(\"--no-manual\", \"--as-cran\"), error_on = \"warning\")'",
" artifacts:",
" reports:",
" junit: test-results.xml",
" paths:",
" - test-results/"
)
return(test_job)
}
#' Create GitLab build job
#'
#' @param ci_config CI configuration
#' @return GitLab build job
create_gitlab_build_job <- function(ci_config) {
build_job <- c(
"build:",
" stage: build",
" image: rocker/r-ver:4.3.0",
" before_script:",
" - apt-get update -qq && apt-get install -y -qq git pandoc",
" - Rscript -e 'install.packages(c(\"remotes\", \"rcmdcheck\"))'",
" - Rscript -e 'remotes::install_deps(dependencies = TRUE)'",
" script:",
" - R CMD build .",
" artifacts:",
" paths:",
" - \"*.tar.gz\"",
" expire_in: 1 week"
)
return(build_job)
}
Jenkins Pipelines¶
Jenkins Pipeline Setup¶
# R/03-jenkins-pipelines.R
#' Create Jenkins pipeline
#'
#' @param pipeline_config Pipeline configuration
#' @return Jenkins pipeline
create_jenkins_pipeline <- function(pipeline_config) {
pipeline <- list(
agent = create_jenkins_agent(pipeline_config),
stages = create_jenkins_stages(pipeline_config),
post = create_jenkins_post(pipeline_config)
)
return(pipeline)
}
#' Create Jenkins agent
#'
#' @param pipeline_config Pipeline configuration
#' @return Jenkins agent configuration
create_jenkins_agent <- function(pipeline_config) {
agent <- c(
"pipeline {",
" agent {",
" label 'r-build'",
" }",
"",
" environment {",
" R_VERSION = '4.3.0'",
" R_OPTIONS = '--no-restore --no-save'",
" R_LIBS_USER = '${WORKSPACE}/.R/library'",
" }"
)
return(agent)
}
#' Create Jenkins stages
#'
#' @param pipeline_config Pipeline configuration
#' @return Jenkins stages
create_jenkins_stages <- function(pipeline_config) {
stages <- c(
" stages {",
" stage('Checkout') {",
" steps {",
" checkout scm",
" }",
" }",
"",
" stage('Install Dependencies') {",
" steps {",
" sh 'Rscript -e \"install.packages(c(\\\"remotes\\\", \\\"rcmdcheck\\\", \\\"testthat\\\"))\"'",
" sh 'Rscript -e \"remotes::install_deps(dependencies = TRUE)\"'",
" }",
" }",
"",
" stage('Lint') {",
" steps {",
" sh 'Rscript -e \"lintr::lint_package()\"'",
" }",
" }",
"",
" stage('Test') {",
" steps {",
" sh 'Rscript -e \"testthat::test_dir(\\\"tests\\\")\"'",
" }",
" }",
"",
" stage('Build') {",
" steps {",
" sh 'R CMD build .'",
" }",
" }",
"",
" stage('Check') {",
" steps {",
" sh 'R CMD check *.tar.gz'",
" }",
" }",
" }"
)
return(stages)
}
#' Create Jenkins post actions
#'
#' @param pipeline_config Pipeline configuration
#' @return Jenkins post actions
create_jenkins_post <- function(pipeline_config) {
post <- c(
" post {",
" always {",
" archiveArtifacts artifacts: '*.tar.gz', fingerprint: true",
" publishTestResults testResultsPattern: 'test-results/*.xml'",
" }",
" success {",
" echo 'Pipeline succeeded!'",
" }",
" failure {",
" echo 'Pipeline failed!'",
" }",
" }",
"}"
)
return(post)
}
Quality Gates¶
Code Quality Checks¶
# R/04-quality-gates.R
#' Create quality gates
#'
#' @param quality_config Quality configuration
#' @return Quality gates
create_quality_gates <- function(quality_config) {
gates <- list(
code_quality = create_code_quality_gate(quality_config),
test_coverage = create_test_coverage_gate(quality_config),
security_checks = create_security_checks_gate(quality_config),
performance_checks = create_performance_checks_gate(quality_config)
)
return(gates)
}
#' Create code quality gate
#'
#' @param quality_config Quality configuration
#' @return Code quality gate
create_code_quality_gate <- function(quality_config) {
code_quality_gate <- c(
"# Code Quality Gate",
"code_quality:",
" stage: quality",
" image: rocker/r-ver:4.3.0",
" before_script:",
" - Rscript -e 'install.packages(c(\"lintr\", \"styler\"))'",
" script:",
" - Rscript -e 'lintr::lint_package()'",
" - Rscript -e 'styler::style_pkg()'",
" rules:",
" - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'",
" - if: '$CI_COMMIT_BRANCH == \"main\"'"
)
return(code_quality_gate)
}
#' Create test coverage gate
#'
#' @param quality_config Quality configuration
#' @return Test coverage gate
create_test_coverage_gate <- function(quality_config) {
test_coverage_gate <- c(
"# Test Coverage Gate",
"test_coverage:",
" stage: test",
" image: rocker/r-ver:4.3.0",
" before_script:",
" - Rscript -e 'install.packages(c(\"covr\", \"testthat\"))'",
" - Rscript -e 'remotes::install_deps(dependencies = TRUE)'",
" script:",
" - Rscript -e 'covr::package_coverage()'",
" coverage: '/TOTAL.*\\s+(\\d+%)/'",
" artifacts:",
" reports:",
" coverage_report: coverage.xml"
)
return(test_coverage_gate)
}
#' Create security checks gate
#'
#' @param quality_config Quality configuration
#' @return Security checks gate
create_security_checks_gate <- function(quality_config) {
security_checks_gate <- c(
"# Security Checks Gate",
"security_checks:",
" stage: security",
" image: rocker/r-ver:4.3.0",
" before_script:",
" - Rscript -e 'install.packages(c(\"safety\", \"audit\"))'",
" script:",
" - Rscript -e 'safety::check_packages()'",
" - Rscript -e 'audit::audit_package()'",
" rules:",
" - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'",
" - if: '$CI_COMMIT_BRANCH == \"main\"'"
)
return(security_checks_gate)
}
#' Create performance checks gate
#'
#' @param quality_config Quality configuration
#' @return Performance checks gate
create_performance_checks_gate <- function(quality_config) {
performance_checks_gate <- c(
"# Performance Checks Gate",
"performance_checks:",
" stage: performance",
" image: rocker/r-ver:4.3.0",
" before_script:",
" - Rscript -e 'install.packages(c(\"profvis\", \"bench\"))'",
" script:",
" - Rscript -e 'profvis::profvis({source(\"benchmark.R\")})'",
" - Rscript -e 'bench::mark({source(\"benchmark.R\")})'",
" rules:",
" - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'",
" - if: '$CI_COMMIT_BRANCH == \"main\"'"
)
return(performance_checks_gate)
}
Deployment Strategies¶
Deployment Automation¶
# R/05-deployment-strategies.R
#' Create deployment automation
#'
#' @param deployment_config Deployment configuration
#' @return Deployment automation
create_deployment_automation <- function(deployment_config) {
automation <- list(
staging_deployment = create_staging_deployment(deployment_config),
production_deployment = create_production_deployment(deployment_config),
rollback_strategy = create_rollback_strategy(deployment_config)
)
return(automation)
}
#' Create staging deployment
#'
#' @param deployment_config Deployment configuration
#' @return Staging deployment
create_staging_deployment <- function(deployment_config) {
staging_deployment <- c(
"# Staging Deployment",
"deploy_staging:",
" stage: deploy",
" image: rocker/r-ver:4.3.0",
" environment:",
" name: staging",
" url: https://staging.example.com",
" before_script:",
" - apt-get update -qq && apt-get install -y -qq curl",
" script:",
" - echo 'Deploying to staging...'",
" - curl -X POST $STAGING_WEBHOOK_URL",
" only:",
" - develop",
" - merge_requests"
)
return(staging_deployment)
}
#' Create production deployment
#'
#' @param deployment_config Deployment configuration
#' @return Production deployment
create_production_deployment <- function(deployment_config) {
production_deployment <- c(
"# Production Deployment",
"deploy_production:",
" stage: deploy",
" image: rocker/r-ver:4.3.0",
" environment:",
" name: production",
" url: https://production.example.com",
" before_script:",
" - apt-get update -qq && apt-get install -y -qq curl",
" script:",
" - echo 'Deploying to production...'",
" - curl -X POST $PRODUCTION_WEBHOOK_URL",
" only:",
" - main",
" when: manual"
)
return(production_deployment)
}
#' Create rollback strategy
#'
#' @param deployment_config Deployment configuration
#' @return Rollback strategy
create_rollback_strategy <- function(deployment_config) {
rollback_strategy <- c(
"# Rollback Strategy",
"rollback:",
" stage: rollback",
" image: rocker/r-ver:4.3.0",
" environment:",
" name: production",
" action: rollback",
" script:",
" - echo 'Rolling back deployment...'",
" - curl -X POST $ROLLBACK_WEBHOOK_URL",
" when: manual",
" only:",
" - main"
)
return(rollback_strategy)
}
Monitoring and Alerting¶
Pipeline Monitoring¶
# R/06-monitoring-alerting.R
#' Create pipeline monitoring
#'
#' @param monitoring_config Monitoring configuration
#' @return Pipeline monitoring
create_pipeline_monitoring <- function(monitoring_config) {
monitoring <- list(
metrics = create_pipeline_metrics(monitoring_config),
alerts = create_pipeline_alerts(monitoring_config),
dashboards = create_pipeline_dashboards(monitoring_config)
)
return(monitoring)
}
#' Create pipeline metrics
#'
#' @param monitoring_config Monitoring configuration
#' @return Pipeline metrics
create_pipeline_metrics <- function(monitoring_config) {
metrics <- c(
"# Pipeline Metrics",
"pipeline_metrics:",
" stage: metrics",
" image: rocker/r-ver:4.3.0",
" script:",
" - echo 'Collecting pipeline metrics...'",
" - Rscript -e 'source(\"metrics.R\")'",
" artifacts:",
" reports:",
" junit: metrics.xml",
" paths:",
" - metrics/"
)
return(metrics)
}
#' Create pipeline alerts
#'
#' @param monitoring_config Monitoring configuration
#' @return Pipeline alerts
create_pipeline_alerts <- function(monitoring_config) {
alerts <- c(
"# Pipeline Alerts",
"pipeline_alerts:",
" stage: alerts",
" image: rocker/r-ver:4.3.0",
" script:",
" - echo 'Setting up pipeline alerts...'",
" - Rscript -e 'source(\"alerts.R\")'",
" rules:",
" - if: '$CI_PIPELINE_STATUS == \"failed\"'",
" - if: '$CI_PIPELINE_STATUS == \"success\"'"
)
return(alerts)
}
#' Create pipeline dashboards
#'
#' @param monitoring_config Monitoring configuration
#' @return Pipeline dashboards
create_pipeline_dashboards <- function(monitoring_config) {
dashboards <- c(
"# Pipeline Dashboards",
"pipeline_dashboards:",
" stage: dashboards",
" image: rocker/r-ver:4.3.0",
" script:",
" - echo 'Creating pipeline dashboards...'",
" - Rscript -e 'source(\"dashboards.R\")'",
" artifacts:",
" paths:",
" - dashboards/"
)
return(dashboards)
}
TL;DR Runbook¶
Quick Start¶
# 1. Create GitHub Actions workflow
github_workflow <- create_github_actions_workflow(workflow_config)
# 2. Create GitLab CI configuration
gitlab_ci <- create_gitlab_ci_configuration(ci_config)
# 3. Create Jenkins pipeline
jenkins_pipeline <- create_jenkins_pipeline(pipeline_config)
# 4. Create quality gates
quality_gates <- create_quality_gates(quality_config)
# 5. Create deployment automation
deployment_automation <- create_deployment_automation(deployment_config)
Essential Patterns¶
# Complete CI/CD pipeline
create_cicd_pipeline <- function(pipeline_config) {
# Create workflow
workflow <- create_github_actions_workflow(pipeline_config$workflow_config)
# Create quality gates
quality_gates <- create_quality_gates(pipeline_config$quality_config)
# Create deployment automation
deployment_automation <- create_deployment_automation(pipeline_config$deployment_config)
# Create monitoring
monitoring <- create_pipeline_monitoring(pipeline_config$monitoring_config)
return(list(
workflow = workflow,
quality_gates = quality_gates,
deployment = deployment_automation,
monitoring = monitoring
))
}
This guide provides the complete machinery for implementing CI/CD pipelines for R applications. Each pattern includes implementation examples, quality control strategies, and real-world usage patterns for enterprise deployment.