Skip to content

Commit

Permalink
v0.4.0 - added slack preamble message, fixed git logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Justintime50 committed Oct 23, 2020
1 parent 602c499 commit 719f2da
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 52 deletions.
9 changes: 8 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Webhook Config
WEBHOOK_SECRET=

# Slack
# Leave SLACK empty if you want this to be false, otherwise set to `True` (https://dev.to/nicolaerario/comment/fe1e)
SLACK=True
SLACK_CHANNEL=
SLACK_BOT_TOKEN=
WEBHOOK_SECRET=

# Flask API
MODE=test
HOST=127.0.0.1
PORT=5000
Expand Down
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[flake8]
max-line-length = 100
max-line-length = 120
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
# CHANGELOG

## v0.4.0 (2020-10-23)

* Added a preamble Slack message when a pipeline starts. Now you'll get notified when a pipeline starts in addition to when it finishes (closes #39)
* Added a `SLACK` env variable so you can decide if you want to send Slack messages or not
* Added unit tests for the `message` module
* Documented all env variables in the README
* Fixed a bug where git clone/pull logic was swapped and wasn't returning anything

## v0.3.0 (2020-10-22)

* Refactored git logic into smaller units
* Added unit tests for `git` modules
* Added unit tests for `git` module
* Changed `fast forward` git operation to `rebase`

## v0.2.0 (2020-09-18)
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pip3 install harvey-ci

# Install locally
make install
cp .env.example .env

# Get Makefile help
make help
Expand Down Expand Up @@ -86,6 +87,20 @@ Harvey's entrypoint is a webhook (eg: `127.0.0.1:5000/harvey`). Pass GitHub data
}
```

### Configuration

```
Environment Variables:
SLACK Set to "true" to send slack messages
SLACK_CHANNEL The Slack channel to send messages to
SLACK_BOT_TOKEN The Slackbot token to use to authenticate each request to Slack
WEBHOOK_SECRET The Webhook secret required by GitHub (if enabled) to secure your webhooks
MODE Set to "test" to bypass the header and auth data from GitHub to test
HOST The host Harvey will run on. Default: 127.0.0.1
PORT The port Harvey will run on. Default: 5000
DEBUG Whether the Flask API will run in debug mode or not
```

### Start API Server (for Webhook)

**Start Harvey:**
Expand Down Expand Up @@ -113,7 +128,7 @@ Take the URL Ngrok provides and use that on your webhooks.
See `examples.py` for all available methods of each class. Almost every usage example is contained in this file.

```bash
venv/bin/python examples.py
venv/bin/python examples/examples.py
```

**Example API Call:**
Expand Down
24 changes: 12 additions & 12 deletions examples/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@
# }
# )

# """API Entrypoint (Webhook)"""
# with open('./git_webhook.json', 'r') as file:
# request = requests.post(
# 'http://127.0.0.1:5000/harvey',
# data=file,
# headers=harvey.Global.JSON_HEADERS
# )
# print(request)
"""API Entrypoint (Webhook)"""
with open('examples/git_webhook.json', 'r') as file:
request = requests.post(
'http://127.0.0.1:5000/harvey',
data=file,
headers=harvey.Global.JSON_HEADERS
)
print(request)

# """Retrieve a Pipeline by ID"""
# request = requests.get(
Expand All @@ -104,7 +104,7 @@
# )
# print(request.text)

"""Retrieve all pipelines"""
request = requests.get(
'http://127.0.0.1:5000/pipelines', headers=harvey.Global.JSON_HEADERS)
print(request.text)
# """Retrieve all pipelines"""
# request = requests.get(
# 'http://127.0.0.1:5000/pipelines', headers=harvey.Global.JSON_HEADERS)
# print(request.text)
2 changes: 1 addition & 1 deletion harvey/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from harvey.container import Container
from harvey.git import Git
from harvey.image import Image
from harvey.messages import Message
from harvey.message import Message
from harvey.pipeline import Pipeline
from harvey.stage import Stage
from harvey.utils import Utils, Logs
Expand Down
10 changes: 6 additions & 4 deletions harvey/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ def update_git_repo(cls, webhook):
return output.decode('UTF-8')

@classmethod
def clone_repo(cls, project_path, webhook):
"""Clone a repo into the Harvey projects folder
def pull_repo(cls, project_path, webhook):
"""Pull updates for a repo in the Harvey projects folder
"""
try:
final_output = subprocess.check_output(
Expand All @@ -31,6 +31,7 @@ def clone_repo(cls, project_path, webhook):
timeout=Global.GIT_TIMEOUT
)
print(final_output)
return final_output
except subprocess.TimeoutExpired:
final_output = 'Error: Harvey timed out during git pull operation.'
print(final_output)
Expand All @@ -41,8 +42,8 @@ def clone_repo(cls, project_path, webhook):
Utils.kill(final_output, webhook)

@classmethod
def pull_repo(cls, project_path, webhook):
"""Pull updates for a repo in the Harvey projects folder
def clone_repo(cls, project_path, webhook):
"""Clone a repo into the Harvey projects folder
"""
try:
final_output = subprocess.check_output(
Expand All @@ -53,6 +54,7 @@ def pull_repo(cls, project_path, webhook):
timeout=Global.GIT_TIMEOUT
)
print(final_output)
return final_output
except subprocess.TimeoutExpired:
final_output = 'Error: Harvey timed out during git clone operation.'
print(final_output)
Expand Down
32 changes: 32 additions & 0 deletions harvey/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
import sys
import slack


SLACK_BOT_TOKEN = os.getenv('SLACK_BOT_TOKEN')
SLACK_CHANNEL = os.getenv('SLACK_CHANNEL')


class Message():
@classmethod
def send_slack_message(cls, message):
"""Send a Slack message via a Slackbot
"""
slack_client = slack.WebClient(SLACK_BOT_TOKEN)
try:
slack_client.chat_postMessage(
channel=SLACK_CHANNEL,
text=message
)
print('Slack message sent!')
except slack.errors.SlackApiError:
final_output = 'Harvey could not send the Slack message.'
print(final_output)
sys.exit(final_output)

@classmethod
def send_email(cls, message):
"""Send an email message
"""
# TODO: Add email functionality
raise NotImplementedError()
24 changes: 0 additions & 24 deletions harvey/messages.py

This file was deleted.

27 changes: 25 additions & 2 deletions harvey/pipeline.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from datetime import datetime
from .stage import Stage
from .utils import Utils
import os
from harvey.stage import Stage
from harvey.utils import Utils
from harvey.globals import Global
from harvey.message import Message


class Pipeline():
Expand All @@ -9,6 +12,10 @@ def test(cls, config, webhook, output):
"""Pull changes and run tests (no deploy)
"""
start_time = datetime.now()
if os.getenv('SLACK'):
Message.send_slack_message(
f'Harvey has started a `{config["pipeline"]}` pipeline for `{Global.repo_full_name(webhook)}`.'
)
test = Stage.test(config, webhook, output)
if 'Error: the above command exited with code' in test:
execution_time = f'Test pipeline execution time: {datetime.now() - start_time}'
Expand All @@ -28,6 +35,10 @@ def deploy(cls, config, webhook, output):
"""Pull changes and build/deploy (no tests)
"""
start_time = datetime.now()
if os.getenv('SLACK'):
Message.send_slack_message(
f'Harvey has started a `{config["pipeline"]}` pipeline for `{Global.repo_full_name(webhook)}`.'
)
build = Stage.build(config, webhook, output)
deploy = Stage.deploy(webhook, output)
execution_time = f'Deploy pipeline execution time: {datetime.now() - start_time}'
Expand All @@ -43,6 +54,10 @@ def full(cls, config, webhook, output):
"""Pull changes, run tests, build image, start container
"""
start_time = datetime.now()
if os.getenv('SLACK'):
Message.send_slack_message(
f'Harvey has started a `{config["pipeline"]}` pipeline for `{Global.repo_full_name(webhook)}`.'
)
test = Stage.test(config, webhook, output)
if 'Error: the above command exited with code' in test:
execution_time = f'Full pipeline execution time: {datetime.now() - start_time}'
Expand All @@ -64,6 +79,10 @@ def deploy_compose(cls, config, webhook, output):
"""Pull changes and build/deploy (no tests) - USING A DOCKER COMPOSE FILE
"""
start_time = datetime.now()
if os.getenv('SLACK'):
Message.send_slack_message(
f'Harvey has started a `{config["pipeline"]}` pipeline for `{Global.repo_full_name(webhook)}`.'
)
deploy = Stage.build_deploy_compose(config, webhook, output)
execution_time = f'Deploy pipeline execution time: {datetime.now() - start_time}'
success = 'Deploy pipeline succeeded!'
Expand All @@ -78,6 +97,10 @@ def full_compose(cls, config, webhook, output):
"""Pull changes, run tests, build image, start container - USING A DOCKER COMPOSE FILE
"""
start_time = datetime.now()
if os.getenv('SLACK'):
Message.send_slack_message(
f'Harvey has started a `{config["pipeline"]}` pipeline for `{Global.repo_full_name(webhook)}`.'
)
test = Stage.test(config, webhook, output)
if 'Error: the above command exited with code' in test:
execution_time = f'Full pipeline execution time: {datetime.now() - start_time}'
Expand Down
10 changes: 6 additions & 4 deletions harvey/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import sys
from .globals import Global
from .messages import Message
from harvey.globals import Global
from harvey.message import Message


class Utils():
Expand All @@ -11,15 +11,17 @@ def kill(cls, final_output, webhook):
tear down Docker stuff, and quit
"""
Logs.generate_logs(final_output, webhook)
Message.slack(final_output)
if os.getenv('SLACK'):
Message.send_slack_message(final_output)
sys.exit()

@classmethod
def success(cls, final_output, webhook):
"""Log output and send message on pipeline success
"""
Logs.generate_logs(final_output, webhook)
Message.slack(final_output)
if os.getenv('SLACK'):
Message.send_slack_message(final_output)


class Logs():
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

setuptools.setup(
name='harvey-ci',
version='0.3.0',
version='0.4.0',
description='Your personal CI/CD and Docker orchestration platform.',
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down
31 changes: 31 additions & 0 deletions test/unit/test_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import mock
import pytest
import slack
from harvey.message import Message


@mock.patch('harvey.message.SLACK_CHANNEL', 'mock-channel')
@mock.patch('harvey.message.SLACK_BOT_TOKEN', '123')
@mock.patch('slack.WebClient.chat_postMessage')
def test_send_slack_message_success(mock_slack):
message = 'mock message'
Message.send_slack_message(message)
mock_slack.assert_called_once_with(channel='mock-channel', text=message)


@mock.patch('sys.exit')
@mock.patch('slack.WebClient.chat_postMessage',
side_effect=slack.errors.SlackApiError(
message='The request to the Slack API failed.',
response={'ok': False, 'error': 'not_authed'}
))
def test_send_slack_message_exception(mock_slack, mock_sys_exit):
message = 'mock message'
Message.send_slack_message(message)
mock_sys_exit.assert_called_once_with('Harvey could not send the Slack message.')


def test_send_email():
message = 'mock message'
with pytest.raises(NotImplementedError):
Message.send_email(message)

0 comments on commit 719f2da

Please sign in to comment.