Compartilhar via


CI/CD com o Jenkins no Azure Databricks

Observação

Este artigo aborda o Jenkins, que não é fornecido nem tem suporte do Databricks. Para entrar em contato com o provedor, confira o Ajuda do Jenkins.

Existem várias ferramentas de CI/CD que podem ser utilizadas para gerenciar e executar seus pipelines de CI/CD. Este artigo ilustra como usar o servidor de automação do Jenkins. CI/CD é um padrão de design, portanto, as etapas e os estágios descritos neste artigo devem ser transferidos com algumas alterações para a linguagem de definição de pipeline de cada ferramenta. Além disso, grande parte do código neste pipeline de exemplo executa o código Python padrão, que você pode invocar em outras ferramentas. Para obter uma visão geral da CI/CD no Azure Databricks, consulte O que é CI/CD no Azure Databricks?.

Para obter informações sobre como utilizar o Azure DevOps com o Azure Databricks, consulte Integração e entrega contínuas no Azure Databricks usando o Azure DevOps.

Fluxo de trabalho de desenvolvimento de CI/CD

O Databricks sugere o seguinte fluxo de trabalho para o desenvolvimento de CI/CD com o Jenkins:

  1. Crie um repositório ou use um repositório existente com seu provedor Git de terceiros.
  2. Conecte seu computador de desenvolvimento local no mesmo repositório de terceiros. Para obter instruções, consulte a documentação do seu provedor Git de terceiros.
  3. Efetue pull de todos os artefatos atualizados existentes (como notebooks, arquivos de código e scripts de compilação) do repositório de terceiros para seu computador de desenvolvimento local.
  4. Conforme desejado, crie, atualize e teste artefatos em seu computador de desenvolvimento local. Em seguida, efetue push de todos os artefatos novos e alterados do seu computador de desenvolvimento local para o repositório de terceiros. Para obter instruções, consulte a documentação do seu provedor Git de terceiros.
  5. Repita as etapas 3 e 4 conforme necessário.
  6. Use o Jenkins periodicamente como uma abordagem integrada para extrair automaticamente artefatos de seu repositório de terceiros para seu computador de desenvolvimento local ou workspace do Azure Databricks; criar, testar e executar código em seu computador de desenvolvimento local ou no workspace do Azure Databricks; e relatar resultados de testes e execuções. Embora você possa executar o Jenkins manualmente, em implementações do mundo real você instruiria seu provedor Git de terceiros a executar o Jenkins sempre que um evento específico acontecesse, como um pull request do repositório.

O restante deste artigo usa um projeto de exemplo para descrever uma maneira de usar o Jenkins para implementar o fluxo de trabalho de desenvolvimento de CI/CD anterior.

Para obter informações sobre como usar o Azure DevOps ao invés do Jenkins, consulte Integração e entrega contínuas no Azure Databricks usando o Azure DevOps.

Configuração do computador de desenvolvimento local

O exemplo deste artigo usa o Jenkins para instruir a CLI do Databricks e os Pacotes de Ativos do Databricks a fazer o seguinte:

  1. Crie um arquivo wheel do Python no computador de desenvolvimento local.
  2. Implante o arquivo wheel do Python criado junto com arquivos Python adicionais e notebooks Python do seu computador de desenvolvimento local em um workspace do Azure Databricks.
  3. Teste e execute o arquivp wheel do Python e os notebooks carregados nesse workspace.

Para configurar seu computador de desenvolvimento local para instruir o workspace do Azure Databricks a executar os estágios de compilação e carregamento deste exemplo, faça o seguinte no computador de desenvolvimento local:

Etapa 1: instalar as ferramentas necessárias

Nesta etapa, você instala as ferramentas de compilação do pacote wheel da CLI do Databricks, Jenkins, jq e Python em seu computador de desenvolvimento local. Essas ferramentas são necessárias para executar este exemplo.

  1. Instale a CLI do Databricks versão 0.205 ou superior, se ainda não tiver feito isso. O Jenkins usa a CLI do Databricks para passar no teste deste exemplo e executar instruções no workspace. Consulte Instalar ou atualizar a CLI do Databricks.

  2. Instale e inicie o Jenkins, se ainda não tiver feito isso. Consulte Como instalar o Jenkins para Linux, macOS ou Windows.

  3. Instalar o jq. Este exemplo usa jq para analisar algumas saídas de comando formatadas em JSON.

  4. Use pip para instalar as Ferramentas de compilação do pacote wheel do Python com o seguinte comando (alguns sistemas podem exigir que você use pip3 em vez de pip):

    pip install --upgrade wheel
    

Etapa 2: criar um pipeline do Jenkins

Nesta etapa, você usa o Jenkins para criar um pipeline do Jenkins para o exemplo deste artigo. O Jenkins fornece alguns tipos de projeto diferentes para criar pipelines de CI/CD. O Pipelines do Jenkins fornece uma interface para definir estágios em um Pipeline do Jenkins usando o código Groovy para chamar e configurar plug-ins do Jenkins.

Tipos de projeto do Jenkins

Para criar o Pipeline do Jenkins no Jenkins:

  1. Depois de iniciar o Jenkins, no Painel do Jenkins, clique em Novo Item.
  2. Em Digite um nome de item, digite um nome para o Pipeline do Jenkins como, por exemplo, jenkins-demo.
  3. Clique no ícone de tipo de projeto Pipeline.
  4. Clique em OK. A página Configurar do pipeline do Jenkins é exibida.
  5. Na área Pipeline, na lista suspensa Definição, selecione Script de pipeline do SCM.
  6. Na lista suspensa do SCM, selecione Git.
  7. Em URL do repositório, digite a URL do repositório hospedado pelo provedor Git de terceiros.
  8. Para Especificador de Ramificação, digite */<branch-name>, onde <branch-name> é o nome da ramificação no repositório que você deseja usar, por exemplo, */main.
  9. Em Caminho do script, digite Jenkinsfile, se ainda não estiver definido. Você cria o Jenkinsfile mais adiante neste artigo.
  10. Desmarque a caixa intitulada Check-out leve, se já estiver marcada.
  11. Clique em Save (Salvar).

Etapa 3: adicionar variáveis de ambiente global ao Jenkins

Nesta etapa, você adiciona três variáveis de ambiente global ao Jenkins. O Jenkins passa essas variáveis de ambiente para a CLI do Databricks. A CLI do Databricks precisa obter os valores dessas variáveis de ambiente para autenticar com o workspace do Azure Databricks. Este exemplo usa a autenticação M2M (máquina a máquina) do OAuth para uma entidade de serviço (embora outros tipos de autenticação também estejam disponíveis). Para configurar a autenticação OAuth M2M para seu espaço de trabalho do Azure Databricks, consulte Autenticar o acesso ao Azure Databricks com uma entidade de serviço usando OAuth (OAuth M2M).

As três variáveis de ambiente global para este exemplo são:

  • DATABRICKS_HOST, foi definida como a URL de seu workspace do Azure Databricks, começando com https://. Confira Nomes, URLs e IDs das instâncias de workspace.
  • DATABRICKS_CLIENT_ID, definido como a ID do cliente da entidade de serviço, que também é conhecida como sua ID do aplicativo.
  • DATABRICKS_CLIENT_SECRET, definido como o segredo OAuth do Azure Databricks para o Databricks da entidade de serviço.

Para definir as variáveis de ambiente globais no Jenkins, no Painel do Jenkins:

  1. Na barra lateral, clique em Gerenciar o Jenkins.
  2. Na seção Configuração do sistema, clique em Sistema.
  3. Na seção Propriedades globais, marque a caixa Variáveis de ambiente.
  4. Clique em Adicionar e insira o Nome e o Valor da variável de ambiente. Repita esse procedimento para cada variável de ambiente adicional.
  5. Quando terminar de adicionar as variáveis de ambiente, clique em Salvar para voltar ao Painel do Jenkins.

Projetar o Pipeline do Jenkins

O Jenkins fornece alguns tipos de projeto diferentes para criar pipelines de CI/CD. Este exemplo implementa um Pipeline do Jenkins. O Pipelines do Jenkins fornece uma interface para definir estágios em um Pipeline do Jenkins usando o código Groovy para chamar e configurar plug-ins do Jenkins.

Você escreve uma definição de Pipeline do Jenkins em um arquivo de texto chamado de Jenkinsfile que, por sua vez, é verificado no repositório de controle de origem de um projeto. Para obter mais informações, confira Pipeline do Jenkins. Aqui está o Pipeline do Jenkins para o exemplo deste artigo. Neste exemplo Jenkinsfile, substitua os seguintes espaços reservados:

  • Substitua <user-name> e <repo-name> pelo nome de usuário e nome do repositório do seu hospedado pelo provedor Git de terceiros. Este artigo usa uma URL do GitHub como exemplo.
  • Substitua <release-branch-name> pelo nome da ramificação da versão no seu repositório. Por exemplo, poderia ser main.
  • Substitua <databricks-cli-installation-path> pelo caminho no seu computador de desenvolvimento local em que a CLI do Databricks está instalada. Por exemplo, no macOS, isso poderia ser /usr/local/bin.
  • Substitua <jq-installation-path> pelo caminho no seu computador de desenvolvimento local em que jq está instalado. Por exemplo, no macOS, isso poderia ser /usr/local/bin.
  • Substitua <job-prefix-name> por alguma cadeia de caracteres para ajudar a identificar exclusivamente os trabalhos do Azure Databricks criados em seu workspace para este exemplo. Por exemplo, poderia ser jenkins-demo.
  • Observe que BUNDLETARGET está definido como dev, que é o nome do destino do Pacote de Ativos do Databricks definido mais adiante neste artigo. Em implementações do mundo real, você alteraria isso para o nome do seu próprio destino do pacote. Mais detalhes sobre os destinos do pacote são fornecidos neste artigo.

Aqui está o Jenkinsfile, que deve ser adicionado à raiz do seu repositório:

// Filename: Jenkinsfile
node {
  def GITREPOREMOTE = "https://github.com/<user-name>/<repo-name>.git"
  def GITBRANCH     = "<release-branch-name>"
  def DBCLIPATH     = "<databricks-cli-installation-path>"
  def JQPATH        = "<jq-installation-path>"
  def JOBPREFIX     = "<job-prefix-name>"
  def BUNDLETARGET  = "dev"

  stage('Checkout') {
    git branch: GITBRANCH, url: GITREPOREMOTE
  }
  stage('Validate Bundle') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET}
       """
  }
  stage('Deploy Bundle') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle deploy -t ${BUNDLETARGET}
       """
  }
  stage('Run Unit Tests') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-unit-tests
       """
  }
  stage('Run Notebook') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-dabdemo-notebook
       """
  }
  stage('Evaluate Notebook Runs') {
    sh """#!/bin/bash
          ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} evaluate-notebook-runs
       """
  }
  stage('Import Test Results') {
    def DATABRICKS_BUNDLE_WORKSPACE_ROOT_PATH
    def getPath = "${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET} | ${JQPATH}/jq -r .workspace.file_path"
    def output = sh(script: getPath, returnStdout: true).trim()

    if (output) {
      DATABRICKS_BUNDLE_WORKSPACE_ROOT_PATH = "${output}"
    } else {
      error "Failed to capture output or command execution failed: ${getPath}"
    }

    sh """#!/bin/bash
          ${DBCLIPATH}/databricks workspace export-dir \
          ${DATABRICKS_BUNDLE_WORKSPACE_ROOT_PATH}/Validation/Output/test-results \
          ${WORKSPACE}/Validation/Output/test-results \
          -t ${BUNDLETARGET} \
          --overwrite
       """
  }
  stage('Publish Test Results') {
    junit allowEmptyResults: true, testResults: '**/test-results/*.xml', skipPublishingChecks: true
  }
}

O restante deste artigo descreve cada estágio neste pipeline do Jenkins e como configurar os artefatos e comandos para execução pelo Jenkins nesse estágio.

Extrair os artefatos mais recentes do repositório de terceiros

O primeiro estágio neste pipeline do Jenkins, o estágio Checkout, é definido da seguinte forma:

stage('Checkout') {
  git branch: GITBRANCH, url: GITREPOREMOTE
}

Este estágio garante que o diretório de trabalho usado pelo Jenkins em sua máquina de desenvolvimento local tenha os artefatos mais recentes do repositório Git de terceiros. Normalmente, o Jenkins define esse diretório de trabalho como <your-user-home-directory>/.jenkins/workspace/<pipeline-name>. Isso permite que, no mesmo computador de desenvolvimento local, você mantenha sua própria cópia de artefatos em desenvolvimento separada dos artefatos que o Jenkins usa do seu repositório Git de terceiros.

Validar o Pacote de Ativos do Databricks

A segunda fase neste pipeline do Jenkins, a fase Validate Bundle, é definida da seguinte forma:

stage('Validate Bundle') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET}
     """
}

Esta fase garante que o Pacote de Ativos do Databricks, que define os fluxos de trabalho para testar e executar seus artefatos, esteja sintaticamente correto. Os Pacotes de Ativos do Databricks, conhecidos simplesmente como pacotes, possibilitam expressar dados completos, análises e projetos de ML como uma coleção de arquivos de origem. Confira O que são Pacotes de Ativos do Databricks?.

Para definir o pacote configurável deste artigo, crie um arquivo nomeado databricks.yml na raiz do repositório clonado em seu computador local. Neste arquivo de exemplo databricks.yml, substitua os seguintes espaços reservados:

  • Substitua <bundle-name> por um nome programático exclusivo para o pacote. Por exemplo, poderia ser jenkins-demo.
  • Substitua <job-prefix-name> por alguma cadeia de caracteres para ajudar a identificar exclusivamente os trabalhos do Azure Databricks criados em seu workspace para este exemplo. Por exemplo, poderia ser jenkins-demo. Ele deve corresponder ao valor JOBPREFIX em seu Jenkinsfile.
  • Substitua <spark-version-id> pelo ID de versão do Databricks Runtime para seus clusters de trabalho, por exemplo 13.3.x-scala2.12.
  • Substitua <cluster-node-type-id> pelo ID do tipo de nó para seus clusters de trabalho, por exemplo, Standard_DS3_v2.
  • Observe que dev no mapeamento targets é o mesmo que BUNDLETARGET no seu Jenkinsfile. Um destino de pacote especifica o host e os comportamentos de implantação relacionados.

Aqui está o arquivo databricks.yml, que deve ser adicionado à raiz do seu repositório para que este exemplo funcione corretamente:

# Filename: databricks.yml
bundle:
  name: <bundle-name>

variables:
  job_prefix:
    description: A unifying prefix for this bundle's job and task names.
    default: <job-prefix-name>
  spark_version:
    description: The cluster's Spark version ID.
    default: <spark-version-id>
  node_type_id:
    description: The cluster's node type ID.
    default: <cluster-node-type-id>

artifacts:
  dabdemo-wheel:
    type: whl
    path: ./Libraries/python/dabdemo

resources:
  jobs:
    run-unit-tests:
      name: ${var.job_prefix}-run-unit-tests
      tasks:
        - task_key: ${var.job_prefix}-run-unit-tests-task
          new_cluster:
            spark_version: ${var.spark_version}
            node_type_id: ${var.node_type_id}
            num_workers: 1
            spark_env_vars:
              WORKSPACEBUNDLEPATH: ${workspace.root_path}
          notebook_task:
            notebook_path: ./run_unit_tests.py
            source: WORKSPACE
          libraries:
            - pypi:
                package: pytest
    run-dabdemo-notebook:
      name: ${var.job_prefix}-run-dabdemo-notebook
      tasks:
        - task_key: ${var.job_prefix}-run-dabdemo-notebook-task
          new_cluster:
            spark_version: ${var.spark_version}
            node_type_id: ${var.node_type_id}
            num_workers: 1
            data_security_mode: SINGLE_USER
            spark_env_vars:
              WORKSPACEBUNDLEPATH: ${workspace.root_path}
          notebook_task:
            notebook_path: ./dabdemo_notebook.py
            source: WORKSPACE
          libraries:
            - whl: "/Workspace${workspace.root_path}/files/Libraries/python/dabdemo/dist/dabdemo-0.0.1-py3-none-any.whl"
    evaluate-notebook-runs:
      name: ${var.job_prefix}-evaluate-notebook-runs
      tasks:
        - task_key: ${var.job_prefix}-evaluate-notebook-runs-task
          new_cluster:
            spark_version: ${var.spark_version}
            node_type_id: ${var.node_type_id}
            num_workers: 1
            spark_env_vars:
              WORKSPACEBUNDLEPATH: ${workspace.root_path}
          spark_python_task:
            python_file: ./evaluate_notebook_runs.py
            source: WORKSPACE
          libraries:
            - pypi:
                package: unittest-xml-reporting

targets:
  dev:
    mode: development

Para obter mais informações sobre o arquivo databricks.yml, confira Configurações do Pacote de Ativos do Databricks.

Implantar o pacote em seu workspace

O terceiro estágio do pipeline do Jenkins, intitulado Deploy Bundle, é definido da seguinte forma:

stage('Deploy Bundle') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle deploy -t ${BUNDLETARGET}
     """
}

Esta fase faz duas coisas:

  1. Como o mapeamento artifact no arquivo databricks.yml é definido como whl, isso instrui a CLI do Databricks a criar a roda Python usando o arquivo setup.py no local especificado.
  2. Depois que o arquivo wheel Python é criado no seu computador de desenvolvimento local, a CLI do Databricks implanta o arquivo wheel Python criado junto com os arquivos Python e blocos de anotações especificados em seu workspace do Azure Databricks. Por padrão, os Pacotes de Ativos do Databricks implantam o arquivo wheel do Python e outros arquivos em /Workspace/Users/<your-username>/.bundle/<bundle-name>/<target-name>.

Para permitir que o arquivo wheel do Python seja construída conforme especificado no arquivo databricks.yml, crie as seguintes pastas e arquivos na raiz do repositório clonado em seu computador local.

Para definir a lógica e os testes de unidade para o arquivo wheel do Python em que o notebook será executado, crie dois arquivos denominados addcol.py e test_addcol.py e adicione-os a uma estrutura de pastas denominada python/dabdemo/dabdemo na pasta Libraries do seu repositório, visualizada da seguinte forma (as elipses indicam pastas omitidas no repositório, para fins de brevidade):

├── ...
├── Libraries
│    └── python
│          └── dabdemo
│                └── dabdemo
│                      ├── addcol.py
│                      └── test_addcol.py
├── ...

O arquivo addcol.py contém uma função de biblioteca que é compilada posteriormente em um arquivo wheel do Python e depois instalada em um cluster do Azure Databricks. É uma função simples que adiciona uma nova coluna, populada por um literal, a um DataFrame do Apache Spark:

# Filename: addcol.py
import pyspark.sql.functions as F

def with_status(df):
  return df.withColumn("status", F.lit("checked"))

O arquivo test_addcol.py contém testes para passar um objeto DataFrame simulado para a função with_status, definida em addcol.py. O resultado é então comparado a um objeto do DataFrame que contém os valores esperados. Se os valores corresponderem, o que nesse caso acontece, o teste será aprovado:

# Filename: test_addcol.py
import pytest
from pyspark.sql import SparkSession
from dabdemo.addcol import *

class TestAppendCol(object):

  def test_with_status(self):
    spark = SparkSession.builder.getOrCreate()

    source_data = [
      ("paula", "white", "paula.white@example.com"),
      ("john", "baer", "john.baer@example.com")
    ]

    source_df = spark.createDataFrame(
      source_data,
      ["first_name", "last_name", "email"]
    )

    actual_df = with_status(source_df)

    expected_data = [
      ("paula", "white", "paula.white@example.com", "checked"),
      ("john", "baer", "john.baer@example.com", "checked")
    ]
    expected_df = spark.createDataFrame(
      expected_data,
      ["first_name", "last_name", "email", "status"]
    )

    assert(expected_df.collect() == actual_df.collect())

Para permitir que a CLI do Databricks empacote corretamente esse código de biblioteca em um arquivo wheel do Python, crie dois arquivos chamados __init__.py e __main__.py na mesma pasta que os dois arquivos anteriores. Crie também um arquivo chamado setup.py na pasta python/dabdemo, visualizado da seguinte forma (as elipses indicam pastas omitidas, para fins de brevidade):

├── ...
├── Libraries
│    └── python
│          └── dabdemo
│                ├── dabdemo
│                │    ├── __init__.py
│                │    ├── __main__.py
│                │    ├── addcol.py
│                │    └── test_addcol.py
│                └── setup.py
├── ...

O arquivo __init__.py contém o número da versão e o autor da biblioteca. Substitua <my-author-name> por seu nome:

# Filename: __init__.py
__version__ = '0.0.1'
__author__ = '<my-author-name>'

import sys, os

sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))

O arquivo __main__.py contém o ponto de entrada da biblioteca:

# Filename: __main__.py
import sys, os

sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))

from addcol import *

def main():
  pass

if __name__ == "__main__":
  main()

O arquivo setup.py contém configurações adicionais para a compilação da biblioteca em um arquivo wheel do Python. Substitua <my-url>, <my-author-name>@<my-organization> e <my-package-description> por valores significativos:

# Filename: setup.py
from setuptools import setup, find_packages

import dabdemo

setup(
  name = "dabdemo",
  version = dabdemo.__version__,
  author = dabdemo.__author__,
  url = "https://<my-url>",
  author_email = "<my-author-name>@<my-organization>",
  description = "<my-package-description>",
  packages = find_packages(include = ["dabdemo"]),
  entry_points={"group_1": "run=dabdemo.__main__:main"},
  install_requires = ["setuptools"]
)

Testar a lógica do componente da roda Python

A fase Run Unit Tests, a quarta fase deste pipeline do Jenkins, usa pytest para testar a lógica de uma biblioteca para garantir que ela funcione como compilada. A fase é definida da seguinte forma:

stage('Run Unit Tests') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-unit-tests
     """
}

Esta fase usa a CLI do Databricks para executar um trabalho de notebook. Este trabalho executa o Notebook Python com o nome do arquivo run-unit-test.py. Este notebook é executado pytest contra a lógica da biblioteca.

Para executar os testes de unidade para este exemplo, adicione um arquivo de Notebook Python nomeado run_unit_tests.py com o seguinte conteúdo à raiz do repositório clonado em seu computador local:

# Databricks notebook source

# COMMAND ----------

# MAGIC %sh
# MAGIC
# MAGIC mkdir -p "/Workspace${WORKSPACEBUNDLEPATH}/Validation/reports/junit/test-reports"

# COMMAND ----------

# Prepare to run pytest.
import sys, pytest, os

# Skip writing pyc files on a readonly filesystem.
sys.dont_write_bytecode = True

# Run pytest.
retcode = pytest.main(["--junit-xml", f"/Workspace{os.getenv('WORKSPACEBUNDLEPATH')}/Validation/reports/junit/test-reports/TEST-libout.xml",
                      f"/Workspace{os.getenv('WORKSPACEBUNDLEPATH')}/files/Libraries/python/dabdemo/dabdemo/"])

# Fail the cell execution if there are any test failures.
assert retcode == 0, "The pytest invocation failed. See the log for details."

Use a roda Python compilada

A quinta fase deste pipeline do Jenkins, intitulada Run Notebook, executa um Notebook Python que chama a lógica no arquivo wheel do Python compilada, da seguinte maneira:

stage('Run Notebook') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} run-dabdemo-notebook
     """
  }

Este estágio executa a CLI do Databricks, que, por sua vez, instrui seu workspace a executar um trabalho de notebook. Este notebook cria um objeto do DataFrame, passa-o para a função with_status da biblioteca, imprime o resultado e relata os resultados da execução do trabalho. Crie o notebook adicionando um arquivo do Notebook Python nomeado dabdaddemo_notebook.py com o seguinte conteúdo na raiz do repositório clonado em seu computador de desenvolvimento local:

# Databricks notebook source

# COMMAND ----------

# Restart Python after installing the wheel.
dbutils.library.restartPython()

# COMMAND ----------

from dabdemo.addcol import with_status

df = (spark.createDataFrame(
  schema = ["first_name", "last_name", "email"],
  data = [
    ("paula", "white", "paula.white@example.com"),
    ("john", "baer", "john.baer@example.com")
  ]
))

new_df = with_status(df)

display(new_df)

# Expected output:
#
# +------------+-----------+-------------------------+---------+
# │first_name │last_name │email                   │status  |
# +============+===========+=========================+=========+
# │paula      │white     │paula.white@example.com │checked |
# +------------+-----------+-------------------------+---------+
# │john       │baer      │john.baer@example.com   │checked |
# +------------+-----------+-------------------------+---------+

Avaliar os resultados da execução do trabalho do notebook

A fase Evaluate Notebook Runs, a sexta fase deste pipeline do Jenkins, avalia os resultados da execução do trabalho de notebook anterior. A fase é definida da seguinte forma:

stage('Evaluate Notebook Runs') {
  sh """#!/bin/bash
        ${DBCLIPATH}/databricks bundle run -t ${BUNDLETARGET} evaluate-notebook-runs
     """
  }

Este estágio executa a CLI do Databricks, que, por sua vez, instrui seu workspace a executar um trabalho de arquivo do Phyton. Esse arquivo do Python determina os critérios de falha e êxito para a execução do trabalho do notebook e relata essa falha ou resultado de êxito. Crie um arquivo nomeado evaluate_notebook_runs.py com o seguinte conteúdo na raiz do repositório clonado em seu computador de desenvolvimento local:

import unittest
import xmlrunner
import json
import glob
import os

class TestJobOutput(unittest.TestCase):

  test_output_path = f"/Workspace${os.getenv('WORKSPACEBUNDLEPATH')}/Validation/Output"

  def test_performance(self):
    path = self.test_output_path
    statuses = []

    for filename in glob.glob(os.path.join(path, '*.json')):
      print('Evaluating: ' + filename)

      with open(filename) as f:
        data = json.load(f)

        duration = data['tasks'][0]['execution_duration']

        if duration > 100000:
            status = 'FAILED'
        else:
            status = 'SUCCESS'

        statuses.append(status)
        f.close()

    self.assertFalse('FAILED' in statuses)

  def test_job_run(self):
    path = self.test_output_path
    statuses = []

    for filename in glob.glob(os.path.join(path, '*.json')):
      print('Evaluating: ' + filename)

      with open(filename) as f:
        data = json.load(f)
        status = data['state']['result_state']
        statuses.append(status)
        f.close()

    self.assertFalse('FAILED' in statuses)

if __name__ == '__main__':
  unittest.main(
    testRunner = xmlrunner.XMLTestRunner(
      output = f"/Workspace${os.getenv('WORKSPACEBUNDLEPATH')}/Validation/Output/test-results",
    ),
    failfast   = False,
    buffer     = False,
    catchbreak = False,
    exit       = False
  )

Importar e relatar resultados de testes

A sétima fase neste pipeline do Jenkins, intitulado Import Test Results, usa a CLI do Databricks para enviar os resultados do teste do workspace para seu computador de desenvolvimento local. A oitava e última fase, intitulada Publish Test Results, publica os resultados do teste para Jenkins usando o plugin junit do Jenkins. Isso permite que você visualize relatórios e dashboards relacionados ao status dos resultados do teste. Estas fases são definidas da seguinte maneira:

stage('Import Test Results') {
  def DATABRICKS_BUNDLE_WORKSPACE_FILE_PATH
  def getPath = "${DBCLIPATH}/databricks bundle validate -t ${BUNDLETARGET} | ${JQPATH}/jq -r .workspace.file_path"
  def output = sh(script: getPath, returnStdout: true).trim()

  if (output) {
    DATABRICKS_BUNDLE_WORKSPACE_FILE_PATH = "${output}"
  } else {
    error "Failed to capture output or command execution failed: ${getPath}"
  }

  sh """#!/bin/bash
        ${DBCLIPATH}/databricks workspace export-dir \
        ${DATABRICKS_BUNDLE_WORKSPACE_FILE_PATH}/Validation/Output/test-results \
        ${WORKSPACE}/Validation/Output/test-results \
        --overwrite
     """
}
stage('Publish Test Results') {
  junit allowEmptyResults: true, testResults: '**/test-results/*.xml', skipPublishingChecks: true
}

Resultados de teste do Jenkins

Efetue push de todas as alterações de código para seu repositório de terceiros

Agora você deve enviar o conteúdo do repositório clonado em seu computador de desenvolvimento local para o repositório de terceiros. Antes de efetuar push, você deve primeiro adicionar as entradas a seguir ao arquivo .gitignore no repositório clonado, pois, provavelmente, não deve efetuar push de arquivos de trabalho internos do Pacote de Ativos do Databricks, relatórios de validação, arquivos de build do Python e caches do Python para o repositório de terceiros. Normalmente, você desejará regenerar novos relatórios de validação e as últimas compilações de roda Python em seu workspace do Azure Databricks, em vez de usar relatórios de validação potencialmente desatualizados e compilações de roda do Python:

.databricks/
.vscode/
Libraries/python/dabdemo/build/
Libraries/python/dabdemo/__pycache__/
Libraries/python/dabdemo/dabdemo.egg-info/
Validation/

Executar seu pipeline do Jenkins

Agora você está pronto para executar seu pipeline do Jenkins manualmente. Para fazer isso, no painel do Jenkins:

  1. Clique no nome do seu pipeline do Jenkins.
  2. Na barra lateral, clique em Compilar agora.
  3. Para ver os resultados, clique na última execução do Pipeline (por exemplo, #1) e, em seguida, clique em Saída do Console.

Neste ponto, o pipeline de CI/CD concluiu um ciclo de integração e implantação. Ao automatizar esse processo, você pode garantir que seu código foi testado e implantado por meio de um processo eficiente, consistente e repetível. Para instruir seu provedor Git de terceiros a executar o Jenkins sempre que um evento específico acontecer, como uma solicitação pull de repositório, consulte a documentação do provedor Git de terceiros.