Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 8f6dab3

Browse files
authored
v0.9.0 - Overhaul Pipelines (#42)
* v0.9.0 - Overhaul Pipelines * Fix Pipeline tests * 100% test coverage on pipelines * Update CHANGELOG
1 parent 6d9ffac commit 8f6dab3

24 files changed

+497
-487
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# CHANGELOG
22

3+
## v0.9.0 (2021-03-09)
4+
5+
* Overhauled the Pipeline/Webhook modules and removed lots of duplicate code
6+
* Fixed a bug where the pipeline timer wouldn't account for startup time (closes #35)
7+
* Added unit tests for the `pipelines` modules
8+
* Various other bug fixes
9+
310
## v0.8.2 (2021-03-06)
411

512
* Disabled container healthchecks temporarily for docker-compose workflows

harvey/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# flake8: noqa
2-
from harvey.globals import Global
3-
from harvey.container import Container
2+
from harvey.containers import Container
43
from harvey.git import Git
5-
from harvey.image import Image
6-
from harvey.message import Message
7-
from harvey.pipeline import Pipeline
8-
from harvey.stage import Stage
9-
from harvey.utils import Utils, Logs
10-
from harvey.webhook import Webhook
4+
from harvey.globals import Global
5+
from harvey.images import Image
6+
from harvey.messages import Message
7+
from harvey.pipelines import Pipeline
8+
from harvey.stages import Stage
9+
from harvey.utils import Logs, Utils
10+
from harvey.webhooks import Webhook

harvey/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from flask import Flask, abort, request
88

99
from harvey.globals import Global
10-
from harvey.webhook import Webhook
10+
from harvey.webhooks import Webhook
1111

1212
load_dotenv() # must remain at the top of this file
1313
API = Flask(__name__)
File renamed without changes.

harvey/globals.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class Global():
66
"""
77
DOCKER_VERSION = 'v1.41' # Docker API version
88
# TODO: Figure out how to sync this version number with the one in `setup.py`
9-
HARVEY_VERSION = '0.8.2' # Harvey release
9+
HARVEY_VERSION = '0.9.0' # Harvey release
1010
PROJECTS_PATH = 'projects'
1111
PROJECTS_LOG_PATH = 'logs/projects'
1212
HARVEY_LOG_PATH = 'logs/harvey'
@@ -16,6 +16,12 @@ class Global():
1616
JSON_HEADERS = {'Content-Type': 'application/json'}
1717
TAR_HEADERS = {'Content-Type': 'application/tar'}
1818
APP_MODE = os.getenv('MODE', 'production').lower()
19+
SUPPORTED_PIPELINES = [
20+
'pull',
21+
'test',
22+
'deploy',
23+
'full'
24+
]
1925

2026
@classmethod
2127
def repo_name(cls, webhook):
File renamed without changes.
File renamed without changes.

harvey/pipeline.py

Lines changed: 0 additions & 170 deletions
This file was deleted.

harvey/pipelines.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import json
2+
import os
3+
from datetime import datetime
4+
5+
from harvey.git import Git
6+
from harvey.globals import Global
7+
from harvey.messages import Message
8+
from harvey.stages import Stage
9+
from harvey.utils import Utils
10+
11+
SLACK = os.getenv('SLACK')
12+
13+
14+
class Pipeline():
15+
@classmethod
16+
def initialize_pipeline(cls, webhook):
17+
"""Initialize the setup for a pipeline by cloning/pulling the project
18+
and setting up standard logging info
19+
"""
20+
start_time = datetime.now()
21+
config = cls.open_project_config(webhook)
22+
23+
if SLACK:
24+
Message.send_slack_message(
25+
f'Harvey has started a `{config["pipeline"]}` pipeline for `{Global.repo_full_name(webhook)}`.'
26+
)
27+
28+
preamble = f'Running Harvey v{Global.HARVEY_VERSION}\n{config["pipeline"].title()} Pipeline Started: {start_time}' # noqa
29+
pipeline_id = f'Pipeline ID: {Global.repo_commit_id(webhook)}\n'
30+
print(preamble)
31+
git_message = (f'New commit by: {Global.repo_commit_author(webhook)}.'
32+
f'\nCommit made on repo: {Global.repo_full_name(webhook)}.')
33+
34+
git = Git.update_git_repo(webhook)
35+
36+
execution_time = f'Startup execution time: {datetime.now() - start_time}\n'
37+
output = (f'{preamble}\n{pipeline_id}Configuration:\n{json.dumps(config, indent=4)}'
38+
f'\n\n{git_message}\n{git}\n{execution_time}')
39+
print(execution_time)
40+
41+
return config, output, start_time
42+
43+
@classmethod
44+
def start_pipeline(cls, webhook, use_compose=False):
45+
"""After receiving a webhook, spin up a pipeline based on the config
46+
If a Pipeline fails, it fails early in the individual functions being called
47+
"""
48+
webhook_config, webhook_output, start_time = cls.initialize_pipeline(webhook)
49+
pipeline = webhook_config.get('pipeline').lower()
50+
51+
if pipeline in Global.SUPPORTED_PIPELINES:
52+
if pipeline == 'pull':
53+
final_output = f'{webhook_output}\nHarvey pulled the project successfully.'
54+
if pipeline in ['test', 'full']:
55+
test = cls.test(webhook_config, webhook, webhook_output, start_time)
56+
57+
end_time = datetime.now()
58+
execution_time = f'Pipeline execution time: {end_time - start_time}'
59+
pipeline_status = 'Pipeline succeeded!'
60+
61+
final_output = f'{webhook_output}\n{test}\n{execution_time}\n{pipeline_status}'
62+
if pipeline in ['deploy', 'full']:
63+
build, deploy, healthcheck = cls.deploy(webhook_config, webhook, webhook_output, start_time, use_compose) # noqa
64+
65+
stage_output = build + '\n' + deploy
66+
healthcheck_message = f'Project passed healthcheck: {healthcheck}'
67+
end_time = datetime.now()
68+
execution_time = f'Pipeline execution time: {end_time - start_time}'
69+
pipeline_status = 'Pipeline succeeded!'
70+
71+
final_output = f'{webhook_output}\n{stage_output}\n{execution_time}\n{healthcheck_message}\n{pipeline_status}' # noqa
72+
73+
Utils.success(final_output, webhook)
74+
else:
75+
final_output = webhook_output + '\nError: Harvey could not run, there was no acceptable pipeline specified.'
76+
pipeline = Utils.kill(final_output, webhook)
77+
78+
return final_output
79+
80+
@classmethod
81+
def open_project_config(cls, webhook):
82+
"""Open the project's config file to assign pipeline variables.
83+
84+
Project configs look like the following:
85+
{
86+
"pipeline": "full",
87+
"language": "php",
88+
"version": "7.4"
89+
}
90+
"""
91+
# TODO: Add the ability to configure projects on the Harvey side
92+
# (eg: save values to a database via a UI) instead of only from
93+
# within a JSON file in the repo
94+
try:
95+
filename = os.path.join(
96+
Global.PROJECTS_PATH, Global.repo_full_name(webhook), 'harvey.json'
97+
)
98+
with open(filename, 'r') as file:
99+
config = json.loads(file.read())
100+
print(json.dumps(config, indent=4))
101+
return config
102+
except FileNotFoundError:
103+
final_output = f'Error: Harvey could not find a "harvey.json" file in {Global.repo_full_name(webhook)}.'
104+
print(final_output)
105+
Utils.kill(final_output, webhook)
106+
107+
@classmethod
108+
def test(cls, config, webhook, output, start_time):
109+
"""Run the test stage in a pipeline
110+
"""
111+
test = Stage.test(config, webhook, output)
112+
if 'Error: the above command exited with code' in test:
113+
# TODO: Ensure this works, it may be broken
114+
end_time = datetime.now()
115+
pipeline_status = 'Pipeline failed!'
116+
execution_time = f'Pipeline execution time: {end_time - start_time}'
117+
final_output = f'{output}\n{test}\n{execution_time}\n{pipeline_status}'
118+
Utils.kill(final_output, webhook)
119+
120+
return test
121+
122+
@classmethod
123+
def deploy(cls, config, webhook, output, start_time, use_compose):
124+
"""Run the build and deploy stages in a pipeline
125+
"""
126+
if use_compose:
127+
build = '' # When using compose, there is no build step
128+
deploy = Stage.build_deploy_compose(config, webhook, output)
129+
# healthcheck = Stage.run_container_healthcheck(webhook) # TODO: Correct healthchecks for compose
130+
healthcheck = True
131+
else:
132+
build = Stage.build(config, webhook, output)
133+
deploy = Stage.deploy(webhook, output)
134+
healthcheck = Stage.run_container_healthcheck(webhook)
135+
136+
if healthcheck is False:
137+
end_time = datetime.now()
138+
pipeline_status = 'Pipeline failed due to a bad healthcheck.'
139+
execution_time = f'Pipeline execution time: {end_time - start_time}'
140+
healthcheck_message = f'Project passed healthcheck: {healthcheck}'
141+
final_output = f'{output}\n{build}\n{deploy}\n{execution_time}\n{healthcheck_message}\n{pipeline_status}'
142+
Utils.kill(final_output, webhook)
143+
144+
return build, deploy, healthcheck

0 commit comments

Comments
 (0)