Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure DevOps CI Option #105

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a3a1962
Begin to add and adapt files for az-devops option
Feb 13, 2023
dd51238
complete draft of test pipeline for pip and conda
AlexF1994 Feb 15, 2023
ebd4274
move append of conda to path to proper if else position
AlexF1994 Feb 20, 2023
8064e4f
Merge pull request #1 from MichaelGehring/feature/az-devops-afo
MichaelGehring Feb 20, 2023
cd476d7
Move DevOps files into project_slug dir
Feb 20, 2023
a5ed727
Fix declaration of all ci files
Feb 20, 2023
6c85536
Copy Build and Delete templates without rendering
Feb 20, 2023
7dba22a
Bf - Add forgotten devops file path
Feb 20, 2023
85f5237
Bf - delete empty step
Feb 20, 2023
2ba8570
Fix indentation
Feb 20, 2023
256ec3e
Revert "Fix indentation"
Feb 20, 2023
605bd58
Fix indentation
Feb 20, 2023
03f4a3c
Bf - delete single quotes
Feb 20, 2023
0a225f4
Fix format
Feb 21, 2023
eca9c11
Fix template file names
Feb 21, 2023
5fba904
Fix template file names
Feb 22, 2023
9aa9a8c
Fix template file names
Feb 22, 2023
ced2620
Fix template file names
Feb 22, 2023
830ce74
Revert "Fix template file names"
Feb 22, 2023
f4cd38e
Fix stage names
Feb 22, 2023
ae02d16
Fix other names
Feb 22, 2023
3d743bc
Fix conda test-pipeline
Feb 22, 2023
910b682
cd option conditional on docker option
Feb 27, 2023
d3551df
add tests for az-devops option
AlexF1994 Mar 14, 2023
8a1a556
add poetry option for az devops
AlexF1994 Oct 11, 2023
1bb50f2
rebase to upstream
AlexF1994 Oct 11, 2023
82f94ff
make poetry test pipeline work
AlexF1994 Oct 18, 2023
ac84560
solve new line issue
AlexF1994 Oct 18, 2023
167203d
Merge branch 'master' into feature/az-devops
AlexF1994 Nov 7, 2023
57a1c44
fix missing comma in cookiecutter.json
AlexF1994 Nov 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Unfortunately, cookiecutter does not allow us to show any description of the opt
* Select your `ci_pipeline`
- `none` (default): Don't use any CI/CD pipeline.
- `gitlab`: If you plan to use GitLab, this option will add a CI/CD Pipeline definition for [GitLab CI/CD](https://docs.gitlab.com/ee/ci/). The pipeline includes basic steps to build, test and deploy your code. The deployment steps do nothing but echoing a String, as deployment is very project-specific.
- `az-devops`: If you plan to use [Azure DevOps](https://azure.microsoft.com/en-us/products/devops), this option will add a CI pipeline and templates for CD pipelines. For the CD pipelines to work, you need to add project specific information.
* `create_cli` (yes or no): if you plan to build an application with a command line interface (CLI), select *yes* here. This will integrate a template for the CLI into your project - minimal boilerplate guaranteed! (We're leveraging the awesome [typer](https://typer.tiangolo.com/) library for this.)
* `config_file`: select your preferred config format. It is best practice to store your configuration separate from your code, even for small projects, but because there are a gazillion ways to do this, each project seems to reinvents the wheel. We want to provide a few options to set you up with a working configuration:
- `yaml`: use [YAML](https://yaml.org/) as your configuration file format. Easy to read and write, widely adopted, relies on the [PyYAML](https://pyyaml.org/) package.
Expand Down
7 changes: 6 additions & 1 deletion cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
"package_manager": ["conda", "pip", "poetry"],
"use_notebooks": ["no", "yes"],
"use_docker": ["no", "yes"],
"ci_pipeline": ["none", "gitlab"],
"ci_pipeline": ["none", "gitlab", "az-devops"],
"create_cli": ["no", "yes"],
"config_file": ["none", "hocon", "yaml"],
"code_formatter": ["none", "black"],
"editor_settings": ["none", "pycharm", "vscode"],
"_copy_without_render": [
"cd/build-dev.yml",
"cd/build.yml",
"cd/delete-old-images.yml"
],
"__prompts__": {
"full_name": "What's your [bold yellow]name[/]?",
"company_name": "Enter your [bold yellow]company name[/]; leave empty if not applicable",
Expand Down
23 changes: 22 additions & 1 deletion hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,18 @@
".gitlab-ci.yml",
}

files_ci_all = files_ci_gitlab
files_ci_devops = {
'ci/test-pipeline.yml',
}

files_cd_devops = {
'cd/build-dev.yml',
'cd/build.yml',
'cd/trigger.yml',
'cd/delete-old-images.yml'
}

files_ci_all = files_ci_gitlab | files_ci_devops | files_cd_devops

folders_editor = [
'.idea__editor',
Expand Down Expand Up @@ -167,10 +178,20 @@ def handle_editor_settings():

def handle_ci():
ci_pipeline = '{{ cookiecutter.ci_pipeline }}'
use_docker = '{{ cookiecutter.use_docker }}'
if ci_pipeline == "gitlab":
_delete_files(files_ci_all - files_ci_gitlab)
os.rmdir('ci')
os.rmdir('cd')
elif ci_pipeline == "az-devops":
_delete_files(files_ci_all - files_ci_devops - files_cd_devops)
if use_docker == 'no':
_delete_files(files_cd_devops)
os.rmdir('cd')
elif ci_pipeline == 'none':
_delete_files(files_ci_all)
os.rmdir('ci')
os.rmdir('cd')


def print_success():
Expand Down
80 changes: 77 additions & 3 deletions tests/test_options.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
from pathlib import Path

from .util import assert_file_contains, check_project
import pytest


@pytest.fixture
def az_devops_cd_files():
return [
'cd/build.yml',
'cd/build-dev.yml',
'cd/delete-old-images.yml',
'cd/trigger.yml'
]


@pytest.fixture
def az_devops_ci_files():
return ['ci/test-pipeline.yml']


@pytest.fixture
def az_devops_files(az_devops_ci_files, az_devops_cd_files):
return az_devops_ci_files + az_devops_cd_files


def test_base():
Expand Down Expand Up @@ -61,10 +82,26 @@ def test_docker_poetry():

def test_docker_no():
check_project(
settings={'use_docker': 'no'},
settings={
'use_docker': 'no'
},
files_non_existent=['Dockerfile', 'docker-compose.yml', '.dockerignore'])


def test_docker_no_az_devops(az_devops_cd_files):
check_project(
settings={
'use_docker': 'no',
'ci_pipeline': 'az-devops'
},
files_non_existent=[
'Dockerfile',
'docker-compose.yml',
'.dockerignore',
].extend(az_devops_cd_files)
)


def test_cli_yes():
check_project(
settings={'create_cli': 'yes'},
Expand Down Expand Up @@ -197,6 +234,7 @@ def test_poetry_regression():
run_pytest=True,
)


def test_gitlab_pip():
check_project(
settings={
Expand All @@ -206,6 +244,7 @@ def test_gitlab_pip():
files_existent=[".gitlab-ci.yml"]
)


def test_gitlab_conda():
check_project(
settings={
Expand All @@ -215,6 +254,7 @@ def test_gitlab_conda():
files_existent=[".gitlab-ci.yml"]
)


def test_gitlab_poetry():
check_project(
settings={
Expand All @@ -224,10 +264,44 @@ def test_gitlab_poetry():
files_existent=[".gitlab-ci.yml"]
)

def test_no_ci_pipeline():

def test_az_devops_pip(az_devops_files):
check_project(
settings={
"package_manager": "pip",
"ci_pipeline": "az-devops",
"use_docker": "yes"
},
files_existent=az_devops_files
)


def test_az_devops_conda(az_devops_files):
check_project(
settings={
"package_manager": "conda",
"ci_pipeline": "az-devops",
"use_docker": "yes"
},
files_existent=az_devops_files
)


def test_az_devops_poetry(az_devops_files):
check_project(
settings={
"package_manager": "poetry",
"ci_pipeline": "az-devops",
"use_docker": "yes"
},
files_existent=az_devops_files
)


def test_no_ci_pipeline(az_devops_files):
check_project(
settings={
"ci_pipeline": "none"
},
files_non_existent=[".gitlab-ci.yml"]
files_non_existent=[".gitlab-ci.yml"] + az_devops_files
)
47 changes: 47 additions & 0 deletions {{cookiecutter.project_slug}}/cd/build-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Pipeline which builds the docker image for DEV and pushes it to DEV Container Registry
#
# expected parameters:
# ACR: Azure Container Registry ID to push image to
# ACR_SUB: Service Connection to authenticate against the Azure Container Registry
# REPOSITORY: The repository to push to in the Container Registry
# SUBSCRIPTION: Service Connection used for Azure CLI Task
# IMAGE_COUNT: Number of last images to retain, all others are deleted

parameters:
- name: ACR
type: string
- name: ACR_SUB
type: string
- name: REPOSITORY
type: string
- name: SUBSCRIPTION
type: string
- name: IMAGE_COUNT
type: number

jobs:
- job: build_push_docker_image
steps:
- checkout: self

- task: Docker@2
inputs:
containerRegistry: ${{ parameters.ACR_SUB }}
repository: ${{ parameters.REPOSITORY }}
command: build
Dockerfile: '**/Dockerfile'

- task: Docker@2
inputs:
containerRegistry: ${{ parameters.ACR_SUB }}
repository: ${{ parameters.REPOSITORY }}
command: push
Dockerfile: '**/Dockerfile'
tags: $(Build.BuildId)

- template: delete-old-images.yml
parameters:
ACR: ${{ parameters.ACR }}
SUBSCRIPTION: ${{ parameters.SUBSCRIPTION }}
REPOSITORY: ${{ parameters.REPOSITORY }}
IMAGE_COUNT: ${{ parameters.IMAGE_COUNT}}
53 changes: 53 additions & 0 deletions {{cookiecutter.project_slug}}/cd/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Pipeline which copies docker image from the Container Registry in a (previous) environment to the next environment
# This will be used to deploy the image DEV -> QA -> PROD
#
# expected parameters:
# ACR_PREVIOUS : Azure Container Registry to pull Docker image from
# ACR_NEXT: Azure Container Registry to push Docker image to
# ACR_SUB_PREVIOUS: Service Connection used for authentication with Container Registry OLD
# ACR_SUB_NEXT: Service Connection used for authentication with Container Registry NEW
# REPOSITORY: The repository to push and pull to/from in the Container Registries, should be the same for all environments to avoid complexity
# IMAGE_COUNT: Number of last images to retain, all others are deleted

parameters:
- name: ACR_PREVIOUS
type: string
- name: ACR_NEXT
type: string
- name: ACR_SUB_PREVIOUS
type: string
- name: ACR_SUB_NEXT
type: string
- name: REPOSITORY
type: string
- name: IMAGE_COUNT
type: number
- name: SUBSCRIPTION
type: string

steps:
- task: Docker@2
displayName: Pull image from container repository of previous environment
inputs:
containerRegistry: ${{ parameters.ACR_SUB_PREVIOUS }}
repository: ${{ parameters.REPOSITORY }}
command: pull
arguments: ${{ parameters.ACR_PREVIOUS }}/${{ parameters.REPOSITORY }}:$(Build.BuildId)

- bash: docker tag ${{ parameters.ACR_PREVIOUS }}/${{ parameters.REPOSITORY }}:$(Build.BuildId) ${{ parameters.ACR_NEXT }}/${{ parameters.REPOSITORY }}:$(Build.BuildId)
displayName: Promote Docker image to registry of next environment

- task: Docker@2
displayName: Push Image to container repository of next environment
inputs:
containerRegistry: ${{ parameters.ACR_SUB_NEXT }}
repository: ${{ parameters.REPOSITORY }}
command: push
tags: $(Build.BuildId)

- template: delete-old-images.yml
parameters:
ACR: ${{ parameters.ACR_NEXT }}
SUBSCRIPTION: ${{ parameters.SUBSCRIPTION }}
REPOSITORY: ${{ parameters.REPOSITORY }}
IMAGE_COUNT: ${{ parameters.IMAGE_COUNT}}
39 changes: 39 additions & 0 deletions {{cookiecutter.project_slug}}/cd/delete-old-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This template encapsulates the functionality to delete old images,
# so only a pre-specified number of images is present in the registry
#
# Expected Parameters
# ACR: Azure Container Registry containing the images to delete
# REPOSITORY: Specific Repo in the Registry
# IMAGE_COUNT: Number of historic images to retain
# ACR_SUB: Azure Service Connection to use for authentication against registry

parameters:
- name: ACR
type: string
- name: REPOSITORY
type: string
- name: IMAGE_COUNT
type: number
- name: SUBSCRIPTION
type: string

steps:
- task: AzureCLI@2
displayName: 'Clean-up old Docker image'
inputs:
azureSubscription: ${{ parameters.SUBSCRIPTION }}
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
ACR=${{ parameters.ACR }}
REPOSITORY=${{ parameters.REPOSITORY }}
# Number of newest images in the repository that will not be deleted
COUNT=${{ parameters.IMAGE_COUNT }}

OLD_IMAGES=$(az acr repository show-tags --name $ACR --repository $REPOSITORY --orderby time_asc -o tsv | head -n -$COUNT)
echo "$OLD_IMAGES"
for OLD_IMAGE in $OLD_IMAGES
do
az acr repository delete --name $ACR --image $REPOSITORY:$OLD_IMAGE --yes
done
arguments: '-failOnStandardError false'
Loading