CI/CD com Jenkins no Azure Databricks

Nota

Este artigo abrange Jenkins, que não é fornecido nem suportado pela Databricks. Para entrar em contato com o provedor, consulte a Ajuda do Jenkins.

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

Para obter informações sobre como usar 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

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

  1. Crie um repositório ou use um repositório existente com seu provedor Git de terceiros.
  2. Conecte sua máquina de desenvolvimento local ao mesmo repositório de terceiros. Para obter instruções, consulte a documentação do seu provedor Git de terceiros.
  3. Puxe todos os artefatos atualizados existentes (como blocos de anotações, arquivos de código e scripts de compilação) do repositório de terceiros para sua máquina de desenvolvimento local.
  4. Conforme desejado, crie, atualize e teste artefatos em sua máquina de desenvolvimento local. Em seguida, envie todos os artefatos novos e alterados de sua máquina 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 os passos 3 e 4 conforme necessário.
  6. Use o Jenkins periodicamente como uma abordagem integrada para extrair automaticamente artefatos do repositório de terceiros para sua máquina de desenvolvimento local ou espaço de trabalho do Azure Databricks; criar, testar e executar código em sua máquina de desenvolvimento local ou espaço de trabalho do Azure Databricks; e reportar resultados de testes e corridas. Embora você possa executar o Jenkins manualmente, em implementações do mundo real, você instruiria seu provedor Git terceirizado a executar o Jenkins sempre que um evento específico acontecesse, como uma solicitação pull de 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 em vez do Jenkins, consulte Integração e entrega contínuas no Azure Databricks usando o Azure DevOps.

Configuração da máquina de desenvolvimento local

O exemplo deste artigo usa Jenkins para instruir a CLI do Databricks e o Databricks Asset Bundles a fazer o seguinte:

  1. Crie um arquivo de roda Python em sua máquina de desenvolvimento local.
  2. Implante o arquivo de roda Python criado junto com arquivos Python adicionais e blocos de anotações Python de sua máquina de desenvolvimento local em um espaço de trabalho do Azure Databricks.
  3. Teste e execute o arquivo de roda Python carregado e os blocos de anotações nesse espaço de trabalho.

Para configurar sua máquina de desenvolvimento local para instruir seu espaço de trabalho do Azure Databricks a executar os estágios de compilação e carregamento para este exemplo, faça o seguinte em sua máquina de desenvolvimento local:

Etapa 1: Instalar as ferramentas necessárias

Nesta etapa, você instala as ferramentas de construção de roda Databricks CLI, Jenkins jqe Python em sua máquina 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 o tiver feito. Jenkins usa a CLI do Databricks para passar no teste deste exemplo e executar instruções em seu espaço de trabalho. Consulte Instalar ou atualizar a CLI do Databricks.

  2. Instale e inicie o Jenkins, se ainda não o fez. Consulte Instalando o Jenkins para Linux, macOS ou Windows.

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

  4. Use pip para instalar as ferramentas de construção de roda 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 Jenkins

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

Tipos de projeto Jenkins

Para criar o Jenkins Pipeline em 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 Jenkins Pipeline, por exemplo jenkins-demo.
  3. Clique no ícone Tipo de projeto Pipeline .
  4. Clique em OK. A página Configurar do Jenkins Pipeline é exibida.
  5. Na área Pipeline, na lista suspensa Definição, selecione Script de pipeline do SCM.
  6. Na lista suspensa SCM, selecione Git.
  7. Em URL do repositório, digite a URL para o repositório hospedado pelo provedor Git de terceiros.
  8. Em 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 mais adiante Jenkinsfile neste artigo.
  10. Desmarque a caixa intitulada Checkout leve, se já estiver marcada.
  11. Clique em Guardar.

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

Nesta etapa, você adiciona três variáveis de ambiente global ao Jenkins. Jenkins passa essas variáveis de ambiente para a CLI do Databricks. A CLI do Databricks precisa dos valores dessas variáveis de ambiente para autenticar com seu espaço de trabalho do Azure Databricks. Este exemplo usa a autenticação OAuth máquina-a-máquina (M2M) 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, defina a URL do espaço de trabalho do Azure Databricks, começando com https://. Consulte Nomes de instância, URLs e IDs do espaço de trabalho.
  • DATABRICKS_CLIENT_ID, definido como ID do cliente da entidade de serviço, que também é conhecido como ID do aplicativo.
  • DATABRICKS_CLIENT_SECRET, definido como o segredo OAuth do Azure Databricks da entidade de serviço.

Para definir variáveis de ambiente global no Jenkins, no seu Painel do Jenkins:

  1. Na barra lateral, clique em Gerenciar 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 lado a lado.
  4. Clique em Adicionar e insira Nome e Valor da variável de ambiente. Repita isso para cada variável de ambiente adicional.
  5. Quando terminar de adicionar variáveis de ambiente, clique em Salvar para retornar ao seu Painel do Jenkins.

Projete o oleoduto Jenkins

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

Você escreve uma definição de Jenkins Pipeline em um arquivo de texto chamado Jenkinsfile, que por sua vez é verificado no repositório de controle de origem de um projeto. Para obter mais informações, consulte Jenkins Pipeline. Aqui está o Jenkins Pipeline 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 para você hospedado pelo seu provedor Git de terceiros. Este artigo usa uma URL do GitHub como exemplo.
  • Substitua <release-branch-name> pelo nome da ramificação de versão em seu repositório. Por exemplo, poderia ser main.
  • Substitua <databricks-cli-installation-path> pelo caminho em sua máquina de desenvolvimento local onde a CLI do Databricks está instalada. Por exemplo, no macOS isso pode ser /usr/local/bin.
  • Substitua <jq-installation-path> pelo caminho na máquina de desenvolvimento local onde jq está instalado. Por exemplo, no macOS isso pode 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 espaço de trabalho para este exemplo. Por exemplo, poderia ser jenkins-demo.
  • Observe que BUNDLETARGET está definido como dev, que é o nome do destino do Databricks Asset Bundle definido posteriormente neste artigo. Em implementações do mundo real, você alteraria isso para o nome do seu próprio destino de pacote. Mais detalhes sobre destinos de pacote são fornecidos mais adiante 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 Jenkins Pipeline e como configurar os artefatos e comandos para Jenkins executar nesse estágio.

Extraia os artefatos mais recentes do repositório de terceiros

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

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

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

Validar o pacote de ativos Databricks

O segundo estágio neste Jenkins Pipeline, o Validate Bundle estágio, é definido da seguinte forma:

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

Esta etapa garante que o Databricks Asset Bundle, que define os fluxos de trabalho para testar e executar seus artefatos, esteja sintaticamente correto. Os Databricks Asset Bundles, conhecidos simplesmente como bundles, tornam possível expressar dados completos, análises e projetos de ML como uma coleção de arquivos de origem. Consulte O que são Databricks Asset Bundles?.

Para definir o pacote para este artigo, crie um arquivo nomeado databricks.yml na raiz do repositório clonado em sua máquina 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 espaço de trabalho para este exemplo. Por exemplo, poderia ser jenkins-demo. Ele deve corresponder ao JOBPREFIX valor 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 no targets mapeamento é o mesmo que dev no BUNDLETARGET seu Jenkinsfile. Um destino de pacote especifica o host e os comportamentos de implantação relacionados.

Aqui está o databricks.yml arquivo, 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, consulte Configuração do databricks.yml Databricks Asset Bundle.

Implantar o pacote em seu espaço de trabalho

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

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

Esta etapa faz duas coisas:

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

Para permitir que o arquivo de roda Python seja construído conforme especificado no databricks.yml arquivo, crie as seguintes pastas e arquivos na raiz do repositório clonado em sua máquina local.

Para definir a lógica e os testes de unidade para o arquivo de roda Python contra o qual o bloco de anotações será executado, crie dois arquivos nomeados addcol.py e test_addcol.py, e adicione-os a uma estrutura de pastas nomeada python/dabdemo/dabdemo dentro da pasta do Libraries repositório, visualizada da seguinte forma (as reticências indicam pastas omitidas no repo, para brevidade):

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

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

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

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

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

# 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 habilitar a CLI do Databricks para empacotar corretamente esse código de biblioteca em um arquivo de roda Python, crie dois arquivos nomeados __init__.py e __main__.py na mesma pasta que os dois arquivos anteriores. Além disso, crie um arquivo nomeado setup.py na python/dabdemo pasta, visualizado da seguinte forma (reticências indicam pastas omitidas, para brevidade):

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

O __init__.py arquivo contém o número da versão e o autor da biblioteca. Substitua <my-author-name> pelo 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 __main__.py arquivo 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 setup.py arquivo contém configurações adicionais para construir a biblioteca em um arquivo de roda 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"]
)

Teste a lógica do componente da roda Python

O Run Unit Tests estágio, o quarto estágio deste Jenkins Pipeline, usa pytest para testar a lógica de uma biblioteca para garantir que ela funcione como construída. Esta fase define-se do seguinte modo:

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

Este estágio usa a CLI do Databricks para executar um trabalho de bloco de anotações. Este trabalho executa o bloco de anotações Python com o nome do run-unit-test.pyarquivo . Este bloco de notas contraria pytest a lógica da biblioteca.

Para executar os testes de unidade para este exemplo, adicione um arquivo de bloco de anotações Python nomeado run_unit_tests.py com o seguinte conteúdo à raiz do repositório clonado em sua máquina 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 construída

O quinto estágio deste Jenkins Pipeline, intitulado Run Notebook, executa um notebook Python que chama a lógica no arquivo de roda Python construído, 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 espaço de trabalho a executar um trabalho de bloco de anotações. Este bloco de anotações cria um objeto DataFrame, passa-o para a função da with_status biblioteca, imprime o resultado e relata os resultados da execução do trabalho. Crie o bloco de anotações adicionando um arquivo de bloco de anotações Python nomeado dabdaddemo_notebook.py com o seguinte conteúdo na raiz do repositório clonado em sua máquina 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 bloco de anotações

A Evaluate Notebook Runs etapa, a sexta etapa deste Jenkins Pipeline, avalia os resultados da execução anterior do trabalho do notebook. Esta fase define-se do seguinte modo:

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 espaço de trabalho a executar um trabalho de arquivo Python. Este arquivo Python determina os critérios de falha e sucesso para a execução do trabalho do bloco de anotações e relata esse resultado de falha ou sucesso. Crie um arquivo nomeado evaluate_notebook_runs.py com o seguinte conteúdo na raiz do repositório clonado em sua máquina 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

O sétimo estágio neste Jenkins Pipeline, intitulado Import Test Results, usa a CLI do Databricks para enviar os resultados do teste do seu espaço de trabalho para sua máquina de desenvolvimento local. A oitava e última etapa, intitulada Publish Test Results, publica os resultados do teste para Jenkins usando o junit plugin Jenkins. Isso permite que você visualize relatórios e painéis relacionados ao status dos resultados do teste. Estas fases são definidas do seguinte modo:

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 do teste Jenkins

Envie todas as alterações de código para o repositório de terceiros

Agora você deve enviar o conteúdo do repositório clonado na máquina de desenvolvimento local para o repositório de terceiros. Antes de enviar por push, você deve primeiro adicionar as seguintes entradas ao .gitignore arquivo em seu repositório clonado, pois provavelmente não deve enviar arquivos de trabalho internos do Databricks Asset Bundle, relatórios de validação, arquivos de compilação Python e caches Python para seu repositório de terceiros. Normalmente, você desejará regenerar novos relatórios de validação e as últimas compilações de roda Python em seu espaço de trabalho do Azure Databricks, em vez de usar relatórios de validação potencialmente desatualizados e compilações de roda Python:

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

Execute seu pipeline Jenkins

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

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

Neste ponto, o pipeline de CI/CD completou um ciclo de integração e implantação. Ao automatizar esse processo, você pode garantir que seu código tenha sido testado e implantado por 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.