Skip to content

Commit 1908380

Browse files
committed
ci
1 parent 1dc0765 commit 1908380

1,471 files changed

Lines changed: 489602 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ci-demo/.DS_Store

6 KB
Binary file not shown.

ci-demo/.flake8

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[flake8]
2+
max-line-length = 100
3+
exclude = .git,__pycache__,venv

ci-demo/.github/workflows/ci.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Flask API CI Pipeline
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build-test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.10"
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install -r requirements.txt
26+
27+
- name: Lint check
28+
run: flake8 app tests
29+
30+
- name: Run tests with coverage
31+
run: pytest --cov=app --cov-report=term-missing

ci-demo/Jenkinsfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
pipeline {
2+
agent { docker { image 'python:3.12.0-alpine3.18' } }
3+
4+
stages {
5+
stage('Install') {
6+
steps {
7+
sh 'python -m venv venv'
8+
sh '. venv/bin/activate && pip install -r ci-demo/requirements.txt'
9+
}
10+
}
11+
stage('Test') {
12+
steps {
13+
sh '. venv/bin/activate && cd ci-demo && python -m pytest --cov-report=term-missing'
14+
}
15+
}
16+
stage('Lint') {
17+
steps {
18+
sh '. venv/bin/activate && python -m flake8 ci-demo/app ci-demo/tests'
19+
}
20+
}
21+
}
22+
post {
23+
success { echo "CI Passed" }
24+
failure { echo "CI Failed" }
25+
}
26+
}

ci-demo/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Install dependencies
2+
pip install -r requirements.txt
3+
4+
# Run locally
5+
python app/main.py
6+
7+
# Test API
8+
curl http://localhost:5000/todos
9+
curl -X POST http://localhost:5000/todos -H "Content-Type: application/json" -d '{"task":"CI Demo"}'
10+
11+
# Run tests
12+
pytest -v --cov=app
13+
14+
# Run lint
15+
flake8 app tests

ci-demo/app/main.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from flask import Flask, jsonify, request
2+
3+
app = Flask(__name__)
4+
5+
todos = [
6+
{"id": 1, "task": "Learn DevOps"},
7+
{"id": 2, "task": "Build CI/CD pipeline"}
8+
]
9+
10+
11+
@app.route("/todos", methods=["GET"])
12+
def get_todos():
13+
return jsonify(todos), 200
14+
15+
16+
@app.route("/todos", methods=["POST"])
17+
def add_todo():
18+
data = request.get_json()
19+
if not data or "task" not in data:
20+
return jsonify({"error": "Invalid payload"}), 400
21+
new_id = max(t["id"] for t in todos) + 1 if todos else 1
22+
todo = {"id": new_id, "task": data["task"]}
23+
todos.append(todo)
24+
return jsonify(todo), 201
25+
26+
27+
@app.route("/todos/<int:todo_id>", methods=["DELETE"])
28+
def delete_todo(todo_id):
29+
global todos
30+
todos = [t for t in todos if t["id"] != todo_id]
31+
return jsonify({"message": f"Todo {todo_id} deleted"}), 200
32+
33+
34+
if __name__ == "__main__":
35+
app.run(host="0.0.0.0", port=8000)

ci-demo/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
flask
2+
pytest
3+
flake8
4+
pytest-cov

ci-demo/tests/test_app.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import pytest
2+
from app.main import app
3+
4+
5+
@pytest.fixture
6+
def client():
7+
app.config["TESTING"] = True
8+
with app.test_client() as client:
9+
yield client
10+
11+
12+
def test_get_todos(client):
13+
response = client.get("/todos")
14+
assert response.status_code == 200
15+
assert isinstance(response.get_json(), list)
16+
17+
18+
def test_add_todo_success(client):
19+
response = client.post("/todos", json={"task": "Write tests"})
20+
assert response.status_code == 201
21+
data = response.get_json()
22+
assert "id" in data
23+
assert data["task"] == "Write tests"
24+
25+
26+
def test_add_todo_invalid(client):
27+
response = client.post("/todos", json={})
28+
assert response.status_code == 400
29+
30+
31+
def test_delete_todo(client):
32+
response = client.delete("/todos/1")
33+
assert response.status_code == 200
34+
assert "deleted" in response.get_json()["message"]

ci-demo/venv1/bin/Activate.ps1

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
<#
2+
.Synopsis
3+
Activate a Python virtual environment for the current PowerShell session.
4+
5+
.Description
6+
Pushes the python executable for a virtual environment to the front of the
7+
$Env:PATH environment variable and sets the prompt to signify that you are
8+
in a Python virtual environment. Makes use of the command line switches as
9+
well as the `pyvenv.cfg` file values present in the virtual environment.
10+
11+
.Parameter VenvDir
12+
Path to the directory that contains the virtual environment to activate. The
13+
default value for this is the parent of the directory that the Activate.ps1
14+
script is located within.
15+
16+
.Parameter Prompt
17+
The prompt prefix to display when this virtual environment is activated. By
18+
default, this prompt is the name of the virtual environment folder (VenvDir)
19+
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
20+
21+
.Example
22+
Activate.ps1
23+
Activates the Python virtual environment that contains the Activate.ps1 script.
24+
25+
.Example
26+
Activate.ps1 -Verbose
27+
Activates the Python virtual environment that contains the Activate.ps1 script,
28+
and shows extra information about the activation as it executes.
29+
30+
.Example
31+
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
32+
Activates the Python virtual environment located in the specified location.
33+
34+
.Example
35+
Activate.ps1 -Prompt "MyPython"
36+
Activates the Python virtual environment that contains the Activate.ps1 script,
37+
and prefixes the current prompt with the specified string (surrounded in
38+
parentheses) while the virtual environment is active.
39+
40+
.Notes
41+
On Windows, it may be required to enable this Activate.ps1 script by setting the
42+
execution policy for the user. You can do this by issuing the following PowerShell
43+
command:
44+
45+
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
46+
47+
For more information on Execution Policies:
48+
https://go.microsoft.com/fwlink/?LinkID=135170
49+
50+
#>
51+
Param(
52+
[Parameter(Mandatory = $false)]
53+
[String]
54+
$VenvDir,
55+
[Parameter(Mandatory = $false)]
56+
[String]
57+
$Prompt
58+
)
59+
60+
<# Function declarations --------------------------------------------------- #>
61+
62+
<#
63+
.Synopsis
64+
Remove all shell session elements added by the Activate script, including the
65+
addition of the virtual environment's Python executable from the beginning of
66+
the PATH variable.
67+
68+
.Parameter NonDestructive
69+
If present, do not remove this function from the global namespace for the
70+
session.
71+
72+
#>
73+
function global:deactivate ([switch]$NonDestructive) {
74+
# Revert to original values
75+
76+
# The prior prompt:
77+
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
78+
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
79+
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
80+
}
81+
82+
# The prior PYTHONHOME:
83+
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
84+
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
85+
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
86+
}
87+
88+
# The prior PATH:
89+
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
90+
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
91+
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
92+
}
93+
94+
# Just remove the VIRTUAL_ENV altogether:
95+
if (Test-Path -Path Env:VIRTUAL_ENV) {
96+
Remove-Item -Path env:VIRTUAL_ENV
97+
}
98+
99+
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
100+
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
101+
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
102+
}
103+
104+
# Leave deactivate function in the global namespace if requested:
105+
if (-not $NonDestructive) {
106+
Remove-Item -Path function:deactivate
107+
}
108+
}
109+
110+
<#
111+
.Description
112+
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
113+
given folder, and returns them in a map.
114+
115+
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
116+
two strings separated by `=` (with any amount of whitespace surrounding the =)
117+
then it is considered a `key = value` line. The left hand string is the key,
118+
the right hand is the value.
119+
120+
If the value starts with a `'` or a `"` then the first and last character is
121+
stripped from the value before being captured.
122+
123+
.Parameter ConfigDir
124+
Path to the directory that contains the `pyvenv.cfg` file.
125+
#>
126+
function Get-PyVenvConfig(
127+
[String]
128+
$ConfigDir
129+
) {
130+
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
131+
132+
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
133+
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
134+
135+
# An empty map will be returned if no config file is found.
136+
$pyvenvConfig = @{ }
137+
138+
if ($pyvenvConfigPath) {
139+
140+
Write-Verbose "File exists, parse `key = value` lines"
141+
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
142+
143+
$pyvenvConfigContent | ForEach-Object {
144+
$keyval = $PSItem -split "\s*=\s*", 2
145+
if ($keyval[0] -and $keyval[1]) {
146+
$val = $keyval[1]
147+
148+
# Remove extraneous quotations around a string value.
149+
if ("'""".Contains($val.Substring(0, 1))) {
150+
$val = $val.Substring(1, $val.Length - 2)
151+
}
152+
153+
$pyvenvConfig[$keyval[0]] = $val
154+
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
155+
}
156+
}
157+
}
158+
return $pyvenvConfig
159+
}
160+
161+
162+
<# Begin Activate script --------------------------------------------------- #>
163+
164+
# Determine the containing directory of this script
165+
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
166+
$VenvExecDir = Get-Item -Path $VenvExecPath
167+
168+
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
169+
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
170+
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
171+
172+
# Set values required in priority: CmdLine, ConfigFile, Default
173+
# First, get the location of the virtual environment, it might not be
174+
# VenvExecDir if specified on the command line.
175+
if ($VenvDir) {
176+
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
177+
}
178+
else {
179+
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
180+
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
181+
Write-Verbose "VenvDir=$VenvDir"
182+
}
183+
184+
# Next, read the `pyvenv.cfg` file to determine any required value such
185+
# as `prompt`.
186+
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
187+
188+
# Next, set the prompt from the command line, or the config file, or
189+
# just use the name of the virtual environment folder.
190+
if ($Prompt) {
191+
Write-Verbose "Prompt specified as argument, using '$Prompt'"
192+
}
193+
else {
194+
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
195+
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
196+
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
197+
$Prompt = $pyvenvCfg['prompt'];
198+
}
199+
else {
200+
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)"
201+
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
202+
$Prompt = Split-Path -Path $venvDir -Leaf
203+
}
204+
}
205+
206+
Write-Verbose "Prompt = '$Prompt'"
207+
Write-Verbose "VenvDir='$VenvDir'"
208+
209+
# Deactivate any currently active virtual environment, but leave the
210+
# deactivate function in place.
211+
deactivate -nondestructive
212+
213+
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
214+
# that there is an activated venv.
215+
$env:VIRTUAL_ENV = $VenvDir
216+
217+
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
218+
219+
Write-Verbose "Setting prompt to '$Prompt'"
220+
221+
# Set the prompt to include the env name
222+
# Make sure _OLD_VIRTUAL_PROMPT is global
223+
function global:_OLD_VIRTUAL_PROMPT { "" }
224+
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
225+
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
226+
227+
function global:prompt {
228+
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
229+
_OLD_VIRTUAL_PROMPT
230+
}
231+
}
232+
233+
# Clear PYTHONHOME
234+
if (Test-Path -Path Env:PYTHONHOME) {
235+
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
236+
Remove-Item -Path Env:PYTHONHOME
237+
}
238+
239+
# Add the venv to the PATH
240+
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
241+
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

0 commit comments

Comments
 (0)