Compare commits
3 Commits
master
...
missing-pa
Author | SHA1 | Date |
---|---|---|
Marek Baczynski | e21ec9065d | 2 years ago |
Marek Baczynski | 4988f0eebd | 2 years ago |
Marek Baczynski | aca7505ae9 | 2 years ago |
@ -0,0 +1,3 @@
|
||||
[submodule "Git-Auto-Deploy"]
|
||||
path = deploy/Git-Auto-Deploy
|
||||
url = https://github.com/olipo186/Git-Auto-Deploy.git
|
@ -0,0 +1,86 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
GitAutoDeploy.conf.json
|
||||
config.json
|
||||
*.tc.json
|
||||
|
||||
.idea
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
.fuse*
|
||||
|
||||
# Ignore
|
||||
deb_dist
|
||||
*.tar.gz
|
||||
MANIFEST
|
||||
|
||||
# dependencies
|
||||
webui/node_modules
|
||||
|
||||
# testing
|
||||
webui/coverage
|
||||
|
||||
# production
|
||||
webui/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log
|
@ -0,0 +1,14 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
# - "2.7"
|
||||
# - "3.2"
|
||||
# - "3.3"
|
||||
# - "3.4"
|
||||
- "3.5"
|
||||
# - "3.5-dev" # 3.5 development branch
|
||||
# - "nightly" # currently points to 3.6-dev
|
||||
# command to install dependencies
|
||||
install: "pip install -r requirements.txt"
|
||||
# command to run tests
|
||||
script: python test/test_parsers.py
|
@ -0,0 +1,9 @@
|
||||
FROM google/python-runtime
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install openssh-client
|
||||
|
||||
RUN mkdir $HOME/.ssh && chmod 600 $HOME/.ssh
|
||||
COPY deploy_rsa /root/.ssh/id_rsa
|
||||
|
||||
ENTRYPOINT ["/env/bin/python", "-u", "GitAutoDeploy.py", "--ssh-keyscan"]
|
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import os
|
||||
import gitautodeploy
|
||||
sys.stderr.write("\033[1;33m[WARNING]\033[0;33m GitAutoDeploy.py is deprecated. Please use \033[1;33m'python gitautodeploy%s'\033[0;33m instead.\033[0m\n" % (' ' + ' '.join(sys.argv[1:])).rstrip())
|
||||
gitautodeploy.main()
|
Binary file not shown.
@ -0,0 +1,102 @@
|
||||
.PHONY: all detect install initsystem
|
||||
|
||||
prefix = /opt/Git-Auto-Deploy/
|
||||
|
||||
#init_version := $(shell /sbin/init --version 2>&1)
|
||||
#test_upstart := $(shell printf $(init_version) | grep -q upstart || grep -q upstart /proc/net/unix ; echo $$?)
|
||||
#test_systemd := $(shell printf $(init_version) | grep -q systemd || grep -q systemd /proc/1/comm || grep -q systemd /proc/net/unix ; echo $$?)
|
||||
|
||||
PYTHON ?= python2
|
||||
|
||||
# Debian distos to create packages for
|
||||
DISTROS= \
|
||||
bionic \
|
||||
artful \
|
||||
xenial \
|
||||
trusty \
|
||||
precise
|
||||
|
||||
# Package name and version
|
||||
PACKAGE_NAME=$(shell python setup.py --name)
|
||||
PACKAGE_VERSION=$(shell python setup.py --version)
|
||||
|
||||
define version =
|
||||
echo "hello"
|
||||
exit 1
|
||||
endef
|
||||
|
||||
all:
|
||||
|
||||
clean: clean-pypi clean-deb
|
||||
rm -rf *.tar.gz
|
||||
|
||||
clean-pypi:
|
||||
rm -rf *.egg-info
|
||||
rm -rf dist/pypi
|
||||
if [ -d "dist" ]; then rmdir --ignore-fail-on-non-empty dist; fi
|
||||
|
||||
pypi:
|
||||
$(PYTHON) setup.py sdist --dist-dir dist/pypi
|
||||
|
||||
upload-pypi:
|
||||
$(PYTHON) setup.py sdist --dist-dir dist/pypi register upload -r pypi
|
||||
|
||||
clean-deb:
|
||||
rm -rf dist/deb
|
||||
rm -f dist/*.tar.gz
|
||||
if [ -d "dist" ]; then rmdir --ignore-fail-on-non-empty dist; fi
|
||||
|
||||
# Usage: make deb-source [DIST=<debian dist>]
|
||||
deb-source: clean-deb
|
||||
@- $(foreach DIST,$(DISTROS), \
|
||||
echo "Creating deb source package for dist $(DIST)" ; \
|
||||
echo "Make a debian source package using stdeb" ; \
|
||||
python setup.py --command-packages=stdeb.command sdist_dsc -x platforms/debian/stdeb.cfg --dist-dir dist/deb --debian-version $(DIST) --suite $(DIST) --upstream-version-suffix "~$(DIST)" ; \
|
||||
echo "Copy debian package config files" ; \
|
||||
cp -vr platforms/debian/stdeb/* dist/deb/$(PACKAGE_NAME)-$(PACKAGE_VERSION)~$(DIST)/debian/ ; \
|
||||
)
|
||||
|
||||
deb: clean-deb deb-source
|
||||
@- $(foreach DIST,$(DISTROS), \
|
||||
echo "Creating unsigned deb package for dist $(DIST)" ; \
|
||||
echo "Build .deb package (without signing)" ; \
|
||||
cd dist/deb/$(PACKAGE_NAME)-$(PACKAGE_VERSION)~$(DIST) ; \
|
||||
dpkg-buildpackage -rfakeroot -uc -us ; \
|
||||
cd ../../../ ; \
|
||||
)
|
||||
|
||||
signed-deb: clean-deb deb-source
|
||||
@- $(foreach DIST,$(DISTROS), \
|
||||
echo "Creating signed deb package for dist $(DIST)" ; \
|
||||
echo "Build .deb package (signed)" ; \
|
||||
cd dist/deb/$(PACKAGE_NAME)-$(PACKAGE_VERSION)~$(DIST) ; \
|
||||
debuild -S -sa ; \
|
||||
cd ../../../ ; \
|
||||
)
|
||||
|
||||
upload-deb: clean-deb signed-deb
|
||||
@- $(foreach DIST,$(DISTROS), \
|
||||
echo "Upload signed debian package to ppa for dist $(DIST)" ; \
|
||||
dput ppa:olipo186/$(PACKAGE_NAME) dist/deb/$(PACKAGE_NAME)_$(PACKAGE_VERSION)~$(DIST)-$(DIST)_source.changes ; \
|
||||
)
|
||||
|
||||
|
||||
#initsystem:
|
||||
#ifeq ($(test_upstart),0)
|
||||
# @echo "Upstart detected!"
|
||||
#else ifeq ($(test_systemd),0)
|
||||
# @echo "Systemd detected!"
|
||||
#else
|
||||
# @echo "InitV supposed"
|
||||
#endif
|
||||
# @echo "Init script not installed - not yet implemented"
|
||||
|
||||
#install: clean all
|
||||
# @echo "Installing deploy script in $(prefix) ..."
|
||||
# @echo "Installing deploy script in $(init_version) ..."
|
||||
# @sudo mkdir $(prefix) &> /dev/null || true
|
||||
# @sudo cp config.json.sample $(prefix)config.json
|
||||
# @sudo cp -r gitautodeploy $(prefix)/
|
||||
#
|
||||
# @echo "Installing run-on-startup scripts according to your init system ..."
|
||||
# @make initsystem
|
@ -0,0 +1,128 @@
|
||||
[![Build Status](https://travis-ci.org/olipo186/Git-Auto-Deploy.svg?branch=master)](https://travis-ci.org/olipo186/Git-Auto-Deploy)
|
||||
# What is it?
|
||||
|
||||
Git-Auto-Deploy consists of a small HTTP server that listens for Webhook requests sent from GitHub, GitLab or Bitbucket servers. This application allows you to continuously and automatically deploy your projects each time you push new commits to your repository.</p>
|
||||
|
||||
![workflow](https://cloud.githubusercontent.com/assets/1056476/9344294/d3bc32a4-4607-11e5-9a44-5cd9b22e61d9.png)
|
||||
|
||||
# How does it work?
|
||||
|
||||
When commits are pushed to your Git repository, the Git server will notify ```Git-Auto-Deploy``` by sending an HTTP POST request with a JSON body to a pre-configured URL (your-host:8001). The JSON body contains detailed information about the repository and what event that triggered the request. ```Git-Auto-Deploy``` parses and validates the request, and if all goes well it issues a ```git pull```.
|
||||
|
||||
Additionally, ```Git-Auto-Deploy``` can be configured to execute a shell command upon each successful ```git pull```, which can be used to trigger custom build actions or test scripts.</p>
|
||||
|
||||
# Getting started
|
||||
|
||||
You can install ```Git-Auto-Deploy``` in multiple ways. Below are instructions for the most common methods.
|
||||
|
||||
## Install from PPA (recommended for Ubuntu systems)
|
||||
|
||||
[Using Debian? Have a look at this answer for instructions.](https://github.com/olipo186/Git-Auto-Deploy/issues/153)
|
||||
|
||||
Add our PPA repository.
|
||||
|
||||
sudo apt-get install software-properties-common
|
||||
sudo add-apt-repository ppa:olipo186/git-auto-deploy
|
||||
sudo apt-get update
|
||||
|
||||
Install ```Git-Auto-Deploy``` using apt.
|
||||
|
||||
sudo apt-get install git-auto-deploy
|
||||
|
||||
Modify the configuration file to match your project setup. [Read more about the configuration options](./docs/Configuration.md).
|
||||
|
||||
nano /etc/git-auto-deploy.conf.json
|
||||
|
||||
Optional: Copy any private SSH key you wish to use to the home directory of GAD.
|
||||
|
||||
sudo cp /path/to/id_rsa /etc/git-auto-deploy/.ssh/
|
||||
sudo chown -R git-auto-deploy:git-auto-deploy /etc/git-auto-deploy
|
||||
|
||||
Start ```Git-Auto-Deploy``` and check it's status.
|
||||
|
||||
service git-auto-deploy start
|
||||
service git-auto-deploy status
|
||||
|
||||
## Install from repository (recommended for other systems)
|
||||
|
||||
When installing ```Git-Auto-Deploy``` from the repository, you'll need to make sure that Python (tested on version 2.7) and Git (tested on version 2.5.0) is installed on your system.
|
||||
|
||||
Clone the repository.
|
||||
|
||||
git clone https://github.com/olipo186/Git-Auto-Deploy.git
|
||||
|
||||
Install the dependencies with [pip](http://www.pip-installer.org/en/latest/), a package manager for Python, by running the following command.
|
||||
|
||||
sudo pip install -r requirements.txt
|
||||
|
||||
If you don't have pip installed, try installing it by running this from the command
|
||||
line:
|
||||
|
||||
curl https://bootstrap.pypa.io/get-pip.py | python
|
||||
|
||||
Copy of the sample config and modify it. [Read more about the configuration options](./docs/Configuration.md). Make sure that ```pidfilepath``` is writable for the user running the script, as well as all paths configured for your repositories.
|
||||
|
||||
cd Git-Auto-Deploy
|
||||
cp config.json.sample config.json
|
||||
|
||||
Start ```Git-Auto-Deploy``` manually using;
|
||||
|
||||
python -m gitautodeploy --config config.json
|
||||
|
||||
To start ```Git-Auto-Deploy``` automatically on boot, open crontab in edit mode using ```crontab -e``` and add the entry below.
|
||||
|
||||
@reboot /usr/bin/python -m /path/to/Git-Auto-Deploy/gitautodeploy --daemon-mode --quiet --config /path/to/git-auto-deploy.conf.json
|
||||
|
||||
You can also configure ```Git-Auto-Deploy``` to start on boot using an init.d-script (for Debian and Sys-V like init systems) or a service for systemd.[Read more about starting Git-Auto-Deploy automatically using init.d or systemd](./docs/Start%20automatically%20on%20boot.md).
|
||||
|
||||
## Install and run GAD under Windows
|
||||
GAD runs under Windows but requires some requisites.
|
||||
|
||||
1. Install Python 2.7 using the [Windows installer](https://www.python.org/downloads/).
|
||||
2. Verify that Python is added to your [system PATH](https://technet.microsoft.com/en-us/library/cc772047(v=ws.11).aspx). Make sure ``C:\Python27`` and ``C:\Python27\Scripts`` is part of the PATH system environment variable.
|
||||
3. Install pip using the [``get-pip.py`` script](https://pip.pypa.io/en/latest/installing/)
|
||||
4. Install Git using the [official Git build for Windows](https://git-scm.com/download/win)
|
||||
5. Verify that Git is added to your [system PATH](https://technet.microsoft.com/en-us/library/cc772047(v=ws.11).aspx). Make sure that ```C:\Program Files\Git\cmd``` is added (should have been added automatically by the installer) as well as ```C:\Program Files\Git\bin``` (*not* added by default).
|
||||
6. Continue with the above instructions for [installing GAD from the repository](#install-from-repository-recommended-for-other-systems)
|
||||
|
||||
## Alternative installation methods
|
||||
|
||||
* [Install as a python module (experimental)](./docs/Install%20as%20a%20python%20module.md)
|
||||
* [Install as a debian package (experimental)](./docs/Install%20as%20a%20debian%20package.md)
|
||||
* [Start automatically on boot (init.d and systemd)](./docs/Start%20automatically%20on%20boot.md)
|
||||
|
||||
## Command line options
|
||||
|
||||
Below is a summarized list of the most common command line options. For a full list of available command line options, invoke the application with the argument ```--help``` or read the documentation article about [all available command line options, environment variables and config attributes](./docs/Configuration.md).
|
||||
|
||||
Command line option | Environment variable | Config attribute | Description
|
||||
---------------------- | -------------------- | ---------------- | --------------------------
|
||||
--daemon-mode (-d) | GAD_DAEMON_MODE | | Run in background (daemon mode)
|
||||
--quiet (-q) | GAD_QUIET | | Supress console output
|
||||
--config (-c) <path> | GAD_CONFIG | | Custom configuration file
|
||||
--pid-file <path> | GAD_PID_FILE | pidfilepath | Specify a custom pid file
|
||||
--log-file <path> | GAD_LOG_FILE | logfilepath | Specify a log file
|
||||
--host <host> | GAD_HOST | host | Address to bind to
|
||||
--port <port> | GAD_PORT | port | Port to bind to
|
||||
|
||||
## Getting webhooks from git
|
||||
To make your git provider send notifications to ```Git-Auto-Deploy``` you will need to provide the hostname and port for your ```Git-Auto-Deploy``` instance. Instructions for the most common git providers is listed below.
|
||||
|
||||
**GitHub**
|
||||
1. Go to your repository -> Settings -> Webhooks and Services -> Add webhook</li>
|
||||
2. In "Payload URL", enter your hostname and port (your-host:8001)
|
||||
3. Hit "Add webhook"
|
||||
|
||||
**GitLab**
|
||||
1. Go to your repository -> Settings -> Web hooks
|
||||
2. In "URL", enter your hostname and port (your-host:8001)
|
||||
3. Hit "Add Web Hook"
|
||||
|
||||
**Bitbucket**
|
||||
1. Go to your repository -> Settings -> Webhooks -> Add webhook
|
||||
2. In "URL", enter your hostname and port (your-host:8001)
|
||||
3. Hit "Save"
|
||||
|
||||
# More documentation
|
||||
|
||||
[Have a look in the *docs* directory](./docs), where you'll find more detailed documentation on configurations, alternative installation methods, and example workflows.
|
@ -0,0 +1,70 @@
|
||||
{
|
||||
// HTTP server options
|
||||
//"http-enabled": true,
|
||||
//"http-host": "0.0.0.0",
|
||||
//"http-port": 8001,
|
||||
|
||||
// HTTPS server options
|
||||
//"https-enabled": false,
|
||||
//"https-host": "0.0.0.0",
|
||||
//"https-port": 8002,
|
||||
|
||||
// Web socket server options (used by web UI for real time updates)
|
||||
//"wss-enabled": false,
|
||||
//"wss-host": "0.0.0.0",
|
||||
//"wss-port": 8003,
|
||||
|
||||
// Web user interface options
|
||||
//"web-ui-enabled": false,
|
||||
//"web-ui-username": null,
|
||||
//"web-ui-password": null,
|
||||
//"web-ui-whitelist": ["127.0.0.1"],
|
||||
|
||||
// TLS/SSL cert (necessary for HTTPS and web socket server to work)
|
||||
//"ssl-key": null, // If specified, holds the private key
|
||||
//"ssl-cert": "cert.pem", // Holds the public key or both the private and public keys
|
||||
|
||||
// File to store a copy of the console output
|
||||
"log-file": "~/gitautodeploy.log",
|
||||
|
||||
// File to store the process id (pid)
|
||||
"pid-file": "~/.gitautodeploy.pid",
|
||||
|
||||
// Record all log levels by default
|
||||
"log-level": "INFO",
|
||||
|
||||
// Deploy commands that should be executed for all projects
|
||||
//"global_deploy": [
|
||||
// "echo Deploy started!",
|
||||
// "echo Deploy completed!"
|
||||
//],
|
||||
|
||||
"repositories": [
|
||||
{
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"branch": "master",
|
||||
"remote": "origin",
|
||||
"path": "~/repositories/Git-Auto-Deploy",
|
||||
"deploy": "echo deploying"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/github/gitignore",
|
||||
"path": "~/repositories/gitignore"
|
||||
},
|
||||
{
|
||||
"url": "git@gitlab.com:gitlab-org/gitlab-ce.git",
|
||||
"path": "~/repositories/gitignore"
|
||||
},
|
||||
{
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"deploy": "echo deploying after pull request",
|
||||
"filters": [
|
||||
{
|
||||
"type": "pull-request-filter",
|
||||
"action": "closed",
|
||||
"ref": "testing-branch"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,343 @@
|
||||
# Command line options and environment variables
|
||||
|
||||
```Git-Auto-Deploy``` supports a number of configurable options. Some of them are available using command line options, where others are only configurable from the config file. Below is a list of the options made available from the command line. Every command line option has also a corresponding environment variable. In the cases where a corresponding config file attribute is available, that attribute name is listed.
|
||||
|
||||
There is also support for supplying configuration options for up to one repository using environmetn variables. Variable names and descriptios are available in the section (Repository configuration using environment variables)[#eepository-configuration-using-environment-variables].
|
||||
|
||||
The list of available command line options can also be seen by invoke the application with the argument ```--help```.
|
||||
|
||||
Command line option | Environment variable | Config attribute | Description
|
||||
---------------------- | -------------------- | ---------------- | --------------------------
|
||||
--daemon-mode (-d) | GAD_DAEMON_MODE | | Run in background (daemon mode)
|
||||
--quiet (-q) | GAD_QUIET | | Supress console output
|
||||
--config (-c) <path> | GAD_CONFIG | | Custom configuration file
|
||||
--pid-file <path> | GAD_PID_FILE | pidfilepath | Specify a custom pid file
|
||||
--log-file <path> | GAD_LOG_FILE | logfilepath | Specify a log file
|
||||
--host <host> | GAD_HOST | host | Address to bind to
|
||||
--port <port> | GAD_PORT | port | Port to bind to
|
||||
--ssh-keyscan | GAD_SSH_KEYSCAN | | Scan repository hosts for ssh keys and add them to $HOME/.ssh/known_hosts
|
||||
|
||||
# Configuration file options
|
||||
The configuration file is formatted according to a `JSON` inspired format, with the additional feature of supporting inline comments. The possible root elements are
|
||||
as follow:
|
||||
|
||||
- **pidfilepath**: The path where `pid` files are kept.
|
||||
- **logfilepath**: To enable logging, set this to a valid file path.
|
||||
- **log-level**: Sets the threshold for the log output. Default value is NOTSET (all details). Recommended value is INFO (less details).
|
||||
- **host**: What IP address to listen on.
|
||||
- **port**: The port for the web server to listen on.
|
||||
- **global_deploy**: An array of two specific commands or path to scripts
|
||||
to be executed for all repositories defined:
|
||||
- `[0]` = The pre-deploy script.
|
||||
- `[1]` = The post-deploy script.
|
||||
- **repositories**: An array of repository configurations.
|
||||
|
||||
## Repositories
|
||||
Repository configurations are comprised of the following elements:
|
||||
|
||||
- **url**: The URL to the repository.
|
||||
- **match-url**: An alternative URL used when matching incoming webhook requests (see https://github.com/olipo186/Git-Auto-Deploy/pull/148)
|
||||
- **branch**: The branch which will be checked out.
|
||||
- **remote**: The name of the remote to use.
|
||||
- **path**: Path to clone the repository to. If omitted, the repository won't
|
||||
be cloned, only the deploy scripts will be executed.
|
||||
- **deploy**: A command to be executed. If `path` is set, the command is
|
||||
executed after a successfull `pull`.
|
||||
- **payload-filter**: A list of inclusive filters/rules that is applied to the request body of incoming web hook requests and determines whether the deploy command should be executed or not. See section *Filters* for more details.
|
||||
- **header-filter**: A set of inclusive filters/rules that is applied to the request header of incoming web hook requests and determines whether the deploy command should be executed or not. See section *Filters* for more details.
|
||||
- **secret-token**: The secret token set for your webhook (currently only implemented for [GitHub](https://developer.github.com/webhooks/securing/) and GitLab)
|
||||
- **prepull**: A command to execute immediately before the `git pull`. This command could do something required for the ``git pull`` to succeed such as changing file permissions.
|
||||
- **postpull**: A command to execute immediately after the `git pull`. After the **prepull** command is executed, **postpull** can clean up any changes made.
|
||||
|
||||
## Filters
|
||||
*(Currently only supported for GitHub and GitLab)*
|
||||
|
||||
With filters, it is possible to trigger the deploy only if a set of specific criterias are met. The filter can be applied to the web hook request header (if specified using the *header-filter* option) or to the request body (*payload-filter*).
|
||||
|
||||
### Allow web hooks with specific header values only (header-filter)
|
||||
|
||||
Some Git providers will add custom HTTP headers in their web hook requests when sending them to GAD. Using a *header-filter*, you can configure GAD to only process web hooks that has a specific HTTP header specified.
|
||||
|
||||
For example, if you'd like to only process requests that has the *X-Event-Key* header set to the value *pullrequest:fulfilled*, you could use the following config;
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"repositories": [
|
||||
{
|
||||
...
|
||||
"header-filter": {
|
||||
"X-Event-Key": "pullrequest:fulfilled"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If a header name is specified but with the value set to true, any request that has the header specified will pass without regard to the header value.
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"repositories": [
|
||||
{
|
||||
...
|
||||
"header-filter": {
|
||||
"X-Event-Key": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Allow web hooks with specific payload only (payload-filter)
|
||||
|
||||
A web hook request typically contains a payload, or a request body, made up of a JSON object. The JSON object in the request body will follow a format choosen by the Git server. Thus, it's format will differ depending on whether you are using GitHub, GitLab, Bitbucket or any other Git provider.
|
||||
|
||||
A *payload-filter* can be used to set specific criterias for which incoming web hook requests should actually trigger the deploy command. Filter can be setup to only trigger deploys when a commit is made to a specific branch, or when a pull request is closed and has a specific destination branch.
|
||||
|
||||
Since the format of the payload differs depending on what Git provider you are using, you'll need to inspect the web hook request format yourself and write a filter that matches its structure.
|
||||
|
||||
To specify a filter that should be applied further down the object tree, a dot notation (".") is used. For example, if the request body looks like this;
|
||||
```json
|
||||
{
|
||||
"action": "opened",
|
||||
"number": 69,
|
||||
"pull_request": {
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69",
|
||||
"id": 61793882,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69",
|
||||
"diff_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.diff",
|
||||
"patch_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.patch",
|
||||
"issue_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69",
|
||||
"number": 69,
|
||||
"state": "open",
|
||||
"locked": false,
|
||||
"title": "Refactoring. Fixed some imminent issues.",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You could specify the following filter, which would only trigger on pull requests created by olipo186.
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"repositories": [
|
||||
{
|
||||
...
|
||||
"payload-filter": [
|
||||
{
|
||||
"action": "opened",
|
||||
"pull_request.user.login": "olipo186"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Legacy filters (older format)
|
||||
|
||||
For example, deploy on `push` to the `master` branch only, ignore other branches.
|
||||
|
||||
Filters are defined by providing keys/values to be looked up in the original
|
||||
data sent by the web hook.
|
||||
|
||||
For example, GitLab web hook data looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"object_kind":"build",
|
||||
"ref":"master",
|
||||
"tag":false,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
A filter can use `object_kind` and `ref` attributes for example to execute the
|
||||
deploy action only on a `build` event on the `master` branch.
|
||||
|
||||
### Examples
|
||||
|
||||
#### GitHub
|
||||
|
||||
The following example will trigger when a pull request with **master** as base is closed. The command `./prepull` and `./postpull` will execute immediately before and after the pull
|
||||
```json
|
||||
{
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"global_deploy": [
|
||||
"echo Pre-deploy script",
|
||||
"echo Post-deploy script"
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"branch": "master",
|
||||
"remote": "origin",
|
||||
"path": "~/repositories/Git-Auto-Deploy",
|
||||
"deploy": "echo deploying",
|
||||
"prepull": "chmod u+w config.json",
|
||||
"postpull": "chmod u-w config.json",
|
||||
"filters": [
|
||||
{
|
||||
"action": "closed",
|
||||
"pull_request": true,
|
||||
"pull_request.base.ref": "master"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### GitLab
|
||||
*(Note: the filter examples below are valid for GitLab)*
|
||||
|
||||
Execute pre-deploy script, don't `pull` the repository but execute a deploy
|
||||
script, and finish with a post-deploy script. Execute only for `push` events on
|
||||
the `master` branch.
|
||||
|
||||
```json
|
||||
{
|
||||
"pidfilepath": "~/.gitautodeploy.pid",
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"global_deploy": [
|
||||
"echo Pre-deploy script",
|
||||
"echo Post-deploy script"
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"url": "http://gitlab/playground/hooktest.git",
|
||||
"deploy": "echo deploying",
|
||||
"filters": [
|
||||
{
|
||||
"object_kind": "push",
|
||||
"ref": "refs/heads/master"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Clone repository on `push` to `master`.
|
||||
|
||||
```json
|
||||
{
|
||||
"pidfilepath": "~/.gitautodeploy.pid",
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"repositories": [
|
||||
{
|
||||
"url": "http://gitlab/playground/hooktest.git",
|
||||
"branch": "master",
|
||||
"remote": "origin",
|
||||
"path": "~/repositories/hooktest",
|
||||
"filters": [
|
||||
{
|
||||
"object_kind": "push",
|
||||
"ref": "refs/heads/master"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Execute script upon GitLab CI successful build of `master` branch.
|
||||
|
||||
```json
|
||||
{
|
||||
"pidfilepath": "~/.gitautodeploy.pid",
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"global_deploy": [
|
||||
"echo Pre-deploy script",
|
||||
"echo Post-deploy script"
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"url": "http://gitlab/playground/hooktest.git",
|
||||
"deploy": "echo deploying project!",
|
||||
"filters": [
|
||||
{
|
||||
"object_kind": "build",
|
||||
"ref": "master",
|
||||
"build_status": "success"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Bitbucket Server
|
||||
|
||||
Get source using SSH.
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"global_deploy": [
|
||||
"echo Pre-deploy script",
|
||||
"echo Post-deploy script"
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"url": "ssh://git@bitbucket.example.com/KEY/Git-Auto-Deploy.git",
|
||||
"match-url": "Git-Auto-Deploy",
|
||||
"branch": "master",
|
||||
"remote": "origin",
|
||||
"path": "~/repositories/Git-Auto-Deploy",
|
||||
"deploy": "echo deploying"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Using HTTPS.
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"global_deploy": [
|
||||
"echo Pre-deploy script",
|
||||
"echo Post-deploy script"
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"url": "https://bitbucket.example.com/scm/KEY/Git-Auto-Deploy.git",
|
||||
"match-url": "Git-Auto-Deploy",
|
||||
"branch": "master",
|
||||
"remote": "origin",
|
||||
"path": "~/repositories/Git-Auto-Deploy",
|
||||
"deploy": "echo deploying"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
# Repository configuration using environment variables
|
||||
|
||||
It's possible to configure up to one repository using environment variables. This can be useful in some specific use cases where a full config file is undesired.
|
||||
|
||||
Environment variable | Description
|
||||
-------------------- | --------------------------
|
||||
GAD_REPO_URL | Repository URL
|
||||
GAD_REPO_BRANCH |
|
||||
GAD_REPO_REMOTE |
|
||||
GAD_REPO_PATH | Path to where ```Git-Auto-Deploy``` should clone and pull repository
|
||||
GAD_REPO_DEPLOY | Deploy command
|
@ -0,0 +1,23 @@
|
||||
# Continuous Delivery via Pull requests (GitHub only)
|
||||
|
||||
![Workflow](./graphics/continuous_delivery_process.png)
|
||||
|
||||
If you use continious delivery (such as this workflow) you may want to trigger deploy event when pull request is opened or closed.
|
||||
You can follow next steps to implement CD process:
|
||||
* Set repo "url" to ```"https://api.github.com"```
|
||||
* Add filter type "pull-request-filter" as described below
|
||||
* Configure "action" that you want to listen
|
||||
* Configure branch in which pull request trying to merge (variable "ref" below)
|
||||
|
||||
Example
|
||||
```json
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"deploy": "echo deploying after pull request",
|
||||
"filters": [
|
||||
{
|
||||
"action": "closed",
|
||||
"pull_request": true,
|
||||
"pull_request.base.ref": "testing-branch"
|
||||
}
|
||||
]
|
||||
```
|
@ -0,0 +1,51 @@
|
||||
# Install as debian package from PPA (experimental)
|
||||
|
||||
Add our PPA
|
||||
|
||||
add-apt-repository ppa:olipo186/git-auto-deploy
|
||||
apt-get update
|
||||
|
||||
Install the package
|
||||
|
||||
apt-get install git-auto-deploy
|
||||
|
||||
Make your changes to the configuration file
|
||||
|
||||
nano /etc/git-auto-deploy.conf.json
|
||||
|
||||
Run the application
|
||||
|
||||
service git-auto-deploy start
|
||||
service git-auto-deploy status
|
||||
|
||||
# Install from .deb file (experimental)
|
||||
|
||||
Below is instructions on how to create a debian (.deb) package using stdeb. You can follow the instructions below to build the .deb package, or use the prepared script (platforms/debian/scripts/create-debian-package.sh) that will do the same. Once the package is created, you can install it using ```dpkg -i```. A sample configuration file as well as a init.d start up script will be installed as part of the package.
|
||||
|
||||
### Install dependencies
|
||||
|
||||
Install stdeb and other dependencies
|
||||
|
||||
apt-get install python-stdeb fakeroot python-all
|
||||
|
||||
### Download and build
|
||||
|
||||
git clone https://github.com/olipo186/Git-Auto-Deploy.git
|
||||
cd Git-Auto-Deploy
|
||||
make deb
|
||||
|
||||
### Install
|
||||
|
||||
When installing the package, a sample configuration file and a init.d start up script will be created.
|
||||
|
||||
dpkg -i dist/deb/git-auto-deploy-<version>.deb
|
||||
|
||||
### Configuration
|
||||
|
||||
nano /etc/git-auto-deploy.conf.json
|
||||
|
||||
### Running the application
|
||||
|
||||
service git-auto-deploy start
|
||||
service git-auto-deploy status
|
||||
|
@ -0,0 +1,41 @@
|
||||
# Install as a python module (experimental)
|
||||
|
||||
## Download and install
|
||||
|
||||
Install using [pip](http://www.pip-installer.org/en/latest/), a package manager for Python, by running the following command.
|
||||
|
||||
pip install git-auto-deploy
|
||||
|
||||
If you don't have pip installed, try installing it by running this from the command
|
||||
line:
|
||||
|
||||
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
|
||||
|
||||
Or, you can [download the source code
|
||||
(ZIP)](https://github.com/olipo186/Git-Auto-Deploy/zipball/master "Git-Auto-Deploy
|
||||
source code") for `Git-Auto-Deploy` and then run:
|
||||
|
||||
python setup.py install
|
||||
|
||||
You may need to run the above commands with `sudo`.
|
||||
|
||||
Once ```Git-Auto-Deploy``` has been installed as a python module, it can be started using the executable ```git-auto-deploy```. During installation with pip, the executable is usually installed in ```/usr/local/bin/git-auto-deploy```. This can vary depending on platform.
|
||||
|
||||
## Configuration
|
||||
|
||||
Copy the content of [config.json.sample](../config.json.sample) and save it anywhere you like, for example ```~/git-auto-deploy.conf.json```. Modify it to match your project setup. [Read more about the configuration options](../docs/Configuration.md).
|
||||
|
||||
## Running the application
|
||||
|
||||
Run the application using the executable ```git-auto-deploy``` which has been provided by pip. Provide the path to your configuration file as a command line argument.
|
||||
|
||||
git-auto-deploy --config ~/git-auto-deploy.conf.json
|
||||
|
||||
## Start automatically on boot using crontab
|
||||
|
||||
The easiest way to configure your system to automatically start ```Git-Auto-Deploy``` after a reboot is using crontab. Open crontab in edit mode using ```crontab -e``` and add the entry below.
|
||||
|
||||
When installing with pip, the executable ```git-auto-deploy``` is usually installed in ```/usr/local/bin/git-auto-deploy```. It is a good idea to verify the path to ```git-auto-deploy``` before adding the entry below.
|
||||
|
||||
@reboot /usr/local/bin/git-auto-deploy --daemon-mode --quiet --config /path/to/git-auto-deploy.conf.json
|
||||
|
@ -0,0 +1,14 @@
|
||||
# Git-Auto-Deploy documentation
|
||||
|
||||
# Documents
|
||||
|
||||
* [Configuration options](./Configuration.md)
|
||||
* [Install as a python module (experimental)](./Install as a python module.md)
|
||||
* [Install as a debian package (experimental)](./Install as a debian package.md)
|
||||
* [Start automatically on boot (init.d and systemd)](./Start automatically on boot.md)
|
||||
|
||||
# Example workflows
|
||||
|
||||
## Continuous Delivery via Pull requests (GitHub only)
|
||||
|
||||
It's possible to configure Git-Auto-Deploy to trigger when pull requests are opened or closed on GitHub. To read more about this workflow and how to configure Git-Aut-Deploy here: [Continuous Delivery via Pull requests](./Continuous Delivery via Pull requests.md)
|
@ -0,0 +1,70 @@
|
||||
# Start automatically on boot
|
||||
|
||||
```Git-Auto-Deploy``` can be automatically started at boot time using various techniques. Below you'll find a couple of suggested approaches with instructions.
|
||||
|
||||
The following instructions assumes that you are running ```Git-Auto-Deploy``` from a clone of this repository. In such a case, ```Git-Auto-Deploy``` is started by invoking ```python -m``` and referencing the ```gitautodeploy``` python module which is found in the cloned repository. Such a command can look like ```python -m /path/to/Git-Auto-Deploy/gitautodeploy --daemon-mode```.
|
||||
|
||||
If you have used any of the alternative installation methods (install with pip or as a debian package), you will instead start ```Git-Auto-Deploy``` using a installed executable. ```Git-Auto-Deploy``` would then be started using a command like ```git-auto-deploy --daemon-mode``` instead. If you have installed ```Git-Auto-Deploy``` in this way, you will need to modify the paths and commands used in the instructions below.
|
||||
|
||||
## Crontab
|
||||
The easiest way to configure your system to automatically start ```Git-Auto-Deploy``` after a reboot is using crontab. Open crontab in edit mode using ```crontab -e``` and add the following:
|
||||
|
||||
@reboot /usr/bin/python -m /path/to/Git-Auto-Deploy/gitautodeploy --daemon-mode --quiet
|
||||
|
||||
## Debian and Sys-V like init system.
|
||||
|
||||
Copy the sample init script into ```/etc/init.d/``` and make it executable.
|
||||
|
||||
cp platforms/linux/initfiles/debianLSBInitScripts/git-auto-deploy /etc/init.d/
|
||||
chmod 755 /etc/init.d/git-auto-deploy
|
||||
|
||||
**Important:** The init script assumes that you have ```Git-Auto-Deploy``` installed in ```/opt/Git-Auto-Deploy/``` and that the ```pidfilepath``` config option is set to ```/var/run/git-auto-deploy.pid```. If this is not the case, edit the ```git-auto-deploy``` init script and modify ```DAEMON```, ```PWD``` and ```PIDFILE```.
|
||||
|
||||
**Important:** The init script will run GAD as the ```root``` user by default, which is convenient but not secure. The recommended way to run GAD is to set up a separate user and modify the init script to run GAD as that user. When running GAD as a user other than root, you will need to make sure that the correct permissions are set on all directories and files that GAD requires access to (such as the path specified in the variable PIDFILE and LOGFIE in the init script).
|
||||
|
||||
Now you need to add the correct symbolic link to your specific runlevel dir to get the script executed on each start up. On Debian_Sys-V just do;
|
||||
|
||||
update-rc.d git-auto-deploy defaults
|
||||
|
||||
Fire it up and verify;
|
||||
|
||||
service git-auto-deploy start
|
||||
service git-auto-deploy status
|
||||
|
||||
## Systemd
|
||||
|
||||
Copy the sample systemd service file ```git-auto-deploy.service``` into ```/etc/systemd/system```;
|
||||
|
||||
cp platforms/linux/initfiles/systemd/git-auto-deploy.service /etc/systemd/system
|
||||
|
||||
Create the user and group specified in git-auto-deploy.service (```www-data```) if those do not exist already.
|
||||
|
||||
useradd -U www-data
|
||||
|
||||
This init script assumes that you have ```Git-Auto-Deploy``` installed in ```/opt/Git-Auto-Deploy/```. If this is not the case, edit the ```git-auto-deploy.service``` service file and modify ```ExecStart``` and ```WorkingDirectory```.
|
||||
|
||||
Now, reload daemons and fire ut up;
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start git-auto-deploy
|
||||
|
||||
Make is start automatically on system boot;
|
||||
|
||||
systemctl enable gitautodeploy
|
||||
|
||||
## CentOS 7
|
||||
|
||||
Have a look at [this script](../platforms/linux/initfiles/centos) which was kindly provided by https://github.com/olipo186/Git-Auto-Deploy/issues/192. Usage:
|
||||
|
||||
Start:
|
||||
|
||||
GitAutoDeploy_Service.sh start
|
||||
|
||||
Stop:
|
||||
|
||||
GitAutoDeploy_Service.sh stop
|
||||
|
||||
Stop:
|
||||
|
||||
GitAutoDeploy_Service.sh restart
|
||||
|
@ -0,0 +1,22 @@
|
||||
# How to add your SSH keys to Git-Auto-Deploy
|
||||
|
||||
1. Copy your ssh keys from your account to /etc/git-auto-deploy/.ssh/
|
||||
|
||||
```cp -R ~/.ssh /etc/git-auto-deploy/```
|
||||
|
||||
2. Add gitlab, github or your hostname to the known_hosts file, eg.
|
||||
|
||||
```ssh-keyscan -t rsa gitlab.com >> /etc/git-auto-deploy/.ssh/known_hosts```
|
||||
|
||||
3. Add read and write permissions to /etc/git-auto-deploy
|
||||
|
||||
```
|
||||
chown -R git-auto-deploy:git-auto-deploy /etc/git-auto-deploy
|
||||
chmod -R 700 /etc/git-auto-deploy/.ssh/*
|
||||
```
|
||||
|
||||
4. Add write permissions to your repository path
|
||||
|
||||
```chown -R git-auto-deploy:git-auto-deploy /home/myrepo```
|
||||
|
||||
Make sure you use your ssh url in your config.
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 48 KiB |
@ -0,0 +1,5 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .gitautodeploy import *
|
||||
from .cli import *
|
||||
from .parsers import *
|
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __package__ != 'gitautodeploy':
|
||||
import sys
|
||||
print("Critical - GAD must be started as a python module, for example using python -m gitautodeploy")
|
||||
sys.exit()
|
||||
|
||||
from gitautodeploy import main
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
main()
|
||||
|
||||
else:
|
||||
from gitautodeploy import main
|
@ -0,0 +1 @@
|
||||
from .config import *
|
@ -0,0 +1,503 @@
|
||||
class ConfigFileNotFoundException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigFileInvalidException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_config_defaults():
|
||||
"""Get the default configuration values."""
|
||||
|
||||
config = {}
|
||||
|
||||
# Supress console output
|
||||
config['quiet'] = False
|
||||
|
||||
# Run in daemon mode
|
||||
config['daemon-mode'] = False
|
||||
|
||||
# File containing additional config options
|
||||
config['config'] = None
|
||||
|
||||
# File to store a copy of the console output
|
||||
config['log-file'] = None
|
||||
|
||||
# File to store the process id (pid)
|
||||
config['pid-file'] = '~/.gitautodeploy.pid'
|
||||
|
||||
# HTTP server options
|
||||
config['http-enabled'] = True
|
||||
config['http-host'] = '0.0.0.0'
|
||||
config['http-port'] = 8001
|
||||
|
||||
# HTTPS server options
|
||||
config['https-enabled'] = True
|
||||
config['https-host'] = '0.0.0.0'
|
||||
config['https-port'] = 8002
|
||||
|
||||
# Web socket server options (used by web UI for real time updates)
|
||||
config['wss-enabled'] = False # Disabled by default until authentication is in place
|
||||
config['wss-host'] = '0.0.0.0'
|
||||
config['wss-port'] = 8003
|
||||
|
||||
# TLS/SSL cert (necessary for HTTPS and web socket server to work)
|
||||
config['ssl-key'] = None # If specified, holds the private key
|
||||
config['ssl-cert'] = '~/cert.pem' # Holds the public key or both the private and public keys
|
||||
|
||||
# Web user interface options
|
||||
config['web-ui-enabled'] = False # Disabled by default until authentication is in place
|
||||
config['web-ui-username'] = None
|
||||
config['web-ui-password'] = None
|
||||
config['web-ui-whitelist'] = ['127.0.0.1']
|
||||
config['web-ui-require-https'] = True
|
||||
config['web-ui-auth-enabled'] = True
|
||||
config['web-ui-prevent-root'] = True
|
||||
|
||||
# Record all log levels by default
|
||||
config['log-level'] = 'NOTSET'
|
||||
|
||||
# Other options
|
||||
config['intercept-stdout'] = True
|
||||
config['ssh-keyscan'] = False
|
||||
config['allow-root-user'] = False
|
||||
|
||||
# Log incoming webhook requests in a way they can be used as test cases
|
||||
config['log-test-case'] = False
|
||||
config['log-test-case-dir'] = None
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def rename_legacy_attribute_names(config):
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
rewrite_map = {
|
||||
'ssl': 'https-enabled',
|
||||
'ssl-pem-file': 'ssl-cert',
|
||||
'host': 'http-host',
|
||||
'port': 'http-port',
|
||||
'pidfilepath': 'pid-file',
|
||||
'logfilepath': 'log-file'
|
||||
}
|
||||
|
||||
for item in rewrite_map.items():
|
||||
old_name, new_name = item
|
||||
if old_name in config:
|
||||
config[new_name] = config[old_name]
|
||||
del config[old_name]
|
||||
print("Config option '%s' is deprecated. Please use '%s' instead." % (old_name, new_name))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_config_from_environment():
|
||||
"""Get configuration values provided as environment variables."""
|
||||
import os
|
||||
|
||||
config = {}
|
||||
|
||||
if 'GAD_QUIET' in os.environ:
|
||||
config['quiet'] = True
|
||||
|
||||
if 'GAD_DAEMON_MODE' in os.environ:
|
||||
config['daemon-mode'] = True
|
||||
|
||||
if 'GAD_CONFIG' in os.environ:
|
||||
config['config'] = os.environ['GAD_CONFIG']
|
||||
|
||||
if 'GAD_SSH_KEYSCAN' in os.environ:
|
||||
config['ssh-keyscan'] = True
|
||||
|
||||
if 'GAD_SSL_KEY' in os.environ:
|
||||
config['ssl-key'] = os.environ['GAD_SSL_KEY']
|
||||
|
||||
if 'GAD_SSL_CERT' in os.environ:
|
||||
config['ssl-cert'] = os.environ['GAD_SSL_CERT']
|
||||
|
||||
if 'GAD_PID_FILE' in os.environ:
|
||||
config['pid-file'] = os.environ['GAD_PID_FILE']
|
||||
|
||||
if 'GAD_LOG_FILE' in os.environ:
|
||||
config['log-file'] = os.environ['GAD_LOG_FILE']
|
||||
|
||||
if 'GAD_HOST' in os.environ:
|
||||
config['http-host'] = os.environ['GAD_HOST']
|
||||
|
||||
if 'GAD_HTTP_HOST' in os.environ:
|
||||
config['http-host'] = os.environ['GAD_HTTP_HOST']
|
||||
|
||||
if 'GAD_HTTPS_HOST' in os.environ:
|
||||
config['https-host'] = os.environ['GAD_HTTPS_HOST']
|
||||
|
||||
if 'GAD_PORT' in os.environ:
|
||||
config['http-port'] = int(os.environ['GAD_PORT'])
|
||||
|
||||
if 'GAD_HTTP_PORT' in os.environ:
|
||||
config['http-port'] = int(os.environ['GAD_HTTP_PORT'])
|
||||
|
||||
if 'GAD_HTTPS_PORT' in os.environ:
|
||||
config['https-port'] = int(os.environ['GAD_HTTPS_PORT'])
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_config_from_argv(argv):
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("-d", "--daemon-mode",
|
||||
help="run in background (daemon mode)",
|
||||
dest="daemon-mode",
|
||||
default=None,
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument("-q", "--quiet",
|
||||
help="supress console output",
|
||||
dest="quiet",
|
||||
default=None,
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument("-c", "--config",
|
||||
help="custom configuration file",
|
||||
dest="config",
|
||||
type=str)
|
||||
|
||||
parser.add_argument("--ssh-keyscan",
|
||||
help="scan repository hosts for ssh keys",
|
||||
dest="ssh-keyscan",
|
||||
default=None,
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument("--pid-file",
|
||||
help="specify a custom pid file",
|
||||
dest="pid-file",
|
||||
type=str)
|
||||
|
||||
parser.add_argument("--log-file",
|
||||
help="specify a log file",
|
||||
dest="log-file",
|
||||
type=str)
|
||||
|
||||
parser.add_argument("--log-level",
|
||||
help="specify log level",
|
||||
dest="log-level",
|
||||
type=str)
|
||||
|
||||
parser.add_argument("--host",
|
||||
help="address to bind http server to",
|
||||
dest="http-host",
|
||||
type=str)
|
||||
|
||||
#parser.add_argument("--http-host",
|
||||
# help="address to bind http server to",
|
||||
# dest="http-host",
|
||||
# type=str)
|
||||
|
||||
#parser.add_argument("--https-host",
|
||||
# help="address to bind https server to",
|
||||
# dest="https-host",
|
||||
# type=str)
|
||||
|
||||
parser.add_argument("--port",
|
||||
help="port to bind http server to",
|
||||
dest="http-port",
|
||||
type=int)
|
||||
|
||||
#parser.add_argument("--http-port",
|
||||
# help="port to bind http server to",
|
||||
# dest="http-port",
|
||||
# type=int)
|
||||
|
||||
#parser.add_argument("--https-port",
|
||||
# help="port to bind http server to",
|
||||
# dest="https-port",
|
||||
# type=int)
|
||||
|
||||
parser.add_argument("--ws-port",
|
||||
help="port to bind web socket server to",
|
||||
dest="web-ui-web-socket-port",
|
||||
type=int)
|
||||
|
||||
parser.add_argument("--ssl",
|
||||
help="enable https",
|
||||
dest="https-enabled",
|
||||
default=None,
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument("--ssl-key",
|
||||
help="path to ssl key file",
|
||||
dest="ssl-key",
|
||||
type=str)
|
||||
|
||||
parser.add_argument("--ssl-cert",
|
||||
help="path to ssl cert file",
|
||||
dest="ssl-cert",
|
||||
type=str)
|
||||
|
||||
parser.add_argument("--allow-root-user",
|
||||
help="allow running as root user",
|
||||
dest="allow-root-user",
|
||||
default=None,
|
||||
action="store_true")
|
||||
|
||||
|
||||
config = vars(parser.parse_args(argv))
|
||||
|
||||
# Delete entries for unprovided arguments
|
||||
del_keys = []
|
||||
for key in config:
|
||||
if config[key] is None:
|
||||
del_keys.append(key)
|
||||
|
||||
for key in del_keys:
|
||||
del config[key]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def find_config_file(target_directories=None):
|
||||
"""Attempt to find a path to a config file. Provided paths are scanned
|
||||
for *.conf(ig)?.json files."""
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
if not target_directories:
|
||||
return
|
||||
|
||||
# Remove duplicates
|
||||
target_directories = list(set(target_directories))
|
||||
|
||||
# Look for a *conf.json or *config.json
|
||||
for dir in target_directories:
|
||||
|
||||
if not os.access(dir, os.R_OK):
|
||||
continue
|
||||
|
||||
for item in os.listdir(dir):
|
||||
if re.match(r".*conf(ig)?\.json$", item):
|
||||
path = os.path.realpath(os.path.join(dir, item))
|
||||
logger.info("Using '%s' as config" % path)
|
||||
return path
|
||||
|
||||
|
||||
def get_config_from_file(path):
|
||||
"""Get configuration values from config file."""
|
||||
import logging
|
||||
import os
|
||||
logger = logging.getLogger()
|
||||
|
||||
config_file_path = os.path.realpath(path)
|
||||
logger.info('Using custom configuration file \'%s\'' % config_file_path)
|
||||
|
||||
# Read config data from json file
|
||||
if config_file_path:
|
||||
config_data = read_json_file(config_file_path)
|
||||
else:
|
||||
logger.info('No configuration file found or specified. Using default values.')
|
||||
config_data = {}
|
||||
|
||||
return config_data
|
||||
|
||||
|
||||
def read_json_file(file_path):
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import errno
|
||||
|
||||
try:
|
||||
json_string = open(file_path).read()
|
||||
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise ConfigFileNotFoundException(file_path)
|
||||
else:
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
try:
|
||||
# Remove commens from JSON (makes sample config options easier)
|
||||
regex = r'\s*(#|\/{2}).*$'
|
||||
regex_inline = r'(:?(?:\s)*([A-Za-z\d\.{}]*)|((?<=\").*\"),?)(?:\s)*(((#|(\/{2})).*)|)$'
|
||||
lines = json_string.split('\n')
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
if re.search(regex, line):
|
||||
if re.search(r'^' + regex, line, re.IGNORECASE):
|
||||
lines[index] = ""
|
||||
elif re.search(regex_inline, line):
|
||||
lines[index] = re.sub(regex_inline, r'\1', line)
|
||||
|
||||
data = json.loads('\n'.join(lines))
|
||||
|
||||
except ValueError as e:
|
||||
raise ConfigFileInvalidException(file_path)
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def init_config(config):
|
||||
"""Initialize config by filling out missing values etc."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from ..models import Project
|
||||
logger = logging.getLogger()
|
||||
|
||||
# Translate any ~ in the path into /home/<user>
|
||||
if 'pid-file' in config and config['pid-file']:
|
||||
config['pid-file'] = os.path.expanduser(config['pid-file'])
|
||||
|
||||
if 'log-file' in config and config['log-file']:
|
||||
config['log-file'] = os.path.expanduser(config['log-file'])
|
||||
|
||||
if 'ssl-cert' in config and config['ssl-cert']:
|
||||
config['ssl-cert'] = os.path.expanduser(config['ssl-cert'])
|
||||
|
||||
if 'ssl-key' in config and config['ssl-key']:
|
||||
config['ssl-key'] = os.path.expanduser(config['ssl-key'])
|
||||
|
||||
if 'repositories' not in config:
|
||||
config['repositories'] = []
|
||||
|
||||
deserialized = []
|
||||
|
||||
for repo_config in config['repositories']:
|
||||
|
||||
# Setup branch if missing
|
||||
if 'branch' not in repo_config:
|
||||
repo_config['branch'] = "master"
|
||||
|
||||
# Setup remote if missing
|
||||
if 'remote' not in repo_config:
|
||||
repo_config['remote'] = "origin"
|
||||
|
||||
# Setup deploy commands list if not present
|
||||
if 'deploy_commands' not in repo_config:
|
||||
repo_config['deploy_commands'] = []
|
||||
|
||||
# Check if any global pre deploy commands is specified
|
||||
if 'global_deploy' in config and len(config['global_deploy']) > 0 and len(config['global_deploy'][0]) is not 0:
|
||||
repo_config['deploy_commands'].insert(0, config['global_deploy'][0])
|
||||
|
||||
# Check if any repo specific deploy command is specified
|
||||
if 'deploy' in repo_config:
|
||||
repo_config['deploy_commands'].append(repo_config['deploy'])
|
||||
|
||||
# Check if any global post deploy command is specified
|
||||
if 'global_deploy' in config and len(config['global_deploy']) > 1 and len(config['global_deploy'][1]) is not 0:
|
||||
repo_config['deploy_commands'].append(config['global_deploy'][1])
|
||||
|
||||
# If a repository is configured with embedded credentials, we create an alternate URL
|
||||
# without these credentials that cen be used when comparing the URL with URLs referenced
|
||||
# in incoming web hook requests.
|
||||
if 'url' in repo_config:
|
||||
regexp = re.search(r"^(https?://)([^@]+)@(.+)$", repo_config['url'])
|
||||
if regexp:
|
||||
repo_config['url_without_usernme'] = regexp.group(1) + regexp.group(3)
|
||||
|
||||
# Translate any ~ in the path into /home/<user>
|
||||
if 'path' in repo_config:
|
||||
repo_config['path'] = os.path.expanduser(repo_config['path'])
|
||||
|
||||
# Support for legacy config format
|
||||
if 'filters' in repo_config:
|
||||
repo_config['payload-filter'] = repo_config['filters']
|
||||
del repo_config['filters']
|
||||
|
||||
if 'payload-filter' not in repo_config:
|
||||
repo_config['payload-filter'] = []
|
||||
|
||||
if 'header-filter' not in repo_config:
|
||||
repo_config['header-filter'] = {}
|
||||
|
||||
# Rewrite some legacy filter config syntax
|
||||
for filter in repo_config['payload-filter']:
|
||||
|
||||
# Legacy config syntax?
|
||||
if ('kind' in filter and filter['kind'] == 'pull-request-handler') or ('type' in filter and filter['type'] == 'pull-request-filter'):
|
||||
|
||||
# Reset legacy values
|
||||
filter['kind'] = None
|
||||
filter['type'] = None
|
||||
|
||||
if 'ref' in filter:
|
||||
filter['pull_request.base.ref'] = filter['ref']
|
||||
filter['ref'] = None
|
||||
|
||||
filter['pull_request'] = True
|
||||
|
||||
project = Project(repo_config)
|
||||
deserialized.append(project)
|
||||
|
||||
config['repositories'] = deserialized
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_repo_config_from_environment():
|
||||
"""Look for repository config in any defined environment variables. If
|
||||
found, import to main config."""
|
||||
import logging
|
||||
import os
|
||||
|
||||
if 'GAD_REPO_URL' not in os.environ:
|
||||
return
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
repo_config = {
|
||||
'url': os.environ['GAD_REPO_URL']
|
||||
}
|
||||
|
||||
logger.info("Added configuration for '%s' found in environment variables" % os.environ['GAD_REPO_URL'])
|
||||
|
||||
if 'GAD_REPO_BRANCH' in os.environ:
|
||||
repo_config['branch'] = os.environ['GAD_REPO_BRANCH']
|
||||
|
||||
if 'GAD_REPO_REMOTE' in os.environ:
|
||||
repo_config['remote'] = os.environ['GAD_REPO_REMOTE']
|
||||
|
||||
if 'GAD_REPO_PATH' in os.environ:
|
||||
repo_config['path'] = os.environ['GAD_REPO_PATH']
|
||||
|
||||
if 'GAD_REPO_DEPLOY' in os.environ:
|
||||
repo_config['deploy'] = os.environ['GAD_REPO_DEPLOY']
|
||||
|
||||
return repo_config
|
||||
|
||||
|
||||
def get_config_file_path(env_config, argv_config, search_target):
|
||||
import os
|
||||
|
||||
# Config file path provided in argument vector?
|
||||
if 'config' in argv_config and argv_config['config']:
|
||||
config_file_path = os.path.realpath(argv_config['config'])
|
||||
|
||||
# Config file path provided in environment variable?
|
||||
elif 'config' in env_config and env_config['config']:
|
||||
config_file_path = os.path.realpath(env_config['config'])
|
||||
|
||||
# Search file system
|
||||
else:
|
||||
|
||||
# Directories to scan for config files
|
||||
target_directories = [
|
||||
os.getcwd(), # cwd
|
||||
search_target # script path
|
||||
]
|
||||
|
||||
config_file_path = find_config_file(target_directories)
|
||||
|
||||
return config_file_path
|
@ -0,0 +1,64 @@
|
||||
{
|
||||
// HTTP server options
|
||||
//"http-enabled": true,
|
||||
//"http-host": "0.0.0.0",
|
||||
//"http-port": 8001,
|
||||
|
||||
// HTTPS server options
|
||||
//"https-enabled": true,
|
||||
//"https-host": "0.0.0.0",
|
||||
//"https-port": 8002,
|
||||
|
||||
// Web socket server options (used by web UI for real time updates)
|
||||
//"wss-enabled": false,
|
||||
//"wss-host": "0.0.0.0",
|
||||
//"wss-port": 8003,
|
||||
|
||||
// Web user interface options
|
||||
//"web-ui-enabled": false,
|
||||
//"web-ui-username": null,
|
||||
//"web-ui-password": null,
|
||||
//"web-ui-whitelist": ["127.0.0.1"],
|
||||
|
||||
// TLS/SSL cert (necessary for HTTPS and web socket server to work)
|
||||
//"ssl-key": null, // If specified, holds the private key
|
||||
"ssl-cert": "/etc/git-auto-deploy/cert.pem", // Holds the public key or both the private and public keys
|
||||
|
||||
// File to store a copy of the console output
|
||||
"log-file": "/var/log/git-auto-deploy.log",
|
||||
|
||||
// Record all log levels by default
|
||||
//"log-level": "NOTSET",
|
||||
|
||||
// Deploy commands that should be executed for all projects
|
||||
//"global_deploy": [
|
||||
// "echo Deploy started!",
|
||||
// "echo Deploy completed!"
|
||||
//],
|
||||
|
||||
// Project configs
|
||||
"repositories": [
|
||||
{
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"branch": "master",
|
||||
"remote": "origin",
|
||||
"path": "/var/lib/git-auto-deploy/Git-Auto-Deploy",
|
||||
"deploy": "echo deploying"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/github/gitignore",
|
||||
"path": "/var/lib/git-auto-deploy/gitignore"
|
||||
}
|
||||
// ,{
|
||||
// "url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
// "deploy": "echo deploying after pull request",
|
||||
// "filters": [
|
||||
// {
|
||||
// "type": "pull-request-filter",
|
||||
// "action": "closed",
|
||||
// "ref": "testing-branch"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
class SystemEvent(object):
|
||||
|
||||
def __init__(self, name=None):
|
||||
import logging
|
||||
|
||||
self.logger = logging.getLogger()
|
||||
self.hub = None
|
||||
self.messages = []
|
||||
self.name = name
|
||||
self.id = None
|
||||
self.waiting = None
|
||||
self.success = None
|
||||
|
||||
def __repr__(self):
|
||||
if self.id:
|
||||
return "<SystemEvent:%s>" % self.id
|
||||
else:
|
||||
return "<SystemEvent>"
|
||||
|
||||
def dict_repr(self):
|
||||
from time import time
|
||||
return {
|
||||
"id": self.id,
|
||||
"type": type(self).__name__,
|
||||
"timestamp": time(),
|
||||
"messages": self.messages,
|
||||
"waiting": self.waiting,
|
||||
"success": self.success
|
||||
}
|
||||
|
||||
def register_hub(self, hub):
|
||||
self.hub = hub
|
||||
|
||||
def register_message(self, message, level="INFO"):
|
||||
self.messages.append(message)
|
||||
self.hub.notify_observers(type="event-updated", event=self.dict_repr())
|
||||
|
||||
def set_id(self, id):
|
||||
self.id = id
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
def set_waiting(self, value):
|
||||
self.waiting = value
|
||||
self.hub.notify_observers(type="event-updated", event=self.dict_repr())
|
||||
|
||||
def set_success(self, value):
|
||||
self.success = value
|
||||
self.hub.notify_observers(type="event-updated", event=self.dict_repr())
|
||||
self.hub.notify_observers(type="event-success", id=self.id, success=value)
|
||||
|
||||
def log_debug(self, message):
|
||||
self.logger.debug(message)
|
||||
self.register_message(message, "DEBUG")
|
||||
|
||||
def log_info(self, message):
|
||||
self.logger.info(message)
|
||||
self.register_message(message, "INFO")
|
||||
|
||||
def log_warning(self, message):
|
||||
self.logger.warning(message)
|
||||
self.register_message(message, "WARNING")
|
||||
|
||||
def log_error(self, message):
|
||||
self.logger.error(message)
|
||||
self.register_message(message, "ERROR")
|
||||
|
||||
def log_critical(self, message):
|
||||
self.logger.critical(message)
|
||||
self.register_message(message, "CRITICAL")
|
||||
|
||||
#def update(self):
|
||||
# self.hub.notify_observers(type="event-updated", event=self.dict_repr())
|
||||
|
||||
|
||||
class WebhookAction(SystemEvent):
|
||||
"""Represents a webhook request event and keeps a copy of all incoming and outgoing data for monitoring purposes."""
|
||||
|
||||
def __init__(self, client_address, request_headers, request_body):
|
||||
self.client_address = client_address
|
||||
self.request_headers = request_headers
|
||||
self.request_body = request_body
|
||||
super(WebhookAction, self).__init__()
|
||||
|
||||
def __repr__(self):
|
||||
return "<WebhookAction>"
|
||||
|
||||
def dict_repr(self):
|
||||
data = super(WebhookAction, self).dict_repr()
|
||||
data['client-address'] = self.client_address[0]
|
||||
data['client-port'] = self.client_address[1]
|
||||
data['request-headers'] = self.request_headers
|
||||
data['request-body'] = self.request_body
|
||||
return data
|
||||
|
||||
|
||||
class DeployEvent(SystemEvent):
|
||||
|
||||
def __init__(self, project):
|
||||
self.project = project
|
||||
super(DeployEvent, self).__init__()
|
||||
|
||||
def __repr__(self):
|
||||
return "<WebhookAction>"
|
||||
|
||||
def dict_repr(self):
|
||||
data = super(DeployEvent, self).dict_repr()
|
||||
data['name'] = self.project.get_name()
|
||||
return data
|
||||
|
||||
|
||||
class StartupEvent(SystemEvent):
|
||||
|
||||
def __init__(self, http_address=None, http_port=None, ws_address=None, ws_port=None):
|
||||
self.http_address = http_address
|
||||
self.http_port = http_port
|
||||
self.http_started = None
|
||||
self.ws_address = ws_address
|
||||
self.ws_port = ws_port
|
||||
self.ws_started = None
|
||||
self.waiting = True
|
||||
super(StartupEvent, self).__init__()
|
||||
|
||||
def __repr__(self):
|
||||
return "<StartupEvent>"
|
||||
|
||||
def dict_repr(self):
|
||||
data = super(StartupEvent, self).dict_repr()
|
||||
data['http-address'] = self.http_address
|
||||
data['http-port'] = self.http_port
|
||||
data['http-started'] = self.http_started
|
||||
data['ws-address'] = self.ws_address
|
||||
data['ws-port'] = self.ws_port
|
||||
data['ws-started'] = self.ws_started
|
||||
return data
|
||||
|
||||
def set_http_started(self, value):
|
||||
self.http_started = value
|
||||
self.hub.notify_observers(type="event-updated", event=self.dict_repr())
|
||||
self.validate_success()
|
||||
|
||||
def set_ws_started(self, value):
|
||||
self.ws_started = value
|
||||
self.hub.notify_observers(type="event-updated", event=self.dict_repr())
|
||||
self.validate_success()
|
||||
|
||||
def validate_success(self):
|
||||
if self.http_started is not False and self.ws_started is not False:
|
||||
self.set_waiting(False)
|
||||
self.set_success(True)
|
||||
|
||||
|
||||
class EventStore(object):
|
||||
|
||||
def __init__(self):
|
||||
self.actions = []
|
||||
self.observers = []
|
||||
self.next_id = 0
|
||||
|
||||
def register_observer(self, observer):
|
||||
self.observers.append(observer)
|
||||
|
||||
def unregister_observer(self, observer):
|
||||
if observer in self.observers:
|
||||
self.observers.remove(observer)
|
||||
|
||||
def notify_observers(self, *args, **kwargs):
|
||||
for observer in self.observers:
|
||||
observer.update(*args, **kwargs)
|
||||
|
||||
def register_action(self, event):
|
||||
event.set_id(self.next_id)
|
||||
event.register_hub(self)
|
||||
self.next_id = self.next_id + 1
|
||||
self.actions.append(event)
|
||||
self.notify_observers(type="new-event", event=event.dict_repr())
|
||||
|
||||
# Store max 100 actions
|
||||
if len(self.actions) > 100:
|
||||
self.actions.pop(0)
|
||||
|
||||
def dict_repr(self):
|
||||
action_repr = []
|
||||
for action in self.actions:
|
||||
action_repr.append(action.dict_repr())
|
||||
return action_repr
|
@ -0,0 +1,679 @@
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
print("Critical - GAD must be started as a python module, for example using python -m gitautodeploy")
|
||||
sys.exit()
|
||||
|
||||
|
||||
class LogInterface(object):
|
||||
"""Interface that functions as a stdout and stderr handler and directs the
|
||||
output to the logging module, which in turn will output to either console,
|
||||
file or both."""
|
||||
|
||||
def __init__(self, level=None):
|
||||
import logging
|
||||
self.level = (level if level else logging.getLogger().info)
|
||||
|
||||
def write(self, msg):
|
||||
for line in msg.strip().split("\n"):
|
||||
self.level(line)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
from .wsserver import WebSocketClientHandlerFactory
|
||||
from .httpserver import WebhookRequestHandlerFactory
|
||||
|
||||
|
||||
class GitAutoDeploy(object):
|
||||
_instance = None
|
||||
_http_server = None
|
||||
_https_server = None
|
||||
_https_server_unwrapped_socket = None
|
||||
_config = {}
|
||||
_server_status = {}
|
||||
_pid = None
|
||||
_event_store = None
|
||||
_default_stdout = None
|
||||
_default_stderr = None
|
||||
_startup_event = None
|
||||
_ws_clients = []
|
||||
_http_port = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""Overload constructor to enable singleton access"""
|
||||
if not cls._instance:
|
||||
cls._instance = super(GitAutoDeploy, cls).__new__(
|
||||
cls, *args, **kwargs)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
from .events import EventStore, StartupEvent
|
||||
|
||||
# Setup an event store instance that can keep a global record of events
|
||||
self._event_store = EventStore()
|
||||
self._event_store.register_observer(self)
|
||||
|
||||
# Create a startup event that can hold status and any error messages
|
||||
# from the startup process
|
||||
self._startup_event = StartupEvent()
|
||||
self._event_store.register_action(self._startup_event)
|
||||
|
||||
def clone_all_repos(self):
|
||||
"""Iterates over all configured repositories and clones them to their
|
||||
configured paths."""
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
from .wrappers import GitWrapper
|
||||
logger = logging.getLogger()
|
||||
|
||||
if 'repositories' not in self._config:
|
||||
return
|
||||
|
||||
# Iterate over all configured repositories
|
||||
for repo_config in self._config['repositories']:
|
||||
|
||||
# Only clone repositories with a configured path
|
||||
if 'url' not in repo_config:
|
||||
logger.critical("Repository has no configured URL")
|
||||
self.exit()
|
||||
return
|
||||
|
||||
# Only clone repositories with a configured path
|
||||
if 'path' not in repo_config:
|
||||
logger.debug("Repository %s will not be cloned (no path configured)" % repo_config['url'])
|
||||
continue
|
||||
|
||||
if os.path.isdir(repo_config['path']) and os.path.isdir(repo_config['path']+'/.git'):
|
||||
GitWrapper.init(repo_config)
|
||||
else:
|
||||
GitWrapper.clone(repo_config)
|
||||
|
||||
def ssh_key_scan(self):
|
||||
import re
|
||||
import logging
|
||||
from .wrappers import ProcessWrapper
|
||||
logger = logging.getLogger()
|
||||
|
||||
for repository in self._config['repositories']:
|
||||
|
||||
if 'url' not in repository:
|
||||
continue
|
||||
|
||||
logger.info("Scanning repository: %s" % repository['url'])
|
||||
m = re.match('[^\@]+\@([^\:\/]+)(:(\d+))?', repository['url'])
|
||||
|
||||
if m is not None:
|
||||
host = m.group(1)
|
||||
port = m.group(3)
|
||||
port_arg = '' if port is None else ('-p %s ' % port)
|
||||
cmd = 'ssh-keyscan %s%s >> $HOME/.ssh/known_hosts' % (port_arg, host)
|
||||
ProcessWrapper().call([cmd], shell=True)
|
||||
|
||||
else:
|
||||
logger.error('Could not find regexp match in path: %s' % repository['url'])
|
||||
|
||||
def create_pid_file(self):
|
||||
import os
|
||||
|
||||
with open(self._config['pid-file'], 'w') as f:
|
||||
f.write(str(os.getpid()))
|
||||
|
||||
def read_pid_file(self):
|
||||
with open(self._config['pid-file'], 'r') as f:
|
||||
return f.readlines()
|
||||
|
||||
def remove_pid_file(self):
|
||||
import os
|
||||
import errno
|
||||
if 'pid-file' in self._config and self._config['pid-file']:
|
||||
try:
|
||||
os.remove(self._config['pid-file'])
|
||||
except OSError as e:
|
||||
# errno.ENOENT = no such file or directory
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def create_daemon():
|
||||
import os
|
||||
|
||||
try:
|
||||
# Spawn first child. Returns 0 in the child and pid in the parent.
|
||||
pid = os.fork()
|
||||
except OSError as e:
|
||||
raise Exception("%s [%d]" % (e.strerror, e.errno))
|
||||
|
||||
# First child
|
||||
if pid == 0:
|
||||
os.setsid()
|
||||
|
||||
try:
|
||||
# Spawn second child
|
||||
pid = os.fork()
|
||||
|
||||
except OSError as e:
|
||||
raise Exception("%s [%d]" % (e.strerror, e.errno))
|
||||
|
||||
if pid == 0:
|
||||
os.umask(0)
|
||||
else:
|
||||
# Kill first child
|
||||
os._exit(0)
|
||||
else:
|
||||
# Kill parent of first child
|
||||
os._exit(0)
|
||||
|
||||
return 0
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
import json
|
||||
data = json.dumps(kwargs).encode('utf-8')
|
||||
for client in self._ws_clients:
|
||||
client.sendMessage(data)
|
||||
|
||||
def get_log_formatter(self):
|
||||
import logging
|
||||
return logging.Formatter("%(asctime)s [%(levelname)-5.5s] %(message)s")
|
||||
|
||||
def setup_console_logger(self):
|
||||
import logging
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
consoleHandler = logging.StreamHandler()
|
||||
consoleHandler.setFormatter(self.get_log_formatter())
|
||||
|
||||
# Check if a stream handler is already present (will be if GAD is started by test script)
|
||||
handler_present = False
|
||||
for handler in logger.handlers:
|
||||
if isinstance(handler, type(consoleHandler)):
|
||||
handler_present = True
|
||||
break
|
||||
|
||||
if not handler_present:
|
||||
logger.addHandler(consoleHandler)
|
||||
|
||||
def setup(self, config):
|
||||
"""Setup an instance of GAD based on the provided config object."""
|
||||
import sys
|
||||
import socket
|
||||
import os
|
||||
import logging
|
||||
import base64
|
||||
from .lock import Lock
|
||||
import getpass
|
||||
|
||||
# This solves https://github.com/olipo186/Git-Auto-Deploy/issues/118
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
from logging import Handler
|
||||
|
||||
class NullHandler(Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
# Attatch config values to this instance
|
||||
self._config = config
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger()
|
||||
logFormatter = self.get_log_formatter()
|
||||
|
||||
# Enable console output?
|
||||
if ('quiet' in self._config and self._config['quiet']) or ('daemon-mode' in self._config and self._config['daemon-mode']):
|
||||
|
||||
# Add a default null handler that suppresses any console output
|
||||
logger.addHandler(NullHandler())
|
||||
|
||||
else:
|
||||
|
||||
# Set up console logger if not already present
|
||||
self.setup_console_logger()
|
||||
|
||||
# Set logging level
|
||||
if 'log-level' in self._config:
|
||||
level = logging.getLevelName(self._config['log-level'])
|
||||
logger.setLevel(level)
|
||||
|
||||
if 'log-file' in self._config and self._config['log-file']:
|
||||
# Translate any ~ in the path into /home/<user>
|
||||
fileHandler = logging.FileHandler(self._config['log-file'])
|
||||
fileHandler.setFormatter(logFormatter)
|
||||
logger.addHandler(fileHandler)
|
||||
|
||||
# Display a warning when trying to run as root
|
||||
if not self._config['allow-root-user'] and getpass.getuser() == 'root':
|
||||
logger.critical("Refusing to start as root. This application shouldn't run as a privileged used. Please run it as a different user. To disregard this warning and start anyway, set the config option \"allow-root-user\" to true, or use the command line argument --allow-root-user")
|
||||
sys.exit()
|
||||
|
||||
if 'ssh-keyscan' in self._config and self._config['ssh-keyscan']:
|
||||
self._startup_event.log_info('Scanning repository hosts for ssh keys...')
|
||||
self.ssh_key_scan()
|
||||
|
||||
# Clone all repos once initially
|
||||
self.clone_all_repos()
|
||||
|
||||
# Set default stdout and stderr to our logging interface (that writes
|
||||
# to file and console depending on user preference)
|
||||
if 'intercept-stdout' in self._config and self._config['intercept-stdout']:
|
||||
self._default_stdout = sys.stdout
|
||||
self._default_stderr = sys.stderr
|
||||
sys.stdout = LogInterface(logger.info)
|
||||
sys.stderr = LogInterface(logger.error)
|
||||
|
||||
if 'daemon-mode' in self._config and self._config['daemon-mode']:
|
||||
self._startup_event.log_info('Starting Git Auto Deploy in daemon mode')
|
||||
GitAutoDeploy.create_daemon()
|
||||
|
||||
self._pid = os.getpid()
|
||||
self.create_pid_file()
|
||||
|
||||
# Generate auth key to protect the web socket server
|
||||
self._server_status['auth-key'] = base64.b64encode(os.urandom(32))
|
||||
|
||||
# Clear any existing lock files, with no regard to possible ongoing processes
|
||||
for repo_config in self._config['repositories']:
|
||||
|
||||
# Do we have a physical repository?
|
||||
if 'path' in repo_config:
|
||||
Lock(os.path.join(repo_config['path'], 'status_running')).clear()
|
||||
Lock(os.path.join(repo_config['path'], 'status_waiting')).clear()
|
||||
|
||||
#if 'daemon-mode' not in self._config or not self._config['daemon-mode']:
|
||||
# self._startup_event.log_info('Git Auto Deploy started')
|
||||
|
||||
def serve_http(self, serve_forever=True):
|
||||
"""Starts a HTTP server that listens for webhook requests and serves the web ui."""
|
||||
import sys
|
||||
import socket
|
||||
import os
|
||||
from .events import SystemEvent
|
||||
|
||||
try:
|
||||
from BaseHTTPServer import HTTPServer
|
||||
except ImportError as e:
|
||||
from http.server import HTTPServer
|
||||
|
||||
if not self._config['http-enabled']:
|
||||
return
|
||||
|
||||
# Setup
|
||||
try:
|
||||
|
||||
# Create web hook request handler class
|
||||
WebhookRequestHandler = WebhookRequestHandlerFactory(self._config, self._event_store, self._server_status, is_https=False)
|
||||
|
||||
# Create HTTP server
|
||||
self._http_server = HTTPServer((self._config['http-host'],
|
||||
self._config['http-port']),
|
||||
WebhookRequestHandler)
|
||||
|
||||
# Setup SSL for HTTP server
|
||||
sa = self._http_server.socket.getsockname()
|
||||
self._http_port = sa[1]
|
||||
self._server_status['http-uri'] = "http://%s:%s" % (self._config['http-host'], sa[1])
|
||||
self._startup_event.log_info("Listening for connections on %s" % self._server_status['http-uri'])
|
||||
self._startup_event.http_address = sa[0]
|
||||
self._startup_event.http_port = sa[1]
|
||||
self._startup_event.set_http_started(True)
|
||||
|
||||
except socket.error as e:
|
||||
self._startup_event.log_critical("Unable to start HTTP server: %s" % e)
|
||||
return
|
||||
|
||||
if not serve_forever:
|
||||
return
|
||||
|
||||
# Run forever
|
||||
try:
|
||||
self._http_server.serve_forever()
|
||||
|
||||
except socket.error as e:
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
event.log_critical("Error on socket: %s" % e)
|
||||
sys.exit(1)
|
||||
|
||||
except KeyboardInterrupt as e:
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
event.log_info('Requested close by keyboard interrupt signal')
|
||||
self.stop()
|
||||
self.exit()
|
||||
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
event.log_info('HTTP server did quit')
|
||||
|
||||
def serve_https(self):
|
||||
"""Starts a HTTPS server that listens for webhook requests and serves the web ui."""
|
||||
import sys
|
||||
import socket
|
||||
import os
|
||||
import ssl
|
||||
from .events import SystemEvent
|
||||
|
||||
try:
|
||||
from BaseHTTPServer import HTTPServer
|
||||
except ImportError as e:
|
||||
from http.server import HTTPServer
|
||||
|
||||
if not self._config['https-enabled']:
|
||||
return
|
||||
|
||||
if not os.path.isfile(self._config['ssl-cert']):
|
||||
self._startup_event.log_critical("Unable to activate SSL: File does not exist: %s" % self._config['ssl-cert'])
|
||||
return
|
||||
|
||||
# Setup
|
||||
try:
|
||||
|
||||
# Create web hook request handler class
|
||||
WebhookRequestHandler = WebhookRequestHandlerFactory(self._config, self._event_store, self._server_status, is_https=True)
|
||||
|
||||
# Create HTTP server
|
||||
self._https_server = HTTPServer((self._config['https-host'],
|
||||
self._config['https-port']),
|
||||
WebhookRequestHandler)
|
||||
|
||||
# Setup SSL for HTTP server
|
||||
self._https_server_unwrapped_socket = self._https_server.socket
|
||||
self._https_server.socket = ssl.wrap_socket(self._https_server.socket,
|
||||
keyfile=self._config['ssl-key'],
|
||||
certfile=self._config['ssl-cert'],
|
||||
server_side=True)
|
||||
|
||||
sa = self._https_server.socket.getsockname()
|
||||
self._http_port = sa[1]
|
||||
self._server_status['https-uri'] = "https://%s:%s" % (self._config['https-host'], sa[1])
|
||||
|
||||
self._startup_event.log_info("Listening for connections on %s" % self._server_status['https-uri'])
|
||||
self._startup_event.http_address = sa[0]
|
||||
self._startup_event.http_port = sa[1]
|
||||
self._startup_event.set_http_started(True)
|
||||
|
||||
except socket.error as e:
|
||||
self._startup_event.log_critical("Unable to start HTTPS server: %s" % e)
|
||||
return
|
||||
|
||||
# Run forever
|
||||
try:
|
||||
self._https_server.serve_forever()
|
||||
|
||||
except socket.error as e:
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
event.log_critical("Error on socket: %s" % e)
|
||||
sys.exit(1)
|
||||
|
||||
except KeyboardInterrupt as e:
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
event.log_info('Requested close by keyboard interrupt signal')
|
||||
self.stop()
|
||||
self.exit()
|
||||
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
event.log_info('HTTPS server did quit')
|
||||
|
||||
def serve_wss(self):
|
||||
"""Start a web socket server over SSL, used by the web UI to get notifications about updates."""
|
||||
import os
|
||||
from .events import SystemEvent
|
||||
|
||||
# Start a web socket server if the web UI is enabled
|
||||
if not self._config['web-ui-enabled']:
|
||||
return
|
||||
|
||||
if not self._config['wss-enabled']:
|
||||
return
|
||||
|
||||
if not os.path.isfile(self._config['ssl-cert']):
|
||||
self._startup_event.log_critical("Unable to activate SSL: File does not exist: %s" % self._config['ssl-cert'])
|
||||
return
|
||||
|
||||
try:
|
||||
import os
|
||||
from autobahn.websocket import WebSocketServerProtocol, WebSocketServerFactory
|
||||
from twisted.internet import reactor, ssl
|
||||
from twisted.internet.error import BindError
|
||||
|
||||
# Create a WebSocketClientHandler instance
|
||||
WebSocketClientHandler = WebSocketClientHandlerFactory(self._config, self._ws_clients, self._event_store, self._server_status)
|
||||
|
||||
uri = u"ws://%s:%s" % (self._config['wss-host'], self._config['wss-port'])
|
||||
factory = WebSocketServerFactory(uri)
|
||||
factory.protocol = WebSocketClientHandler
|
||||
# factory.setProtocolOptions(maxConnections=2)
|
||||
|
||||
# note to self: if using putChild, the child must be bytes...
|
||||
if self._config['ssl-key'] and self._config['ssl-cert']:
|
||||
contextFactory = ssl.DefaultOpenSSLContextFactory(privateKeyFileName=self._config['ssl-key'], certificateFileName=self._config['ssl-cert'])
|
||||
else:
|
||||
contextFactory = ssl.DefaultOpenSSLContextFactory(privateKeyFileName=self._config['ssl-cert'], certificateFileName=self._config['ssl-cert'])
|
||||
|
||||
|
||||
self._ws_server_port = reactor.listenSSL(self._config['wss-port'], factory, contextFactory)
|
||||
# self._ws_server_port = reactor.listenTCP(self._config['wss-port'], factory)
|
||||
|
||||
self._server_status['wss-uri'] = "wss://%s:%s" % (self._config['wss-host'], self._config['wss-port'])
|
||||
|
||||
self._startup_event.log_info("Listening for connections on %s" % self._server_status['wss-uri'])
|
||||
self._startup_event.ws_address = self._config['wss-host']
|
||||
self._startup_event.ws_port = self._config['wss-port']
|
||||
self._startup_event.set_ws_started(True)
|
||||
|
||||
# Serve forever (until reactor.stop())
|
||||
reactor.run(installSignalHandlers=False)
|
||||
|
||||
except BindError as e:
|
||||
self._startup_event.log_critical("Unable to start web socket server: %s" % e)
|
||||
|
||||
except ImportError:
|
||||
self._startup_event.log_error("Unable to start web socket server due to missing dependency.")
|
||||
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
event.log_info('WSS server did quit')
|
||||
|
||||
def serve_forever(self):
|
||||
"""Start HTTP and web socket servers."""
|
||||
import sys
|
||||
import socket
|
||||
import logging
|
||||
import os
|
||||
from .events import SystemEvent
|
||||
import threading
|
||||
|
||||
try:
|
||||
from autobahn.websocket import WebSocketServerProtocol, WebSocketServerFactory
|
||||
from twisted.internet import reactor
|
||||
|
||||
# Given that the nessecary dependencies are present, notify the
|
||||
# event that we expect the web socket server to be started
|
||||
self._startup_event.ws_started = False
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Notify the event that we expect the http server to be started
|
||||
self._startup_event.http_started = False
|
||||
|
||||
# Add script dir to sys path, allowing us to import sub modules even after changing cwd
|
||||
sys.path.insert(1, os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
# Set CWD to public www folder. This makes the http server serve files from the wwwroot directory.
|
||||
wwwroot = os.path.join(os.path.dirname(os.path.realpath(__file__)), "wwwroot")
|
||||
os.chdir(wwwroot)
|
||||
|
||||
threads = [
|
||||
# HTTP server
|
||||
threading.Thread(target=self.serve_http),
|
||||
|
||||
# HTTPS server
|
||||
threading.Thread(target=self.serve_https),
|
||||
|
||||
# Web socket SSL server
|
||||
threading.Thread(target=self.serve_wss)
|
||||
]
|
||||
|
||||
# Start all threads
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
|
||||
# Wait for each thread to finish
|
||||
for thread in threads:
|
||||
|
||||
# Wait for thread to finish without blocking main thread
|
||||
while thread.is_alive():
|
||||
thread.join(5)
|
||||
|
||||
def signal_handler(self, signum, frame):
|
||||
from .events import SystemEvent
|
||||
self.stop()
|
||||
|
||||
event = SystemEvent()
|
||||
self._event_store.register_action(event)
|
||||
|
||||
# Reload configuration on SIGHUP events (conventional for daemon processes)
|
||||
if signum == 1:
|
||||
self.setup(self._config)
|
||||
self.serve_forever()
|
||||
return
|
||||
|
||||
# Keyboard interrupt signal
|
||||
elif signum == 2:
|
||||
event.log_info('Recieved keyboard interrupt signal (%s) from the OS, shutting down.' % signum)
|
||||
|
||||
else:
|
||||
event.log_info('Recieved signal (%s) from the OS, shutting down.' % signum)
|
||||
|
||||
self.exit()
|
||||
|
||||
def stop(self):
|
||||
"""Stop all running TCP servers (HTTP and web socket servers)"""
|
||||
|
||||
# Stop HTTP server if running
|
||||
if self._http_server is not None:
|
||||
|
||||
# Shut down the underlying TCP server
|
||||
self._http_server.shutdown()
|
||||
|
||||
# Close the socket
|
||||
self._http_server.socket.close()
|
||||
|
||||
# Stop HTTPS server if running
|
||||
if self._https_server is not None:
|
||||
|
||||
# Shut down the underlying TCP server
|
||||
self._https_server.shutdown()
|
||||
|
||||
# Close the socket
|
||||
self._https_server.socket.close()
|
||||
|
||||
if self._https_server_unwrapped_socket is not None:
|
||||
|
||||
self._https_server_unwrapped_socket.close()
|
||||
|
||||
# Stop web socket server if running
|
||||
try:
|
||||
from twisted.internet import reactor
|
||||
reactor.callFromThread(reactor.stop)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def exit(self):
|
||||
import sys
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
logger.info('Goodbye')
|
||||
|
||||
# Delete PID file
|
||||
self.remove_pid_file()
|
||||
|
||||
# Restore stdin and stdout
|
||||
if 'intercept-stdout' in self._config and self._config['intercept-stdout']:
|
||||
sys.stdout = self._default_stdout
|
||||
sys.stderr = self._default_stderr
|
||||
|
||||
|
||||
def main():
|
||||
import signal
|
||||
from gitautodeploy import GitAutoDeploy
|
||||
from cli.config import get_config_defaults, get_config_from_environment
|
||||
from cli.config import get_config_from_argv, find_config_file
|
||||
from cli.config import get_config_from_file, get_repo_config_from_environment
|
||||
from cli.config import init_config, get_config_file_path, rename_legacy_attribute_names
|
||||
from cli.config import ConfigFileNotFoundException, ConfigFileInvalidException
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
app = GitAutoDeploy()
|
||||
|
||||
if hasattr(signal, 'SIGHUP'):
|
||||
signal.signal(signal.SIGHUP, app.signal_handler)
|
||||
if hasattr(signal, 'SIGINT'):
|
||||
signal.signal(signal.SIGINT, app.signal_handler)
|
||||
if hasattr(signal, 'SIGABRT'):
|
||||
signal.signal(signal.SIGABRT, app.signal_handler)
|
||||
if hasattr(signal, 'SIGPIPE') and hasattr(signal, 'SIG_IGN'):
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
|
||||
|
||||
# Get default config values
|
||||
config = get_config_defaults()
|
||||
|
||||
# Get config values from environment variables and commadn line arguments
|
||||
environment_config = get_config_from_environment()
|
||||
argv_config = get_config_from_argv(sys.argv[1:])
|
||||
|
||||
# Merge config values from environment variables
|
||||
config.update(environment_config)
|
||||
|
||||
search_target = os.path.dirname(os.path.realpath(__file__))
|
||||
config_file_path = get_config_file_path(environment_config, argv_config, search_target)
|
||||
|
||||
# Config file path provided or found?
|
||||
if config_file_path:
|
||||
|
||||
try:
|
||||
file_config = get_config_from_file(config_file_path)
|
||||
except ConfigFileNotFoundException as e:
|
||||
app.setup_console_logger()
|
||||
logger.critical("No config file not found at '%s'" % e)
|
||||
return
|
||||
except ConfigFileInvalidException as e:
|
||||
app.setup_console_logger()
|
||||
logger.critical("Unable to read config file due to invalid JSON format in '%s'" % e)
|
||||
return
|
||||
|
||||
# Merge config values from config file (overrides environment variables)
|
||||
config.update(file_config)
|
||||
|
||||
# Merge config value from command line (overrides environment variables and config file)
|
||||
config.update(argv_config)
|
||||
|
||||
# Rename legacy config option names
|
||||
config = rename_legacy_attribute_names(config)
|
||||
|
||||
# Extend config data with any repository defined by environment variables
|
||||
repo_config = get_repo_config_from_environment()
|
||||
|
||||
if repo_config:
|
||||
|
||||
if not 'repositories' in config:
|
||||
config['repositories'] = []
|
||||
|
||||
config['repositories'].append(repo_config)
|
||||
|
||||
# Initialize config by expanding with missing values
|
||||
init_config(config)
|
||||
|
||||
app.setup(config)
|
||||
app.serve_forever()
|
@ -0,0 +1,346 @@
|
||||
from __future__ import absolute_import
|
||||
from .events import WebhookAction
|
||||
from .parsers import get_service_handler
|
||||
|
||||
|
||||
def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=False):
|
||||
"""Factory method for webhook request handler class"""
|
||||
try:
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
except ImportError as e:
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
class WebhookRequestHandler(SimpleHTTPRequestHandler, object):
|
||||
"""Extends the BaseHTTPRequestHandler class and handles the incoming
|
||||
HTTP requests."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._config = config
|
||||
self._event_store = event_store
|
||||
self._server_status = server_status
|
||||
self._is_https = is_https
|
||||
super(WebhookRequestHandler, self).__init__(*args, **kwargs)
|
||||
|
||||
def end_headers(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
SimpleHTTPRequestHandler.end_headers(self)
|
||||
|
||||
def do_HEAD(self):
|
||||
|
||||
# Web UI needs to be enabled
|
||||
if not self.validate_web_ui_enabled():
|
||||
return
|
||||
|
||||
# Web UI might require HTTPS
|
||||
if not self.validate_web_ui_https():
|
||||
return
|
||||
|
||||
# Client needs to be whitelisted
|
||||
if not self.validate_web_ui_whitelist():
|
||||
return
|
||||
|
||||
# Client needs to authenticate
|
||||
if not self.validate_web_ui_basic_auth():
|
||||
return
|
||||
|
||||
return SimpleHTTPRequestHandler.do_HEAD(self)
|
||||
|
||||
def do_GET(self):
|
||||
|
||||
# Web UI needs to be enabled
|
||||
if not self.validate_web_ui_enabled():
|
||||
return
|
||||
|
||||
# Web UI might require HTTPS
|
||||
if not self.validate_web_ui_https():
|
||||
return
|
||||
|
||||
# Client needs to be whitelisted
|
||||
if not self.validate_web_ui_whitelist():
|
||||
return
|
||||
|
||||
# Client needs to authenticate
|
||||
if not self.validate_web_ui_basic_auth():
|
||||
return
|
||||
|
||||
# Handle status API call
|
||||
if self.path == "/api/status":
|
||||
self.handle_status_api()
|
||||
return
|
||||
|
||||
# Serve static file
|
||||
return SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
def handle_status_api(self):
|
||||
import json
|
||||
from os import urandom
|
||||
from base64 import b64encode
|
||||
|
||||
data = {
|
||||
'events': self._event_store.dict_repr(),
|
||||
'auth-key': self._server_status['auth-key']
|
||||
}
|
||||
|
||||
data.update(self.get_server_status())
|
||||
|
||||
self.send_response(200, 'OK')
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(data).encode('utf-8'))
|
||||
|
||||
def do_POST(self):
|
||||
"""Invoked on incoming POST requests"""
|
||||
from threading import Timer
|
||||
import logging
|
||||
import json
|
||||
import threading
|
||||
from urlparse import parse_qs
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
content_length = int(self.headers.get('content-length'))
|
||||
request_body = self.rfile.read(content_length).decode('utf-8')
|
||||
|
||||
# Extract request headers and make all keys to lowercase (makes them easier to compare)
|
||||
request_headers = dict(self.headers)
|
||||
request_headers = dict((k.lower(), v) for k, v in request_headers.items())
|
||||
|
||||
action = WebhookAction(self.client_address, request_headers, request_body)
|
||||
self._event_store.register_action(action)
|
||||
action.set_waiting(True)
|
||||
|
||||
action.log_info('Incoming request from %s:%s' % (self.client_address[0], self.client_address[1]))
|
||||
|
||||
# Payloads from GitHub can be delivered as form data. Test the request for this pattern and extract json payload
|
||||
if request_headers['content-type'] == 'application/x-www-form-urlencoded':
|
||||
res = parse_qs(request_body.decode('utf-8'))
|
||||
if 'payload' in res and len(res['payload']) == 1:
|
||||
request_body = res['payload'][0]
|
||||
|
||||
# Test case debug data
|
||||
test_case = {
|
||||
'headers': dict(self.headers),
|
||||
'payload': json.loads(request_body),
|
||||
'config': {},
|
||||
'expected': {'status': 200, 'data': [{'deploy': 0}]}
|
||||
}
|
||||
|
||||
try:
|
||||
|
||||
# Will raise a ValueError exception if it fails
|
||||
ServiceRequestHandler = get_service_handler(request_headers, request_body, action)
|
||||
|
||||
# Unable to identify the source of the request
|
||||
if not ServiceRequestHandler:
|
||||
self.send_error(400, 'Unrecognized service')
|
||||
test_case['expected']['status'] = 400
|
||||
action.log_error("Unable to find appropriate handler for request. The source service is not supported")
|
||||
action.set_waiting(False)
|
||||
action.set_success(False)
|
||||
return
|
||||
|
||||
service_handler = ServiceRequestHandler(self._config)
|
||||
|
||||
action.log_info("Handling the request with %s" % ServiceRequestHandler.__name__)
|
||||
|
||||
# Could be GitHubParser, GitLabParser or other
|
||||
projects = service_handler.get_matching_projects(request_headers, request_body, action)
|
||||
|
||||
action.log_info("%s candidates matches the request" % len(projects))
|
||||
|
||||
# request_filter = WebhookRequestFilter()
|
||||
|
||||
if len(projects) == 0:
|
||||
self.send_error(400, 'Bad request')
|
||||
test_case['expected']['status'] = 400
|
||||
action.log_error("No matching projects")
|
||||
action.set_waiting(False)
|
||||
action.set_success(False)
|
||||
return
|
||||
|
||||
# Apply filters
|
||||
matching_projects = []
|
||||
for project in projects:
|
||||
if project.apply_filters(request_headers, request_body, action):
|
||||
matching_projects.append(project)
|
||||
|
||||
# Only keep projects that matches
|
||||
projects = matching_projects
|
||||
|
||||
action.log_info("%s candidates matches after applying filters" % len(projects))
|
||||
|
||||
if not service_handler.validate_request(request_headers, request_body, projects, action):
|
||||
self.send_error(400, 'Bad request')
|
||||
test_case['expected']['status'] = 400
|
||||
action.log_warning("Request was rejected due to a secret token mismatch")
|
||||
action.set_waiting(False)
|
||||
action.set_success(False)
|
||||
return
|
||||
|
||||
test_case['expected']['status'] = 200
|
||||
|
||||
self.send_response(200, 'OK')
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
|
||||
if len(projects) == 0:
|
||||
action.set_waiting(False)
|
||||
action.set_success(False)
|
||||
return
|
||||
|
||||
action.log_info("Proceeding with %s candidates" % len(projects))
|
||||
action.set_waiting(False)
|
||||
action.set_success(True)
|
||||
|
||||
for project in projects:
|
||||
|
||||
# Schedule the execution of the webhook (git pull and trigger deploy etc)
|
||||
thread = threading.Thread(target=project.execute_webhook, args=[self._event_store])
|
||||
thread.start()
|
||||
|
||||
# Add additional test case data
|
||||
test_case['config'] = {
|
||||
'url': 'url' in project and project['url'],
|
||||
'branch': 'branch' in project and project['branch'],
|
||||
'remote': 'remote' in project and project['remote'],
|
||||
'deploy': 'echo test!'
|
||||
}
|
||||
|
||||
except ValueError as e:
|
||||
self.send_error(400, 'Unprocessable request')
|
||||
action.log_warning('Unable to process incoming request from %s:%s' % (self.client_address[0], self.client_address[1]))
|
||||
test_case['expected']['status'] = 400
|
||||
action.set_waiting(False)
|
||||
action.set_success(False)
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
self.send_error(500, 'Unable to process request')
|
||||
test_case['expected']['status'] = 500
|
||||
action.log_warning("Unable to process request")
|
||||
action.set_waiting(False)
|
||||
action.set_success(False)
|
||||
|
||||
raise e
|
||||
|
||||
finally:
|
||||
|
||||
# Save the request as a test case
|
||||
if 'log-test-case' in self._config and self._config['log-test-case']:
|
||||
self.save_test_case(test_case)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Overloads the default message logging method to allow messages to
|
||||
go through our custom logger instead."""
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
logger.info("%s - %s" % (self.client_address[0], format%args))
|
||||
|
||||
def save_test_case(self, test_case):
|
||||
"""Log request information in a way it can be used as a test case."""
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
|
||||
# Mask some header values
|
||||
masked_headers = ['x-github-delivery', 'x-hub-signature']
|
||||
for key in test_case['headers']:
|
||||
if key in masked_headers:
|
||||
test_case['headers'][key] = 'xxx'
|
||||
|
||||
target = '%s-%s.tc.json' % (self.client_address[0], time.strftime("%Y%m%d%H%M%S"))
|
||||
if 'log-test-case-dir' in self._config and self._config['log-test-case-dir']:
|
||||
target = os.path.join(self._config['log-test-case-dir'], target)
|
||||
|
||||
file = open(target, 'w')
|
||||
file.write(json.dumps(test_case, sort_keys=True, indent=4))
|
||||
file.close()
|
||||
|
||||
def get_server_status(self):
|
||||
"""Generate a copy of the server status object that contains the public IP or hostname."""
|
||||
|
||||
server_status = {}
|
||||
for item in self._server_status.items():
|
||||
key, value = item
|
||||
public_host = self.headers.get('host').split(':')[0]
|
||||
|
||||
if key == 'http-uri':
|
||||
server_status[key] = value.replace(self._config['http-host'], public_host)
|
||||
|
||||
if key == 'https-uri':
|
||||
server_status[key] = value.replace(self._config['https-host'], public_host)
|
||||
|
||||
if key == 'wss-uri':
|
||||
server_status[key] = value.replace(self._config['wss-host'], public_host)
|
||||
|
||||
return server_status
|
||||
|
||||
def validate_web_ui_enabled(self):
|
||||
"""Verify that the Web UI is enabled"""
|
||||
|
||||
if self._config['web-ui-enabled']:
|
||||
return True
|
||||
|
||||
self.send_error(403, "Web UI is not enabled")
|
||||
return False
|
||||
|
||||
def validate_web_ui_https(self):
|
||||
"""Verify that the request is made over HTTPS"""
|
||||
|
||||
if self._is_https:
|
||||
return True
|
||||
|
||||
if not self._config['web-ui-require-https']:
|
||||
return True
|
||||
|
||||
# Attempt to redirect the request to HTTPS
|
||||
server_status = self.get_server_status()
|
||||
if 'https-uri' in server_status:
|
||||
self.send_response(307)
|
||||
self.send_header('Location', '%s%s' % (server_status['https-uri'], self.path))
|
||||
self.end_headers()
|
||||
return False
|
||||
|
||||
self.send_error(403, "Web UI is only accessible through HTTPS")
|
||||
return False
|
||||
|
||||
def validate_web_ui_whitelist(self):
|
||||
"""Verify that the client address is whitelisted"""
|
||||
|
||||
# Allow all if whitelist is empty
|
||||
if len(self._config['web-ui-whitelist']) == 0:
|
||||
return True
|
||||
|
||||
# Verify that client IP is whitelisted
|
||||
if self.client_address[0] in self._config['web-ui-whitelist']:
|
||||
return True
|
||||
|
||||
self.send_error(403, "%s is not allowed access" % self.client_address[0])
|
||||
return False
|
||||
|
||||
def validate_web_ui_basic_auth(self):
|
||||
"""Authenticate the user"""
|
||||
import base64
|
||||
|
||||
if not self._config['web-ui-auth-enabled']:
|
||||
return True
|
||||
|
||||
# Verify that a username and password is specified in the config
|
||||
if self._config['web-ui-username'] is None or self._config['web-ui-password'] is None:
|
||||
self.send_error(403, "Authentication credentials missing in config")
|
||||
return False
|
||||
|
||||
# Verify that the provided username and password matches the ones in the config
|
||||
key = base64.b64encode("%s:%s" % (self._config['web-ui-username'], self._config['web-ui-password']))
|
||||
if self.headers.getheader('Authorization') == 'Basic ' + key:
|
||||
return True
|
||||
|
||||
# Let the client know that authentication is required
|
||||
self.send_response(401)
|
||||
self.send_header('WWW-Authenticate', 'Basic realm=\"GAD\"')
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write('Not authenticated')
|
||||
return False
|
||||
|
||||
return WebhookRequestHandler
|
@ -0,0 +1,54 @@
|
||||
|
||||
class Lock():
|
||||
"""Simple implementation of a mutex lock using the file systems. Works on
|
||||
*nix systems."""
|
||||
|
||||
path = None
|
||||
lock = None
|
||||
|
||||
def __init__(self, path):
|
||||
try:
|
||||
from lockfile import LockFile
|
||||
except ImportError:
|
||||
from lockfile import FileLock
|
||||
# Different naming in older versions of lockfile
|
||||
LockFile = FileLock
|
||||
|
||||
self.path = path
|
||||
self.lock = LockFile(path)
|
||||
|
||||
def obtain(self):
|
||||
import os
|
||||
import logging
|
||||
from lockfile import AlreadyLocked
|
||||
logger = logging.getLogger()
|
||||
|
||||
try:
|
||||
self.lock.acquire(0)
|
||||
logger.debug("Successfully obtained lock: %s" % self.path)
|
||||
except AlreadyLocked:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def release(self):
|
||||
import os
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
if not self.has_lock():
|
||||
raise Exception("Unable to release lock that is owned by another process")
|
||||
|
||||
self.lock.release()
|
||||
logger.debug("Successfully released lock: %s" % self.path)
|
||||
|
||||
def has_lock(self):
|
||||
return self.lock.i_am_locking()
|
||||
|
||||
def clear(self):
|
||||
import os
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
self.lock.break_lock()
|
||||
logger.debug("Successfully cleared lock: %s" % self.path)
|
@ -0,0 +1 @@
|
||||
from .project import *
|
@ -0,0 +1,210 @@
|
||||
import collections
|
||||
from ..wrappers import GitWrapper
|
||||
from ..lock import Lock
|
||||
from ..wrappers import GitWrapper
|
||||
from ..events import DeployEvent
|
||||
|
||||
|
||||
class Project(collections.MutableMapping):
|
||||
|
||||
"""A dictionary that applies an arbitrary key-altering
|
||||
function before accessing the keys"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.store = dict()
|
||||
self.update(dict(*args, **kwargs)) # use the free update to set keys
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.store[self.__keytransform__(key)]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.store[self.__keytransform__(key)] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.store[self.__keytransform__(key)]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.store)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.store)
|
||||
|
||||
def __keytransform__(self, key):
|
||||
return key
|
||||
|
||||
def get_name(self):
|
||||
return self['url'].split('/')[-1].split('.git')[0]
|
||||
|
||||
def passes_payload_filter(self, payload, action):
|
||||
|
||||
# At least one filter must match
|
||||
for filter in self['payload-filter']:
|
||||
|
||||
# All options specified in the filter must match
|
||||
for filter_key, filter_value in filter.items():
|
||||
|
||||
# Ignore filters with value None (let them pass)
|
||||
if filter_value == None:
|
||||
continue
|
||||
|
||||
# Interpret dots in filter name as path notations
|
||||
node_value = payload
|
||||
for node_key in filter_key.split('.'):
|
||||
|
||||
# If the path is not valid the filter does not match
|
||||
if not node_key in node_value:
|
||||
action.log_info("Filter '%s' does not match since the path is invalid" % (filter_key))
|
||||
|
||||
# Filter does not match, do not process this repo config
|
||||
return False
|
||||
|
||||
node_value = node_value[node_key]
|
||||
|
||||
if filter_value == node_value:
|
||||
continue
|
||||
|
||||
# If the filter value is set to True. the filter
|
||||
# will pass regardless of the actual value
|
||||
if filter_value == True:
|
||||
continue
|
||||
|
||||
action.log_debug("Filter '%s' does not match ('%s' != '%s')" % (filter_key, filter_value, (str(node_value)[:75] + '..') if len(str(node_value)) > 75 else str(node_value)))
|
||||
|
||||
# Filter does not match, do not process this repo config
|
||||
return False
|
||||
|
||||
# Filter does match, proceed
|
||||
return True
|
||||
|
||||
def passes_header_filter(self, request_headers):
|
||||
|
||||
# At least one filter must match
|
||||
for key in self['header-filter']:
|
||||
|
||||
# Verify that the request has the required header attribute
|
||||
if key.lower() not in request_headers:
|
||||
return False
|
||||
|
||||
# "True" indicates that any header value is accepted
|
||||
if self['header-filter'][key] is True:
|
||||
continue
|
||||
|
||||
# Verify that the request has the required header value
|
||||
if self['header-filter'][key] != request_headers[key.lower()]:
|
||||
return False
|
||||
|
||||
# Filter does match, proceed
|
||||
return True
|
||||
|
||||
def apply_filters(self, request_headers, request_body, action):
|
||||
"""Verify that the suggested repositories has matching settings and
|
||||
issue git pull and/or deploy commands."""
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
payload = json.loads(request_body)
|
||||
|
||||
# Verify that all payload filters matches the request (if any payload filters are specified)
|
||||
if 'payload-filter' in self and not self.passes_payload_filter(payload, action):
|
||||
|
||||
# Filter does not match, do not process this repo config
|
||||
return False
|
||||
|
||||
# Verify that all header filters matches the request (if any header filters are specified)
|
||||
if 'header-filter' in self and not self.passes_header_filter(request_headers):
|
||||
|
||||
# Filter does not match, do not process this repo config
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def execute_webhook(self, event_store):
|
||||
"""Verify that the suggested repositories has matching settings and
|
||||
issue git pull and/or deploy commands."""
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
event = DeployEvent(self)
|
||||
event_store.register_action(event)
|
||||
event.set_waiting(True)
|
||||
event.log_info("Running deploy commands")
|
||||
|
||||
# In case there is no path configured for the repository, no pull will
|
||||
# be made.
|
||||
if 'path' not in self:
|
||||
res = GitWrapper.deploy(self)
|
||||
event.log_info("%s" % res)
|
||||
event.set_waiting(False)
|
||||
event.set_success(True)
|
||||
return
|
||||
|
||||
# If the path does not exist, a warning will be raised and no pull or
|
||||
# deploy will be made.
|
||||
if not os.path.isdir(self['path']):
|
||||
event.log_error("The repository '%s' does not exist locally. Make sure it was pulled properly without errors by reviewing the log." % self['path'])
|
||||
event.set_waiting(False)
|
||||
event.set_success(False)
|
||||
return
|
||||
|
||||
# If the path is not writable, a warning will be raised and no pull or
|
||||
# deploy will be made.
|
||||
if not os.access(self['path'], os.W_OK):
|
||||
event.log_error("The path '%s' is not writable. Make sure that GAD has write access to that path." % self['path'])
|
||||
event.set_waiting(False)
|
||||
event.set_success(False)
|
||||
return
|
||||
|
||||
running_lock = Lock(os.path.join(self['path'], 'status_running'))
|
||||
waiting_lock = Lock(os.path.join(self['path'], 'status_waiting'))
|
||||
try:
|
||||
|
||||
# Attempt to obtain the status_running lock
|
||||
while not running_lock.obtain():
|
||||
|
||||
# If we're unable, try once to obtain the status_waiting lock
|
||||
if not waiting_lock.has_lock() and not waiting_lock.obtain():
|
||||
event.log_error("Unable to obtain the status_running lock nor the status_waiting lock. Another process is already waiting, so we'll ignore the request.")
|
||||
|
||||
# If we're unable to obtain the waiting lock, ignore the request
|
||||
break
|
||||
|
||||
# Keep on attempting to obtain the status_running lock until we succeed
|
||||
time.sleep(5)
|
||||
|
||||
n = 4
|
||||
res = None
|
||||
while n > 0:
|
||||
|
||||
# Attempt to pull up a maximum of 4 times
|
||||
res = GitWrapper.pull(self)
|
||||
|
||||
# Return code indicating success?
|
||||
if res == 0:
|
||||
break
|
||||
|
||||
n -= 1
|
||||
|
||||
if 0 < n:
|
||||
res = GitWrapper.deploy(self)
|
||||
|
||||
#except Exception as e:
|
||||
# logger.error('Error during \'pull\' or \'deploy\' operation on path: %s' % self['path'])
|
||||
# logger.error(e)
|
||||
# raise e
|
||||
|
||||
finally:
|
||||
|
||||
# Release the lock if it's ours
|
||||
if running_lock.has_lock():
|
||||
running_lock.release()
|
||||
|
||||
# Release the lock if it's ours
|
||||
if waiting_lock.has_lock():
|
||||
waiting_lock.release()
|
||||
|
||||
event.log_info("Deploy commands were executed")
|
||||
event.set_waiting(False)
|
||||
event.set_success(True)
|
||||
|
@ -0,0 +1,53 @@
|
||||
from .bitbucket import BitBucketRequestParser
|
||||
from .github import GitHubRequestParser
|
||||
from .gitlab import GitLabRequestParser
|
||||
from .gitlabci import GitLabCIRequestParser
|
||||
from .generic import GenericRequestParser
|
||||
from .coding import CodingRequestParser
|
||||
|
||||
|
||||
def get_service_handler(request_headers, request_body, action):
|
||||
"""Parses the incoming request and attempts to determine whether
|
||||
it originates from GitHub, GitLab or any other known service."""
|
||||
import json
|
||||
|
||||
payload = json.loads(request_body)
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError("Invalid JSON object")
|
||||
|
||||
user_agent = 'user-agent' in request_headers and request_headers['user-agent']
|
||||
content_type = 'content-type' in request_headers and request_headers['content-type']
|
||||
|
||||
# Assume Coding if the X-Coding-Event HTTP header is set
|
||||
if 'x-coding-event' in request_headers:
|
||||
return CodingRequestParser
|
||||
|
||||
# Assume GitLab if the X-Gitlab-Event HTTP header is set
|
||||
elif 'x-gitlab-event' in request_headers:
|
||||
|
||||
# Special Case for Gitlab CI
|
||||
if content_type == "application/json" and "build_status" in payload:
|
||||
return GitLabCIRequestParser
|
||||
else:
|
||||
return GitLabRequestParser
|
||||
|
||||
# Assume GitHub if the X-GitHub-Event HTTP header is set
|
||||
elif 'x-github-event' in request_headers:
|
||||
|
||||
return GitHubRequestParser
|
||||
|
||||
# Assume BitBucket if the User-Agent HTTP header is set to
|
||||
# 'Bitbucket-Webhooks/2.0' (or something similar)
|
||||
elif user_agent and user_agent.lower().find('bitbucket') != -1:
|
||||
|
||||
return BitBucketRequestParser
|
||||
|
||||
# This handles old GitLab requests and Gogs requests for example.
|
||||
elif content_type == "application/json":
|
||||
|
||||
action.log_info("Received event from unknown origin.")
|
||||
return GenericRequestParser
|
||||
|
||||
action.log_error("Unable to recognize request origin. Don't know how to handle the request.")
|
||||
return
|
@ -0,0 +1,29 @@
|
||||
class WebhookRequestParserBase(object):
|
||||
"""Abstract parent class for git service parsers. Contains helper
|
||||
methods."""
|
||||
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
|
||||
def get_matching_repo_configs(self, urls, action):
|
||||
"""Iterates over the various repo URLs provided as argument (git://,
|
||||
ssh:// and https:// for the repo) and compare them to any repo URL
|
||||
specified in the config"""
|
||||
|
||||
configs = []
|
||||
for url in urls:
|
||||
for repo_config in self._config['repositories']:
|
||||
if repo_config in configs:
|
||||
continue
|
||||
if repo_config.get('match-url', repo_config.get('url')) == url:
|
||||
configs.append(repo_config)
|
||||
elif 'url_without_usernme' in repo_config and repo_config['url_without_usernme'] == url:
|
||||
configs.append(repo_config)
|
||||
|
||||
if len(configs) == 0:
|
||||
action.log_warning('The URLs references in the webhook did not match any repository entry in the config. For this webhook to work, make sure you have at least one repository configured with one of the following URLs; %s' % ', '.join(urls))
|
||||
|
||||
return configs
|
||||
|
||||
def validate_request(self, request_headers, request_body, repo_configs, action):
|
||||
return True
|
@ -0,0 +1,44 @@
|
||||
from .base import WebhookRequestParserBase
|
||||
|
||||
|
||||
class BitBucketRequestParser(WebhookRequestParserBase):
|
||||
|
||||
def get_matching_projects(self, request_headers, request_body, action):
|
||||
import json
|
||||
|
||||
data = json.loads(request_body)
|
||||
|
||||
repo_urls = []
|
||||
|
||||
action.log_debug("Received event from BitBucket")
|
||||
|
||||
if 'repository' not in data:
|
||||
action.log_error("Unable to recognize data format")
|
||||
return []
|
||||
|
||||
# One repository may posses multiple URLs for different protocols
|
||||
for k in ['url', 'git_url', 'clone_url', 'ssh_url']:
|
||||
if k in data['repository']:
|
||||
repo_urls.append(data['repository'][k])
|
||||
|
||||
if 'full_name' in data['repository']:
|
||||
repo_urls.append('git@bitbucket.org:%s.git' % data['repository']['full_name'])
|
||||
|
||||
# Add a simplified version of the bitbucket HTTPS URL - without the username@bitbucket.com part. This is
|
||||
# needed since the configured repositories might be configured using a different username.
|
||||
repo_urls.append('https://bitbucket.org/%s.git' % (data['repository']['full_name']))
|
||||
|
||||
if 'fullName' in data['repository']:
|
||||
# For Bitbucket Server add repository name (OWNER/REPO_NAME).
|
||||
# Support "Post Webhooks for Bitbucket".
|
||||
repo_urls.append(data['repository']['fullName'])
|
||||
|
||||
if 'slug' in data['repository']:
|
||||
# For Bitbucket Server add repository slug.
|
||||
# Support "Post-Receive WebHooks" and "Post Webhooks for Bitbucket".
|
||||
repo_urls.append(data['repository']['slug'])
|
||||
|
||||
# Get a list of configured repositories that matches the incoming web hook reqeust
|
||||
repo_configs = self.get_matching_repo_configs(repo_urls, action)
|
||||
|
||||
return repo_configs
|
@ -0,0 +1,40 @@
|
||||
from .base import WebhookRequestParserBase
|
||||
|
||||
|
||||
class CodingRequestParser(WebhookRequestParserBase):
|
||||
|
||||
def get_matching_projects(self, request_headers, request_body, action):
|
||||
import json
|
||||
|
||||
data = json.loads(request_body)
|
||||
|
||||
repo_urls = []
|
||||
|
||||
coding_event = 'x-coding-event' in request_headers and request_headers['x-coding-event']
|
||||
|
||||
if 'repository' not in data:
|
||||
action.log_error("Unable to recognize data format")
|
||||
return []
|
||||
|
||||
# One repository may posses multiple URLs for different protocols
|
||||
for k in ['web_url', 'https_url', 'ssh_url']:
|
||||
if k in data['repository']:
|
||||
repo_urls.append(data['repository'][k])
|
||||
|
||||
# Get a list of configured repositories that matches the incoming web hook reqeust
|
||||
items = self.get_matching_repo_configs(repo_urls, action)
|
||||
|
||||
repo_configs = []
|
||||
for repo_config in items:
|
||||
# Validate secret token if present
|
||||
if 'secret-token' in repo_config:
|
||||
if 'token' not in data or not self.verify_token(repo_config['secret-token'], data['token']):
|
||||
action.log_warning("Request token does not match the 'secret-token' configured for repository %s." % repo_config['url'])
|
||||
continue
|
||||
|
||||
repo_configs.append(repo_config)
|
||||
|
||||
return repo_configs
|
||||
|
||||
def verify_token(self, secret_token, request_token):
|
||||
return secret_token == request_token
|
@ -0,0 +1,28 @@
|
||||
from .base import WebhookRequestParserBase
|
||||
|
||||
|
||||
class GenericRequestParser(WebhookRequestParserBase):
|
||||
|
||||
def get_matching_projects(self, request_headers, request_body, action):
|
||||
import json
|
||||
|
||||
data = json.loads(request_body)
|
||||
|
||||
repo_urls = []
|
||||
|
||||
action.log_info("Received event from unknown origin. Assume generic data format.")
|
||||
|
||||
if 'repository' not in data:
|
||||
action.log_error("Unable to recognize data format")
|
||||
return []
|
||||
|
||||
# One repository may posses multiple URLs for different protocols
|
||||
for k in ['url', 'git_http_url', 'git_ssh_url', 'http_url', 'ssh_url']:
|
||||
if k in data['repository']:
|
||||
repo_urls.append(data['repository'][k])
|
||||
|
||||
# Get a list of configured repositories that matches the incoming web hook reqeust
|
||||
repo_configs = self.get_matching_repo_configs(repo_urls, action)
|
||||
|
||||
return repo_configs
|
||||
|
@ -0,0 +1,48 @@
|
||||
from .base import WebhookRequestParserBase
|
||||
|
||||
|
||||
class GitHubRequestParser(WebhookRequestParserBase):
|
||||
|
||||
def get_matching_projects(self, request_headers, request_body, action):
|
||||
import json
|
||||
|
||||
data = json.loads(request_body)
|
||||
|
||||
repo_urls = []
|
||||
|
||||
github_event = 'x-github-event' in request_headers and request_headers['x-github-event']
|
||||
|
||||
action.log_info("Received '%s' event from GitHub" % github_event)
|
||||
|
||||
if 'repository' not in data:
|
||||
action.log_error("Unable to recognize data format")
|
||||
return []
|
||||
|
||||
# One repository may posses multiple URLs for different protocols
|
||||
for k in ['url', 'git_url', 'clone_url', 'ssh_url']:
|
||||
if k in data['repository']:
|
||||
repo_urls.append(data['repository'][k])
|
||||
|
||||
# Get a list of configured repositories that matches the incoming web hook reqeust
|
||||
repo_configs = self.get_matching_repo_configs(repo_urls, action)
|
||||
|
||||
return repo_configs
|
||||
|
||||
def validate_request(self, request_headers, request_body, repo_configs, action):
|
||||
|
||||
for repo_config in repo_configs:
|
||||
|
||||
# Validate secret token if present
|
||||
if 'secret-token' in repo_config and 'x-hub-signature' in request_headers:
|
||||
if not self.verify_signature(repo_config['secret-token'], request_body, request_headers['x-hub-signature']):
|
||||
action.log_info("Request signature does not match the 'secret-token' configured for repository %s." % repo_config['url'])
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def verify_signature(self, token, body, signature):
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
result = "sha1=" + hmac.new(str(token), body, hashlib.sha1).hexdigest()
|
||||
return result == signature
|
@ -0,0 +1,42 @@
|
||||
from .base import WebhookRequestParserBase
|
||||
|
||||
|
||||
class GitLabRequestParser(WebhookRequestParserBase):
|
||||
|
||||
def get_matching_projects(self, request_headers, request_body, action):
|
||||
import json
|
||||
|
||||
data = json.loads(request_body)
|
||||
|
||||
repo_urls = []
|
||||
|
||||
gitlab_event = 'x-gitlab-event' in request_headers and request_headers['x-gitlab-event']
|
||||
|
||||
action.log_info("Received '%s' event from GitLab" % gitlab_event)
|
||||
|
||||
if 'repository' not in data:
|
||||
action.log_error("Unable to recognize data format")
|
||||
return []
|
||||
|
||||
# One repository may posses multiple URLs for different protocols
|
||||
for k in ['url', 'git_http_url', 'git_ssh_url']:
|
||||
if k in data['repository']:
|
||||
repo_urls.append(data['repository'][k])
|
||||
|
||||
# Get a list of configured repositories that matches the incoming web hook reqeust
|
||||
repo_configs = self.get_matching_repo_configs(repo_urls, action)
|
||||
|
||||
return repo_configs
|
||||
|
||||
def validate_request(self, request_headers, request_body, repo_configs, action):
|
||||
|
||||
for repo_config in repo_configs:
|
||||
|
||||
# Validate secret token if present
|
||||
if 'secret-token' in repo_config and 'x-gitlab-token' in request_headers:
|
||||
|
||||
if repo_config['secret-token'] != request_headers['x-gitlab-token']:
|
||||
action.log_info("Request token does not match the 'secret-token' configured for repository %s." % repo_config['url'])
|
||||
return False
|
||||
|
||||
return True
|
@ -0,0 +1,31 @@
|
||||
from .base import WebhookRequestParserBase
|
||||
|
||||
|
||||
class GitLabCIRequestParser(WebhookRequestParserBase):
|
||||
|
||||
def get_matching_projects(self, request_headers, request_body, action):
|
||||
import json
|
||||
|
||||
data = json.loads(request_body)
|
||||
|
||||
repo_urls = []
|
||||
|
||||
action.log_info('Received event from Gitlab CI')
|
||||
|
||||
if 'repository' not in data:
|
||||
action.log_error("Unable to recognize data format")
|
||||
return []
|
||||
|
||||
# Only add repositories if the build is successful. Ignore it in other case.
|
||||
if data['build_status'] == "success":
|
||||
for k in ['url', 'git_http_url', 'git_ssh_url']:
|
||||
for n in ['repository', 'project']:
|
||||
if n in data and k in data[n]:
|
||||
repo_urls.append(data[n][k])
|
||||
else:
|
||||
action.log_warning("Gitlab CI build '%d' has status '%s'. Not pull will be done" % (data['build_id'], data['build_status']))
|
||||
|
||||
# Get a list of configured repositories that matches the incoming web hook reqeust
|
||||
repo_configs = self.get_matching_repo_configs(repo_urls, action)
|
||||
|
||||
return repo_configs
|
@ -0,0 +1,2 @@
|
||||
from .git import *
|
||||
from .process import *
|
@ -0,0 +1,158 @@
|
||||
class GitWrapper():
|
||||
"""Wraps the git client. Currently uses git through shell command
|
||||
invocations."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def init(repo_config):
|
||||
"""Init remote url of the repo from the git server"""
|
||||
import logging
|
||||
from .process import ProcessWrapper
|
||||
import os
|
||||
import platform
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.info("Initializing repository %s" % repo_config['path'])
|
||||
|
||||
commands = []
|
||||
|
||||
# On Windows, bash command needs to be run using bash.exe. This assumes bash.exe
|
||||
# (typically installed under C:\Program Files\Git\bin) is in the system PATH.
|
||||
if platform.system().lower() == "windows":
|
||||
commands.append('bash -c "cd \\"' + repo_config['path'] + '\\" && unset GIT_DIR"')
|
||||
else:
|
||||
commands.append('unset GIT_DIR')
|
||||
|
||||
commands.append('git remote set-url ' + repo_config['remote'] + " " + repo_config['url'])
|
||||
commands.append('git fetch ' + repo_config['remote'])
|
||||
commands.append('git checkout -f -B ' + repo_config['branch'] + ' -t ' + repo_config['remote'] + '/' + repo_config['branch'])
|
||||
commands.append('git submodule update --init --recursive')
|
||||
|
||||
# All commands need to success
|
||||
for command in commands:
|
||||
res = ProcessWrapper().call(command, cwd=repo_config['path'], shell=True, supressStderr=True)
|
||||
|
||||
if res != 0:
|
||||
logger.error("Command '%s' failed with exit code %s" % (command, res))
|
||||
break
|
||||
|
||||
if res == 0 and os.path.isdir(repo_config['path']):
|
||||
logger.info("Repository %s successfully initialized" % repo_config['path'])
|
||||
else:
|
||||
logger.error("Unable to init repository %s" % repo_config['path'])
|
||||
|
||||
return int(res)
|
||||
|
||||
@staticmethod
|
||||
def pull(repo_config):
|
||||
"""Pulls the latest version of the repo from the git server"""
|
||||
import logging
|
||||
from .process import ProcessWrapper
|
||||
import os
|
||||
import platform
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.info("Updating repository %s" % repo_config['path'])
|
||||
|
||||
# Only pull if there is actually a local copy of the repository
|
||||
if 'path' not in repo_config:
|
||||
logger.info('No local repository path configured, no pull will occure')
|
||||
return 0
|
||||
|
||||
commands = []
|
||||
|
||||
# On Windows, bash command needs to be run using bash.exe. This assumes bash.exe
|
||||
# (typically installed under C:\Program Files\Git\bin) is in the system PATH.
|
||||
if platform.system().lower() == "windows":
|
||||
commands.append('bash -c "cd \\"' + repo_config['path'] + '\\" && unset GIT_DIR"')
|
||||
else:
|
||||
commands.append('unset GIT_DIR')
|
||||
|
||||
if "prepull" in repo_config:
|
||||
commands.append(repo_config['prepull'])
|
||||
|
||||
commands.append('git fetch ' + repo_config['remote'])
|
||||
commands.append('git reset --hard ' + repo_config['remote'] + "/" + repo_config['branch'])
|
||||
commands.append('git submodule update --init --recursive')
|
||||
|
||||
if "postpull" in repo_config:
|
||||
commands.append(repo_config['postpull'])
|
||||
|
||||
# All commands need to success
|
||||
for command in commands:
|
||||
res = ProcessWrapper().call(command, cwd=repo_config['path'], shell=True, supressStderr=True)
|
||||
|
||||
if res != 0:
|
||||
logger.error("Command '%s' failed with exit code %s" % (command, res))
|
||||
break
|
||||
|
||||
if res == 0 and os.path.isdir(repo_config['path']):
|
||||
logger.info("Repository %s successfully updated" % repo_config['path'])
|
||||
else:
|
||||
logger.error("Unable to update repository %s" % repo_config['path'])
|
||||
|
||||
return int(res)
|
||||
|
||||
@staticmethod
|
||||
def clone(repo_config):
|
||||
"""Clones the latest version of the repo from the git server"""
|
||||
import logging
|
||||
from .process import ProcessWrapper
|
||||
import os
|
||||
import platform
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.info("Cloning repository %s" % repo_config['path'])
|
||||
|
||||
# Only pull if there is actually a local copy of the repository
|
||||
if 'path' not in repo_config:
|
||||
logger.info('No local repository path configured, no clone will occure')
|
||||
return 0
|
||||
|
||||
commands = []
|
||||
commands.append('unset GIT_DIR')
|
||||
commands.append('git clone --recursive ' + repo_config['url'] + ' -b ' + repo_config['branch'] + ' ' + repo_config['path'])
|
||||
|
||||
# All commands need to success
|
||||
for command in commands:
|
||||
res = ProcessWrapper().call(command, shell=True)
|
||||
|
||||
if res != 0:
|
||||
logger.error("Command '%s' failed with exit code %s" % (command, res))
|
||||
break
|
||||
|
||||
if res == 0 and os.path.isdir(repo_config['path']):
|
||||
logger.info("Repository %s successfully cloned" % repo_config['url'])
|
||||
else:
|
||||
logger.error("Unable to clone repository %s" % repo_config['url'])
|
||||
|
||||
return int(res)
|
||||
|
||||
@staticmethod
|
||||
def deploy(repo_config):
|
||||
"""Executes any supplied post-pull deploy command"""
|
||||
from .process import ProcessWrapper
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
if 'path' in repo_config:
|
||||
path = repo_config['path']
|
||||
|
||||
if not 'deploy_commands' in repo_config or len(repo_config['deploy_commands']) == 0:
|
||||
logger.info('No deploy commands configured')
|
||||
return []
|
||||
|
||||
logger.info('Executing %s deploy commands' % str(len(repo_config['deploy_commands'])))
|
||||
|
||||
# Use repository path as default cwd when executing deploy commands
|
||||
cwd = (repo_config['path'] if 'path' in repo_config else None)
|
||||
|
||||
res = []
|
||||
for cmd in repo_config['deploy_commands']:
|
||||
res.append(ProcessWrapper().call([cmd], cwd=cwd, shell=True))
|
||||
|
||||
logger.info('%s commands executed with status; %s' % (str(len(res)), str(res)))
|
||||
|
||||
return res
|
@ -0,0 +1,43 @@
|
||||
class ProcessWrapper():
|
||||
"""Wraps the subprocess popen method and provides logging."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def call(*popenargs, **kwargs):
|
||||
"""Run command with arguments. Wait for command to complete. Sends
|
||||
output to logging module. The arguments are the same as for the Popen
|
||||
constructor."""
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
kwargs['stdout'] = PIPE
|
||||
kwargs['stderr'] = PIPE
|
||||
|
||||
supressStderr = None
|
||||
if 'supressStderr' in kwargs:
|
||||
supressStderr = kwargs['supressStderr']
|
||||
del kwargs['supressStderr']
|
||||
|
||||
p = Popen(*popenargs, **kwargs)
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
# Decode bytes to string (assume utf-8 encoding)
|
||||
stdout = stdout.decode("utf-8")
|
||||
stderr = stderr.decode("utf-8")
|
||||
|
||||
if stdout:
|
||||
for line in stdout.strip().split("\n"):
|
||||
logger.info(line)
|
||||
|
||||
if stderr:
|
||||
for line in stderr.strip().split("\n"):
|
||||
if supressStderr:
|
||||
logger.info(line)
|
||||
else:
|
||||
logger.error(line)
|
||||
|
||||
return p.returncode
|
@ -0,0 +1,117 @@
|
||||
from .events import SystemEvent
|
||||
|
||||
try:
|
||||
from autobahn.websocket import WebSocketServerProtocol
|
||||
except ImportError:
|
||||
WebSocketServerProtocol = object
|
||||
|
||||
def WebSocketClientHandlerFactory(config, clients, event_store, server_status):
|
||||
"""Factory method for webhook request handler class"""
|
||||
|
||||
class WebSocketClientHandler(WebSocketServerProtocol, object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._config = config
|
||||
self.clients = clients
|
||||
self._event_store = event_store
|
||||
self._server_status = server_status
|
||||
import logging
|
||||
self.logger = logging.getLogger()
|
||||
super(WebSocketClientHandler, self).__init__(*args, **kwargs)
|
||||
|
||||
def onConnect(self, request):
|
||||
self.logger.info("Client connecting: {0}".format(request.peer))
|
||||
|
||||
# Web UI needs to be enabled
|
||||
if not self.validate_web_ui_enabled():
|
||||
return
|
||||
|
||||
# Client needs to be whitelisted
|
||||
if not self.validate_web_ui_whitelist():
|
||||
return
|
||||
|
||||
def onOpen(self):
|
||||
self.logger.info("WebSocket connection open.")
|
||||
|
||||
def onMessage(self, payload, isBinary):
|
||||
import json
|
||||
|
||||
if isBinary:
|
||||
return
|
||||
|
||||
try:
|
||||
data = json.loads(payload)
|
||||
|
||||
# Handle authentication requests
|
||||
if 'type' in data and data['type'] == 'authenticate':
|
||||
|
||||
# Verify auth key
|
||||
if 'auth-key' in data and data['auth-key'] == self._server_status['auth-key']:
|
||||
self.clients.append(self)
|
||||
|
||||
# Let the client know that they are authenticated
|
||||
self.sendMessage(json.dumps({
|
||||
"type": "authenticated"
|
||||
}))
|
||||
return
|
||||
|
||||
else:
|
||||
self.logger.error("Recieved bad auth key.")
|
||||
|
||||
self.sendMessage(json.dumps({
|
||||
"type": "bad-auth-key"
|
||||
}))
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
|
||||
self.logger.error("Unable to interpret incoming message: %s" % e)
|
||||
|
||||
if self not in self.clients:
|
||||
self.logger.error("Recieved message form unauthenticated client, closing connection.")
|
||||
self.sendClose()
|
||||
return
|
||||
|
||||
#self.logger.info("WebSocket connection open.")
|
||||
#if isBinary:
|
||||
# self.logger.info("Binary message received: {0} bytes".format(len(payload)))
|
||||
#else:
|
||||
# self.logger.info("Text message received: {0}".format(payload.decode('utf8')))
|
||||
|
||||
#for client in self.clients:
|
||||
# client.sendMessage(payload, isBinary)
|
||||
|
||||
# echo back message verbatim
|
||||
#self.sendMessage(payload, isBinary)
|
||||
|
||||
def onClose(self, wasClean, code, reason):
|
||||
self.logger.info("WebSocket connection closed: {0}".format(reason))
|
||||
|
||||
if self in self.clients:
|
||||
self.clients.remove(self)
|
||||
|
||||
def validate_web_ui_enabled(self):
|
||||
"""Verify that the Web UI is enabled"""
|
||||
|
||||
if self._config['web-ui-enabled']:
|
||||
return True
|
||||
|
||||
self.sendClose()
|
||||
return False
|
||||
|
||||
def validate_web_ui_whitelist(self):
|
||||
"""Verify that the client address is whitelisted"""
|
||||
|
||||
# Allow all if whitelist is empty
|
||||
if len(self._config['web-ui-whitelist']) == 0:
|
||||
return True
|
||||
|
||||
# Verify that client IP is whitelisted
|
||||
if self.peer.host in self._config['web-ui-whitelist']:
|
||||
return True
|
||||
|
||||
self.sendClose()
|
||||
logger.info("Unautorized connection attempt from %s" % self.peer.host)
|
||||
return False
|
||||
|
||||
return WebSocketClientHandler
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"main.css": "static/css/main.e20565bf.css",
|
||||
"main.css.map": "static/css/main.e20565bf.css.map",
|
||||
"main.js": "static/js/main.58631280.js",
|
||||
"main.js.map": "static/js/main.58631280.js.map"
|
||||
}
|
@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="shortcut icon" href="/favicon.ico"><title>Git-Auto-Deploy Web UI</title><link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"><link href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i" rel="stylesheet"><link rel="stylesheet" href="//cdn.materialdesignicons.com/1.7.22/css/materialdesignicons.min.css"><link href="/static/css/main.e20565bf.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="/static/js/main.58631280.js"></script></body></html>
|
@ -0,0 +1,2 @@
|
||||
body,html{height:100%;margin:0;padding:0}body{background:#f2f2f2}
|
||||
/*# sourceMappingURL=main.e20565bf.css.map*/
|
@ -0,0 +1 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"static/css/main.e20565bf.css","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# DEPRECATED: Use make instead
|
||||
#
|
||||
# This script compiles a binary Debian package (.deb)
|
||||
#
|
||||
|
||||
echo "DEPRECATED: Use make instead"
|
||||
exit
|
||||
|
||||
# Get current path
|
||||
ORIGINAL_CWD=`pwd -P`
|
||||
|
||||
# Get script path (<path>/Git-Auto-Deploy/platforms/debian/scripts)
|
||||
pushd `dirname $0` > /dev/null
|
||||
SCRIPT_PATH=`pwd -P`
|
||||
popd > /dev/null
|
||||
|
||||
# Path to Git-Auto-Deploy project directory
|
||||
PROJECT_PATH=`readlink -f $SCRIPT_PATH/../../../`
|
||||
cd $PROJECT_PATH
|
||||
|
||||
# Get package name and version
|
||||
PACKAGE_NAME=`python setup.py --name`
|
||||
PACKAGE_VERSION=`python setup.py --version`
|
||||
|
||||
# Generate a Debian source package
|
||||
echo
|
||||
echo "** Generating a Debian source package **"
|
||||
python setup.py --command-packages=stdeb.command sdist_dsc -x platforms/debian/stdeb.cfg
|
||||
|
||||
# Path to newly generated deb_dist directory
|
||||
TARGET=`readlink -f "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION"`
|
||||
|
||||
# Copy configuration files
|
||||
echo
|
||||
echo "** Copying configuration files **"
|
||||
cp -vr "$PROJECT_PATH/platforms/debian/stdeb"/* "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/"
|
||||
#cp -vrp "$PROJECT_PATH/platforms/debian/etc"/* "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/"
|
||||
#cp -vrp "$PROJECT_PATH/platforms/debian/etc"/* "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/source"
|
||||
#mkdir "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/gitautodeploy"
|
||||
#mkdir "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/git-auto-deploy"
|
||||
#mkdir "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/tmp"
|
||||
#cp -vrp "$PROJECT_PATH/platforms/debian/etc"/* "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/gitautodeploy"
|
||||
#cp -vrp "$PROJECT_PATH/platforms/debian/etc"/* "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/debian/tmp"
|
||||
#cp -vrp "$PROJECT_PATH/platforms/debian/etc"/* "$PROJECT_PATH/deb_dist/$PACKAGE_NAME-$PACKAGE_VERSION/gitautodeploy"
|
||||
|
||||
# Copile a Debian binary package
|
||||
echo
|
||||
echo "** Compiling a Debian binary package **"
|
||||
cd "$PROJECT_PATH/deb_dist/"*
|
||||
|
||||
#dpkg-source --commit
|
||||
|
||||
dpkg-buildpackage -rfakeroot -uc -us
|
||||
|
||||
# Restore cwd
|
||||
cd $ORIGINAL_CWD
|
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
Package: git-auto-deploy
|
||||
|
@ -0,0 +1 @@
|
||||
7
|
@ -0,0 +1,17 @@
|
||||
Source: git-auto-deploy
|
||||
Maintainer: Oliver Poignant <oliver@poignant.se>
|
||||
Section: python
|
||||
Priority: optional
|
||||
Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7), python-setuptools (>= 0.6), git (>= 2), python-lockfile (>= 0.8), python-twisted (>= 13.2), python-autobahn (>= 0.5)
|
||||
Standards-Version: 3.9.1
|
||||
|
||||
|
||||
|
||||
Package: git-auto-deploy
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, ${python:Depends}
|
||||
Description: Deploy your GitHub, GitLab or Bitbucket projects automatical
|
||||
GitAutoDeploy consists of a HTTP server that listens for Web hook requests sent from GitHub, GitLab or Bitbucket servers. This application allows you to continuously and automatically deploy you projects each time you push new commits to your repository.
|
||||
|
||||
|
||||
|
@ -0,0 +1,153 @@
|
||||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: gitautodeploy
|
||||
# Required-Start: $remote_fs $syslog $network
|
||||
# Required-Stop: $remote_fs $syslog $network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Script to start Autodeploy Git
|
||||
# Description: Autodeploy script for Gitlab
|
||||
### END INIT INFO
|
||||
|
||||
# Author: JA Nache <nache.nache@gmail.com>
|
||||
|
||||
NAME="git-auto-deploy"
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="GitAutodeploy"
|
||||
DAEMON=$(which $NAME)
|
||||
DAEMON_UID="git-auto-deploy"
|
||||
DAEMON_GID="git-auto-deploy"
|
||||
RUNDIR=/var/run/$NAME
|
||||
PIDFILE=$RUNDIR/$NAME.pid
|
||||
LOGFILE=/var/log/$NAME.log
|
||||
#PWD=/opt/Git-Auto-Deploy/
|
||||
OPTIONS="--daemon-mode --pid-file $PIDFILE --log-file $LOGFILE --config /etc/$NAME.conf.json"
|
||||
SCRIPTNAME="/etc/init.d/$NAME"
|
||||
|
||||
# Exit if the package is not installed
|
||||
#[ -x $DAEMON ] || echo "$NAME is not installed" && exit 0
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME || ENABLE_GITAUTODEPLOY=yes
|
||||
|
||||
#
|
||||
# Check whether daemon starting is enabled
|
||||
#
|
||||
check_start_daemon() {
|
||||
if [ ! "$ENABLE_GITAUTODEPLOY" = "yes" ]; then
|
||||
[ "$VERBOSE" != no ] && \
|
||||
log_warning_msg "Not starting gitautodeploy, disabled via /etc/default/gitautodeploy"
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
echo "Starting.."
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
|
||||
if [ ! -d $RUNDIR ]; then
|
||||
mkdir $RUNDIR
|
||||
fi
|
||||
|
||||
if ! dpkg-statoverride --list $RUNDIR >/dev/null 2>&1; then
|
||||
chown $DAEMON_UID:$DAEMON_GID $RUNDIR
|
||||
chmod g-w,o-rwx $RUNDIR
|
||||
fi
|
||||
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
|
||||
--name $NAME --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
|
||||
--name $NAME --umask 0027 --chuid $DAEMON_UID:$DAEMON_GID -- $OPTIONS \
|
||||
|| return 2
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
echo "Stopping.."
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --signal 6 --retry 30 --pidfile $PIDFILE
|
||||
}
|
||||
|
||||
#
|
||||
# Function that reload the daemon/service
|
||||
#
|
||||
do_reload()
|
||||
{
|
||||
echo "Reloading.."
|
||||
start-stop-daemon --stop -s 1 --pidfile $PIDFILE
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
check_start_daemon || exit 0
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc -p $PIDFILE "python" "$DAEMON" && exit 0 || exit $?
|
||||
|
||||
;;
|
||||
reload)
|
||||
do_reload
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:
|
||||
|
@ -0,0 +1 @@
|
||||
gitautodeploy/data/git-auto-deploy.conf.json etc
|
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
NAME="git-auto-deploy"
|
||||
GAD_UID="git-auto-deploy"
|
||||
GAD_GID="git-auto-deploy"
|
||||
|
||||
HOME_DIR="/etc/$NAME"
|
||||
DATA_DIR="/var/lib/$NAME"
|
||||
PID_DIR="/var/run/$NAME"
|
||||
PID_FILE="$PID_DIR/$NAME.pid"
|
||||
|
||||
# Add user and group
|
||||
adduser --quiet --system --home $HOME_DIR --no-create-home --ingroup nogroup --disabled-password $GAD_UID
|
||||
addgroup --system $GAD_GID
|
||||
adduser $GAD_UID $GAD_GID
|
||||
|
||||
# Create home dir
|
||||
mkdir -p $HOME_DIR
|
||||
mkdir -p $HOME_DIR/.ssh
|
||||
touch $HOME_DIR/.ssh/known_hosts
|
||||
|
||||
# Generate SSL certificate if openssl is installed
|
||||
if [ -x "$(command -v openssl)" ]; then
|
||||
openssl req -new -x509 -keyout $HOME_DIR/cert.pem -out $HOME_DIR/cert.pem -days 365 -nodes -subj "/C=/ST=/L=/O=Git Auto Deploy/OU=/CN=localhost"
|
||||
fi
|
||||
|
||||
# Set permissions on home dirßß
|
||||
chown -R $GAD_UID:$GAD_GID $HOME_DIR
|
||||
chmod -R 750 $HOME_DIR
|
||||
|
||||
# Create log file
|
||||
touch /var/log/$NAME.log
|
||||
chown $GAD_UID:$GAD_GID /var/log/$NAME.log
|
||||
chmod 750 /var/log/$NAME.log
|
||||
|
||||
# Create data directory
|
||||
if [ ! -d "$DATA_DIR" ]; then
|
||||
mkdir -p $DATA_DIR
|
||||
chown -R $GAD_UID:$GAD_GID $DATA_DIR
|
||||
chmod 750 $DATA_DIR
|
||||
fi
|
||||
|
||||
# Create pid file
|
||||
if [ ! -d "$PID_DIR" ]; then
|
||||
mkdir $PID_DIR
|
||||
touch $PID_FILE
|
||||
chown $GAD_UID:$GAD_GID $PID_DIR $PID_FILE
|
||||
chmod 750 $PID_FILE
|
||||
fi
|
||||
|
||||
update-rc.d $NAME defaults
|
@ -0,0 +1 @@
|
||||
#!/bin/bash
|
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
# This file was automatically generated by stdeb 0.8.5 at
|
||||
# Thu, 10 Mar 2016 19:52:51 +0100
|
||||
|
||||
%:
|
||||
dh $@ --with python2 --buildsystem=python_distutils
|
||||
|
||||
|
||||
override_dh_auto_clean:
|
||||
python setup.py clean -a
|
||||
find . -name \*.pyc -exec rm {} \;
|
||||
|
||||
|
||||
|
||||
override_dh_auto_build:
|
||||
python setup.py build --force
|
||||
|
||||
|
||||
|
||||
override_dh_auto_install:
|
||||
python setup.py install --force --root=debian/git-auto-deploy --no-compile -O0 --install-layout=deb
|
||||
|
||||
|
||||
|
||||
override_dh_python2:
|
||||
dh_python2 --no-guessing-versions
|
@ -0,0 +1 @@
|
||||
3.0 (quilt)
|
@ -0,0 +1 @@
|
||||
extend-diff-ignore="\.egg-info$"
|
@ -0,0 +1,44 @@
|
||||
SERVICE_NAME=GitAutoDeploy
|
||||
PID_PATH_NAME=tmp/.gitautodeploy.pid
|
||||
LOG_PATH_NAME=tmp/gitautodeploy.log
|
||||
DATE=`date +%Y%m%d_%H%M%S`
|
||||
case $1 in
|
||||
start)
|
||||
echo "Starting $SERVICE_NAME ..."
|
||||
if [ ! -f $PID_PATH_NAME ]; then
|
||||
python -m gitautodeploy --allow-root-user --daemon-mode --config config.json
|
||||
echo "$SERVICE_NAME started ..."
|
||||
else
|
||||
echo "$SERVICE_NAME is already running ..."
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ -f $PID_PATH_NAME ]; then
|
||||
PID=$(cat $PID_PATH_NAME);
|
||||
echo "$SERVICE_NAME stoping ..."
|
||||
kill $PID;
|
||||
echo "$SERVICE_NAME stopped ..."
|
||||
cp $LOG_PATH_NAME $LOG_PATH_NAME"_"$DATE
|
||||
rm $PID_PATH_NAME
|
||||
rm $LOG_PATH_NAME
|
||||
else
|
||||
echo "$SERVICE_NAME is not running ..."
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
if [ -f $PID_PATH_NAME ]; then
|
||||
PID=$(cat $PID_PATH_NAME);
|
||||
echo "$SERVICE_NAME stopping ...";
|
||||
kill $PID;
|
||||
echo "$SERVICE_NAME stopped ...";
|
||||
cp $LOG_PATH_NAME $LOG_PATH_NAME"_"$DATE
|
||||
rm $PID_PATH_NAME
|
||||
rm $LOG_PATH_NAME
|
||||
echo "$SERVICE_NAME starting ..."
|
||||
python -m gitautodeploy --allow-root-user --daemon-mode --config config.json
|
||||
echo "$SERVICE_NAME started ..."
|
||||
else
|
||||
echo "$SERVICE_NAME is not running ..."
|
||||
fi
|
||||
;;
|
||||
esac
|
@ -0,0 +1,151 @@
|
||||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: gitautodeploy
|
||||
# Required-Start: $remote_fs $syslog $network
|
||||
# Required-Stop: $remote_fs $syslog $network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Script to start Autodeploy Git
|
||||
# Description: Autodeploy script for Gitlab
|
||||
### END INIT INFO
|
||||
|
||||
# Author: JA Nache <nache.nache@gmail.com>
|
||||
|
||||
NAME="git-auto-deploy"
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="GitAutodeploy"
|
||||
DAEMON="/usr/bin/env python -m /opt/Git-Auto-Deploy/gitautodeploy"
|
||||
DAEMON_UID=root
|
||||
DAEMON_GID=root
|
||||
RUNDIR=/var/run/$NAME
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
LOGFILE=/var/log/$NAME.log
|
||||
PWD=/opt/Git-Auto-Deploy/
|
||||
OPTIONS="--daemon-mode --quiet --pid-file $PIDFILE --log-file $LOGFILE"
|
||||
USER=root
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
#[ -x $DAEMON ] || echo "$NAME is not installed" && exit 0
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME || ENABLE_GITAUTODEPLOY=yes
|
||||
|
||||
#
|
||||
# Check whether daemon starting is enabled
|
||||
#
|
||||
check_start_daemon() {
|
||||
if [ ! "$ENABLE_GITAUTODEPLOY" = "yes" ]; then
|
||||
[ "$VERBOSE" != no ] && \
|
||||
log_warning_msg "Not starting gitautodeploy, disabled via /etc/default/gitautodeploy"
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
|
||||
if [ ! -d $RUNDIR ]; then
|
||||
mkdir $RUNDIR
|
||||
fi
|
||||
|
||||
if ! dpkg-statoverride --list $dir >/dev/null 2>&1; then
|
||||
chown $DAEMON_UID:$DAEMON_GID $RUNDIR
|
||||
chmod g-w,o-rwx $RUNDIR
|
||||
fi
|
||||
|
||||
start-stop-daemon --start --quiet --chdir $PWD --pidfile $PIDFILE --startas $DAEMON \
|
||||
--name $NAME --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --chdir $PWD --pidfile $PIDFILE --startas $DAEMON \
|
||||
--name $NAME --umask 0027 --chuid $DAEMON_UID:$DAEMON_GID -- $OPTIONS \
|
||||
|| return 2
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --signal 6 --retry 30 --pidfile $PIDFILE
|
||||
}
|
||||
|
||||
#
|
||||
# Function that reload the daemon/service
|
||||
#
|
||||
do_reload()
|
||||
{
|
||||
start-stop-daemon --stop -s 1 --pidfile $PIDFILE
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
check_start_daemon || exit 0
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc -p $PIDFILE "python" "$DAEMON" && exit 0 || exit $?
|
||||
|
||||
;;
|
||||
reload)
|
||||
do_reload
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:
|
||||
|
@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=GitAutoDeploy
|
||||
|
||||
[Service]
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=/opt/Git-Auto-Deploy/
|
||||
ExecStart=/usr/bin/python -m /opt/Git-Auto-Deploy/gitautodeploy --daemon-mode
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -0,0 +1,2 @@
|
||||
setuptools>=20.3.1
|
||||
lockfile>=0.12.2
|
@ -0,0 +1,43 @@
|
||||
from setuptools import setup, find_packages
|
||||
import os
|
||||
import sys
|
||||
|
||||
def package_files(package_path, directory_name):
|
||||
paths = []
|
||||
directory_path = os.path.join(package_path, directory_name)
|
||||
|
||||
for (path, directories, filenames) in os.walk(directory_path):
|
||||
relative_path = os.path.relpath(path, package_path)
|
||||
for filename in filenames:
|
||||
if filename[0] == ".":
|
||||
continue
|
||||
paths.append(os.path.join(relative_path, filename))
|
||||
return paths
|
||||
|
||||
# Get path to project
|
||||
package_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "gitautodeploy")
|
||||
|
||||
# Get list of data files
|
||||
wwwroot_files = package_files(package_path, "wwwroot")
|
||||
data_files = package_files(package_path, "data")
|
||||
|
||||
setup(name='git-auto-deploy',
|
||||
version='0.17',
|
||||
url='https://github.com/olipo186/Git-Auto-Deploy',
|
||||
author='Oliver Poignant',
|
||||
author_email='oliver@poignant.se',
|
||||
packages=find_packages(),
|
||||
package_data={'gitautodeploy': data_files + wwwroot_files},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'git-auto-deploy = gitautodeploy.__main__:main'
|
||||
]
|
||||
},
|
||||
install_requires=[
|
||||
'lockfile',
|
||||
'autobahn',
|
||||
'twisted'
|
||||
],
|
||||
description = "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks.",
|
||||
long_description = "GitAutoDeploy consists of a HTTP server that listens for Web hook requests sent from GitHub, GitLab or Bitbucket servers. This application allows you to continuously and automatically deploy you projects each time you push new commits to your repository."
|
||||
)
|
@ -0,0 +1,4 @@
|
||||
|
||||
if __name__ == '__main__':
|
||||
from test_parsers import main
|
||||
main()
|
@ -0,0 +1,210 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://bitbucket.org/oliverpoignant/test-hook.git",
|
||||
"header-filter": {
|
||||
"x-event-key": "pullrequest:merged"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": []
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate",
|
||||
"connection": "keep-alive",
|
||||
"content-length": "4248",
|
||||
"content-type": "application/json",
|
||||
"host": "narpau.se:8001",
|
||||
"user-agent": "Bitbucket-Webhooks/2.0",
|
||||
"x-attempt-number": "1",
|
||||
"x-event-key": "pullrequest:created",
|
||||
"x-hook-uuid": "1f813038-7b26-4d08-9b80-ee76eeb53d93",
|
||||
"x-request-uuid": "2e6626d9-effe-46f7-8bfd-e9dee9a58920"
|
||||
},
|
||||
"payload": {
|
||||
"actor": {
|
||||
"display_name": "Oliver Poignant",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/oliverpoignant/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/oliverpoignant"
|
||||
}
|
||||
},
|
||||
"type": "user",
|
||||
"username": "oliverpoignant",
|
||||
"uuid": "{d628d18b-5fe4-4f45-b34f-cc1a20073194}"
|
||||
},
|
||||
"pullrequest": {
|
||||
"author": {
|
||||
"display_name": "Oliver Poignant",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/oliverpoignant/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/oliverpoignant"
|
||||
}
|
||||
},
|
||||
"type": "user",
|
||||
"username": "oliverpoignant",
|
||||
"uuid": "{d628d18b-5fe4-4f45-b34f-cc1a20073194}"
|
||||
},
|
||||
"close_source_branch": false,
|
||||
"closed_by": null,
|
||||
"comment_count": 0,
|
||||
"created_on": "2016-12-08T19:30:52.967233+00:00",
|
||||
"description": "",
|
||||
"destination": {
|
||||
"branch": {
|
||||
"name": "master"
|
||||
},
|
||||
"commit": {
|
||||
"hash": "04dad24aa44d",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/commit/04dad24aa44d"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "oliverpoignant/test-hook",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook"
|
||||
}
|
||||
},
|
||||
"name": "test-hook",
|
||||
"type": "repository",
|
||||
"uuid": "{88ad4da3-6e3e-41ad-9aef-f3a26731d913}"
|
||||
}
|
||||
},
|
||||
"id": 1,
|
||||
"links": {
|
||||
"activity": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/activity"
|
||||
},
|
||||
"approve": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/approve"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/comments"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/commits"
|
||||
},
|
||||
"decline": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/decline"
|
||||
},
|
||||
"diff": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/diff"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/pull-requests/1"
|
||||
},
|
||||
"merge": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/merge"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/statuses"
|
||||
}
|
||||
},
|
||||
"merge_commit": null,
|
||||
"participants": [],
|
||||
"reason": "",
|
||||
"reviewers": [],
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "development"
|
||||
},
|
||||
"commit": {
|
||||
"hash": "198a37b77185",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/commit/198a37b77185"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "oliverpoignant/test-hook",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook"
|
||||
}
|
||||
},
|
||||
"name": "test-hook",
|
||||
"type": "repository",
|
||||
"uuid": "{88ad4da3-6e3e-41ad-9aef-f3a26731d913}"
|
||||
}
|
||||
},
|
||||
"state": "OPEN",
|
||||
"task_count": 0,
|
||||
"title": "ahhha edited online with Bitbucket",
|
||||
"type": "pullrequest",
|
||||
"updated_on": "2016-12-08T19:30:52.984875+00:00"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "oliverpoignant/test-hook",
|
||||
"is_private": true,
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook"
|
||||
}
|
||||
},
|
||||
"name": "test-hook",
|
||||
"owner": {
|
||||
"display_name": "Oliver Poignant",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/oliverpoignant/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/oliverpoignant"
|
||||
}
|
||||
},
|
||||
"type": "user",
|
||||
"username": "oliverpoignant",
|
||||
"uuid": "{d628d18b-5fe4-4f45-b34f-cc1a20073194}"
|
||||
},
|
||||
"scm": "git",
|
||||
"type": "repository",
|
||||
"uuid": "{88ad4da3-6e3e-41ad-9aef-f3a26731d913}",
|
||||
"website": ""
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://bitbucket.org/oliverpoignant/test-hook.git",
|
||||
"header-filter": {
|
||||
"x-event-key": "pullrequest:created"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate",
|
||||
"connection": "keep-alive",
|
||||
"content-length": "4248",
|
||||
"content-type": "application/json",
|
||||
"host": "narpau.se:8001",
|
||||
"user-agent": "Bitbucket-Webhooks/2.0",
|
||||
"x-attempt-number": "1",
|
||||
"x-event-key": "pullrequest:created",
|
||||
"x-hook-uuid": "1f813038-7b26-4d08-9b80-ee76eeb53d93",
|
||||
"x-request-uuid": "2e6626d9-effe-46f7-8bfd-e9dee9a58920"
|
||||
},
|
||||
"payload": {
|
||||
"actor": {
|
||||
"display_name": "Oliver Poignant",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/oliverpoignant/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/oliverpoignant"
|
||||
}
|
||||
},
|
||||
"type": "user",
|
||||
"username": "oliverpoignant",
|
||||
"uuid": "{d628d18b-5fe4-4f45-b34f-cc1a20073194}"
|
||||
},
|
||||
"pullrequest": {
|
||||
"author": {
|
||||
"display_name": "Oliver Poignant",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/oliverpoignant/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/oliverpoignant"
|
||||
}
|
||||
},
|
||||
"type": "user",
|
||||
"username": "oliverpoignant",
|
||||
"uuid": "{d628d18b-5fe4-4f45-b34f-cc1a20073194}"
|
||||
},
|
||||
"close_source_branch": false,
|
||||
"closed_by": null,
|
||||
"comment_count": 0,
|
||||
"created_on": "2016-12-08T19:30:52.967233+00:00",
|
||||
"description": "",
|
||||
"destination": {
|
||||
"branch": {
|
||||
"name": "master"
|
||||
},
|
||||
"commit": {
|
||||
"hash": "04dad24aa44d",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/commit/04dad24aa44d"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "oliverpoignant/test-hook",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook"
|
||||
}
|
||||
},
|
||||
"name": "test-hook",
|
||||
"type": "repository",
|
||||
"uuid": "{88ad4da3-6e3e-41ad-9aef-f3a26731d913}"
|
||||
}
|
||||
},
|
||||
"id": 1,
|
||||
"links": {
|
||||
"activity": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/activity"
|
||||
},
|
||||
"approve": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/approve"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/comments"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/commits"
|
||||
},
|
||||
"decline": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/decline"
|
||||
},
|
||||
"diff": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/diff"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/pull-requests/1"
|
||||
},
|
||||
"merge": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/merge"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/pullrequests/1/statuses"
|
||||
}
|
||||
},
|
||||
"merge_commit": null,
|
||||
"participants": [],
|
||||
"reason": "",
|
||||
"reviewers": [],
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "development"
|
||||
},
|
||||
"commit": {
|
||||
"hash": "198a37b77185",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook/commit/198a37b77185"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "oliverpoignant/test-hook",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook"
|
||||
}
|
||||
},
|
||||
"name": "test-hook",
|
||||
"type": "repository",
|
||||
"uuid": "{88ad4da3-6e3e-41ad-9aef-f3a26731d913}"
|
||||
}
|
||||
},
|
||||
"state": "OPEN",
|
||||
"task_count": 0,
|
||||
"title": "ahhha edited online with Bitbucket",
|
||||
"type": "pullrequest",
|
||||
"updated_on": "2016-12-08T19:30:52.984875+00:00"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "oliverpoignant/test-hook",
|
||||
"is_private": true,
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/test-hook"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/oliverpoignant/test-hook"
|
||||
}
|
||||
},
|
||||
"name": "test-hook",
|
||||
"owner": {
|
||||
"display_name": "Oliver Poignant",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/oliverpoignant/avatar/32/"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/oliverpoignant/"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/oliverpoignant"
|
||||
}
|
||||
},
|
||||
"type": "user",
|
||||
"username": "oliverpoignant",
|
||||
"uuid": "{d628d18b-5fe4-4f45-b34f-cc1a20073194}"
|
||||
},
|
||||
"scm": "git",
|
||||
"type": "repository",
|
||||
"uuid": "{88ad4da3-6e3e-41ad-9aef-f3a26731d913}",
|
||||
"website": ""
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://bitbucket.example.com/scm/PROJ_KEY/test-hook.git",
|
||||
"match-url": "PROJ_KEY/test-hook",
|
||||
"header-filter": {
|
||||
"x-event-key": "pullrequest:merged"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": []
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate",
|
||||
"connection": "keep-alive",
|
||||
"content-length": "4248",
|
||||
"content-type": "application/json",
|
||||
"host": "narpau.se:8001",
|
||||
"user-agent": "Bitbucket-Webhooks/2.0",
|
||||
"x-attempt-number": "1",
|
||||
"x-event-key": "pullrequest:created",
|
||||
"x-hook-uuid": "1f813038-7b26-4d08-9b80-ee76eeb53d93",
|
||||
"x-request-uuid": "2e6626d9-effe-46f7-8bfd-e9dee9a58920"
|
||||
},
|
||||
"payload": {
|
||||
"actor": {
|
||||
"username": "username",
|
||||
"displayName": "User Name"
|
||||
},
|
||||
"pullrequest": {
|
||||
"id": "1",
|
||||
"title": "Pull request title",
|
||||
"link": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/pull-requests/1",
|
||||
"authorLogin": "User Name",
|
||||
"fromRef": {
|
||||
"repository": {
|
||||
"scmId": "git",
|
||||
"project": {
|
||||
"key": "PROJ_KEY",
|
||||
"name": "Project Key"
|
||||
},
|
||||
"slug": "test-hook",
|
||||
"links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/browse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"public": false,
|
||||
"owner": {
|
||||
"username": "PROJ_KEY",
|
||||
"displayName": "PROJ_KEY"
|
||||
},
|
||||
"fullName": "PROJ_KEY/test-hook",
|
||||
"ownerName": "PROJ_KEY"
|
||||
},
|
||||
"branch": {
|
||||
"name": "test",
|
||||
"rawNode": "4f430da32d7bea2ea13fb8e564267758bce84801"
|
||||
},
|
||||
"commit": {
|
||||
"message": null,
|
||||
"date": null,
|
||||
"hash": "4f430da32d7bea2ea13fb8e564267758bce84801",
|
||||
"authorTimestamp": 0
|
||||
}
|
||||
},
|
||||
"toRef": {
|
||||
"repository": {
|
||||
"scmId": "git",
|
||||
"project": {
|
||||
"key": "PROJ_KEY",
|
||||
"name": "Project Key"
|
||||
},
|
||||
"slug": "test-hook",
|
||||
"links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/browse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"public": false,
|
||||
"owner": {
|
||||
"username": "PROJ_KEY",
|
||||
"displayName": "PROJ_KEY"
|
||||
},
|
||||
"fullName": "PROJ_KEY/test-hook",
|
||||
"ownerName": "PROJ_KEY"
|
||||
},
|
||||
"branch": {
|
||||
"name": "develop",
|
||||
"rawNode": "82f42b549b35a42626df183003f84db6d896209d"
|
||||
},
|
||||
"commit": {
|
||||
"message": null,
|
||||
"date": null,
|
||||
"hash": "82f42b549b35a42626df183003f84db6d896209d",
|
||||
"authorTimestamp": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"scmId": "git",
|
||||
"project": {
|
||||
"key": "PROJ_KEY",
|
||||
"name": "Project Key"
|
||||
},
|
||||
"slug": "test-hook",
|
||||
"links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/browse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"public": false,
|
||||
"owner": {
|
||||
"username": "PROJ_KEY",
|
||||
"displayName": "PROJ_KEY"
|
||||
},
|
||||
"fullName": "PROJ_KEY/test-hook",
|
||||
"ownerName": "PROJ_KEY"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://bitbucket.example.com/scm/PROJ_KEY/test-hook.git",
|
||||
"match-url": "PROJ_KEY/test-hook",
|
||||
"header-filter": {
|
||||
"x-event-key": "pullrequest:created"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate",
|
||||
"connection": "keep-alive",
|
||||
"content-length": "4248",
|
||||
"content-type": "application/json",
|
||||
"host": "narpau.se:8001",
|
||||
"user-agent": "Bitbucket-Webhooks/2.0",
|
||||
"x-attempt-number": "1",
|
||||
"x-event-key": "pullrequest:created",
|
||||
"x-hook-uuid": "1f813038-7b26-4d08-9b80-ee76eeb53d93",
|
||||
"x-request-uuid": "2e6626d9-effe-46f7-8bfd-e9dee9a58920"
|
||||
},
|
||||
"payload": {
|
||||
"actor": {
|
||||
"username": "username",
|
||||
"displayName": "User Name"
|
||||
},
|
||||
"pullrequest": {
|
||||
"id": "1",
|
||||
"title": "Pull request title",
|
||||
"link": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/pull-requests/1",
|
||||
"authorLogin": "User Name",
|
||||
"fromRef": {
|
||||
"repository": {
|
||||
"scmId": "git",
|
||||
"project": {
|
||||
"key": "PROJ_KEY",
|
||||
"name": "Project Key"
|
||||
},
|
||||
"slug": "test-hook",
|
||||
"links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/browse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"public": false,
|
||||
"owner": {
|
||||
"username": "PROJ_KEY",
|
||||
"displayName": "PROJ_KEY"
|
||||
},
|
||||
"fullName": "PROJ_KEY/test-hook",
|
||||
"ownerName": "PROJ_KEY"
|
||||
},
|
||||
"branch": {
|
||||
"name": "test",
|
||||
"rawNode": "4f430da32d7bea2ea13fb8e564267758bce84801"
|
||||
},
|
||||
"commit": {
|
||||
"message": null,
|
||||
"date": null,
|
||||
"hash": "4f430da32d7bea2ea13fb8e564267758bce84801",
|
||||
"authorTimestamp": 0
|
||||
}
|
||||
},
|
||||
"toRef": {
|
||||
"repository": {
|
||||
"scmId": "git",
|
||||
"project": {
|
||||
"key": "PROJ_KEY",
|
||||
"name": "Project Key"
|
||||
},
|
||||
"slug": "test-hook",
|
||||
"links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/browse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"public": false,
|
||||
"owner": {
|
||||
"username": "PROJ_KEY",
|
||||
"displayName": "PROJ_KEY"
|
||||
},
|
||||
"fullName": "PROJ_KEY/test-hook",
|
||||
"ownerName": "PROJ_KEY"
|
||||
},
|
||||
"branch": {
|
||||
"name": "develop",
|
||||
"rawNode": "82f42b549b35a42626df183003f84db6d896209d"
|
||||
},
|
||||
"commit": {
|
||||
"message": null,
|
||||
"date": null,
|
||||
"hash": "82f42b549b35a42626df183003f84db6d896209d",
|
||||
"authorTimestamp": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"scmId": "git",
|
||||
"project": {
|
||||
"key": "PROJ_KEY",
|
||||
"name": "Project Key"
|
||||
},
|
||||
"slug": "test-hook",
|
||||
"links": {
|
||||
"self": [
|
||||
{
|
||||
"href": "https://bitbucket.example.com/projects/PROJ_KEY/repos/test-hook/browse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"public": false,
|
||||
"owner": {
|
||||
"username": "PROJ_KEY",
|
||||
"displayName": "PROJ_KEY"
|
||||
},
|
||||
"fullName": "PROJ_KEY/test-hook",
|
||||
"ownerName": "PROJ_KEY"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://bitbucket.org/team/repository.git",
|
||||
"header-filter": {
|
||||
"x-event-key": "pullrequest:fulfilled"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"X-Request-UUID": "78b41616-e3b4-4f18-b911-4ba3baf5fec5",
|
||||
"X-Event-Key": "pullrequest:fulfilled",
|
||||
"User-Agent": "Bitbucket-Webhooks/2.0",
|
||||
"X-Attempt-Number": "3",
|
||||
"X-Hook-UUID": "07a11b9b-d82a-4558-a903-8b76e2b541f5",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"payload": {
|
||||
"pullrequest": {
|
||||
"merge_commit": {
|
||||
"hash": "4ea4f3f317a6",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/commit/4ea4f3f317a6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "",
|
||||
"links": {
|
||||
"decline": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/decline"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/commits"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/comments"
|
||||
},
|
||||
"merge": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/merge"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository/pull-requests/156"
|
||||
},
|
||||
"activity": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/activity"
|
||||
},
|
||||
"diff": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/diff"
|
||||
},
|
||||
"approve": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/approve"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/statuses"
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"username": "User",
|
||||
"display_name": "User",
|
||||
"type": "user",
|
||||
"uuid": "{59f0a5d6-47ab-4f00-899d-efc81e98dba1}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/User"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/User/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/User/avatar/32/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"close_source_branch": false,
|
||||
"reviewers": [],
|
||||
"destination": {
|
||||
"commit": {
|
||||
"hash": "095aa224e257",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/commit/095aa224e257"
|
||||
}
|
||||
}
|
||||
},
|
||||
"branch": {
|
||||
"name": "master"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "team/repository",
|
||||
"type": "repository",
|
||||
"uuid": "{a064b276-e793-42a5-8b27-050056718971}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/team/repository/avatar/32/"
|
||||
}
|
||||
},
|
||||
"name": "Repository Name"
|
||||
}
|
||||
},
|
||||
"comment_count": 0,
|
||||
"id": 156,
|
||||
"source": {
|
||||
"commit": {
|
||||
"hash": "d41c93cba906",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/commit/d41c93cba906"
|
||||
}
|
||||
}
|
||||
},
|
||||
"branch": {
|
||||
"name": "develop"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "team/repository",
|
||||
"type": "repository",
|
||||
"uuid": "{a064b276-e793-42a5-8b27-050056718971}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/team/repository/avatar/32/"
|
||||
}
|
||||
},
|
||||
"name": "Repository Name"
|
||||
}
|
||||
},
|
||||
"participants": [],
|
||||
"state": "MERGED",
|
||||
"task_count": 0,
|
||||
"created_on": "2016-12-02T19:13:24.073024+00:00",
|
||||
"updated_on": "2016-12-02T19:13:47.132095+00:00",
|
||||
"reason": "",
|
||||
"title": "Pull request title.",
|
||||
"type": "pullrequest",
|
||||
"closed_by": {
|
||||
"username": "User",
|
||||
"display_name": "User",
|
||||
"type": "user",
|
||||
"uuid": "{59f0a5d6-47ab-4f00-899d-efc81e98dba1}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/User"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/User/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/User/avatar/32/"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"website": "",
|
||||
"scm": "git",
|
||||
"uuid": "{a064b276-e793-42a5-8b27-050056718971}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/team/repository/avatar/32/"
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/teams/team/projects/PROJ"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/account/user/team/projects/PROJ"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/user/team/projects/PROJ/avatar/32"
|
||||
}
|
||||
},
|
||||
"type": "project",
|
||||
"uuid": "{0b88330f-47da-4fa8-9a61-dc8e0dae21f1}",
|
||||
"key": "PROJ",
|
||||
"name": "Repository Name"
|
||||
},
|
||||
"full_name": "team/repository",
|
||||
"owner": {
|
||||
"username": "team",
|
||||
"display_name": "Repository Name ",
|
||||
"type": "team",
|
||||
"uuid": "{1ac257f7-39d5-49b4-acbd-21def2e164fc}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/teams/team"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/team/avatar/32/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "repository",
|
||||
"is_private": true,
|
||||
"name": "Repository Name"
|
||||
},
|
||||
"actor": {
|
||||
"username": "User",
|
||||
"display_name": "User",
|
||||
"type": "user",
|
||||
"uuid": "{59f0a5d6-47ab-4f00-899d-efc81e98dba1}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/User"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/User/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/User/avatar/32/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://bitbucket.org/team/repository.git",
|
||||
"header-filter": {
|
||||
"x-event-key": "pullrequest:created"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": []
|
||||
},
|
||||
"headers": {
|
||||
"X-Request-UUID": "78b41616-e3b4-4f18-b911-4ba3baf5fec5",
|
||||
"X-Event-Key": "pullrequest:fulfilled",
|
||||
"User-Agent": "Bitbucket-Webhooks/2.0",
|
||||
"X-Attempt-Number": "3",
|
||||
"X-Hook-UUID": "07a11b9b-d82a-4558-a903-8b76e2b541f5",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"payload": {
|
||||
"pullrequest": {
|
||||
"merge_commit": {
|
||||
"hash": "4ea4f3f317a6",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/commit/4ea4f3f317a6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "",
|
||||
"links": {
|
||||
"decline": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/decline"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/commits"
|
||||
},
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/comments"
|
||||
},
|
||||
"merge": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/merge"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository/pull-requests/156"
|
||||
},
|
||||
"activity": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/activity"
|
||||
},
|
||||
"diff": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/diff"
|
||||
},
|
||||
"approve": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/approve"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/pullrequests/156/statuses"
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"username": "User",
|
||||
"display_name": "User",
|
||||
"type": "user",
|
||||
"uuid": "{59f0a5d6-47ab-4f00-899d-efc81e98dba1}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/User"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/User/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/User/avatar/32/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"close_source_branch": false,
|
||||
"reviewers": [],
|
||||
"destination": {
|
||||
"commit": {
|
||||
"hash": "095aa224e257",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/commit/095aa224e257"
|
||||
}
|
||||
}
|
||||
},
|
||||
"branch": {
|
||||
"name": "master"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "team/repository",
|
||||
"type": "repository",
|
||||
"uuid": "{a064b276-e793-42a5-8b27-050056718971}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/team/repository/avatar/32/"
|
||||
}
|
||||
},
|
||||
"name": "Repository Name"
|
||||
}
|
||||
},
|
||||
"comment_count": 0,
|
||||
"id": 156,
|
||||
"source": {
|
||||
"commit": {
|
||||
"hash": "d41c93cba906",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository/commit/d41c93cba906"
|
||||
}
|
||||
}
|
||||
},
|
||||
"branch": {
|
||||
"name": "develop"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "team/repository",
|
||||
"type": "repository",
|
||||
"uuid": "{a064b276-e793-42a5-8b27-050056718971}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/team/repository/avatar/32/"
|
||||
}
|
||||
},
|
||||
"name": "Repository Name"
|
||||
}
|
||||
},
|
||||
"participants": [],
|
||||
"state": "MERGED",
|
||||
"task_count": 0,
|
||||
"created_on": "2016-12-02T19:13:24.073024+00:00",
|
||||
"updated_on": "2016-12-02T19:13:47.132095+00:00",
|
||||
"reason": "",
|
||||
"title": "Pull request title.",
|
||||
"type": "pullrequest",
|
||||
"closed_by": {
|
||||
"username": "User",
|
||||
"display_name": "User",
|
||||
"type": "user",
|
||||
"uuid": "{59f0a5d6-47ab-4f00-899d-efc81e98dba1}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/User"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/User/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/User/avatar/32/"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"website": "",
|
||||
"scm": "git",
|
||||
"uuid": "{a064b276-e793-42a5-8b27-050056718971}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/repositories/team/repository"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/repository"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/team/repository/avatar/32/"
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/teams/team/projects/PROJ"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/account/user/team/projects/PROJ"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/user/team/projects/PROJ/avatar/32"
|
||||
}
|
||||
},
|
||||
"type": "project",
|
||||
"uuid": "{0b88330f-47da-4fa8-9a61-dc8e0dae21f1}",
|
||||
"key": "PROJ",
|
||||
"name": "Repository Name"
|
||||
},
|
||||
"full_name": "team/repository",
|
||||
"owner": {
|
||||
"username": "team",
|
||||
"display_name": "Repository Name ",
|
||||
"type": "team",
|
||||
"uuid": "{1ac257f7-39d5-49b4-acbd-21def2e164fc}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/teams/team"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/team/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/team/avatar/32/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "repository",
|
||||
"is_private": true,
|
||||
"name": "Repository Name"
|
||||
},
|
||||
"actor": {
|
||||
"username": "User",
|
||||
"display_name": "User",
|
||||
"type": "user",
|
||||
"uuid": "{59f0a5d6-47ab-4f00-899d-efc81e98dba1}",
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https://api.bitbucket.org/2.0/users/User"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://bitbucket.org/User/"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https://bitbucket.org/account/User/avatar/32/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,462 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"filters": [
|
||||
{
|
||||
"type": "pull-request-filter",
|
||||
"action": "opened",
|
||||
"ref": "master"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": []
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-length": "6602",
|
||||
"content-type": "application/json",
|
||||
"host": "host:8001",
|
||||
"user-agent": "GitHub-Hookshot/e4028f5",
|
||||
"x-github-delivery": "xxx",
|
||||
"x-github-event": "pull_request",
|
||||
"x-hub-signature": "xxx"
|
||||
},
|
||||
"payload": {
|
||||
"action": "closed",
|
||||
"number": 69,
|
||||
"pull_request": {
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69",
|
||||
"id": 61793882,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69",
|
||||
"diff_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.diff",
|
||||
"patch_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.patch",
|
||||
"issue_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69",
|
||||
"number": 69,
|
||||
"state": "closed",
|
||||
"locked": false,
|
||||
"title": "Refactoring. Fixed some imminent issues.",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"body": "",
|
||||
"created_at": "2016-03-05T01:05:00Z",
|
||||
"updated_at": "2016-03-05T01:05:15Z",
|
||||
"closed_at": "2016-03-05T01:05:15Z",
|
||||
"merged_at": "2016-03-05T01:05:15Z",
|
||||
"merge_commit_sha": "eac02076546716660d7b9ad7a354172257012035",
|
||||
"assignee": null,
|
||||
"milestone": null,
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits",
|
||||
"review_comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments",
|
||||
"review_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"head": {
|
||||
"label": "olipo186:development",
|
||||
"ref": "development",
|
||||
"sha": "fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"label": "olipo186:master",
|
||||
"ref": "master",
|
||||
"sha": "5b525b98a9226e1232e233fcbaea684a49c52255",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/olipo186/Git-Auto-Deploy/pull/69"
|
||||
},
|
||||
"issue": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments"
|
||||
},
|
||||
"review_comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments"
|
||||
},
|
||||
"review_comment": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6"
|
||||
}
|
||||
},
|
||||
"merged": true,
|
||||
"mergeable": null,
|
||||
"mergeable_state": "unknown",
|
||||
"merged_by": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"comments": 0,
|
||||
"review_comments": 0,
|
||||
"commits": 2,
|
||||
"additions": 139,
|
||||
"deletions": 88,
|
||||
"changed_files": 5
|
||||
},
|
||||
"repository": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
},
|
||||
"sender": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,466 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"filters": [
|
||||
{
|
||||
"type": "pull-request-filter",
|
||||
"action": "closed",
|
||||
"ref": "master"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-length": "6602",
|
||||
"content-type": "application/json",
|
||||
"host": "host:8001",
|
||||
"user-agent": "GitHub-Hookshot/e4028f5",
|
||||
"x-github-delivery": "xxx",
|
||||
"x-github-event": "pull_request",
|
||||
"x-hub-signature": "xxx"
|
||||
},
|
||||
"payload": {
|
||||
"action": "closed",
|
||||
"number": 69,
|
||||
"pull_request": {
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69",
|
||||
"id": 61793882,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69",
|
||||
"diff_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.diff",
|
||||
"patch_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.patch",
|
||||
"issue_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69",
|
||||
"number": 69,
|
||||
"state": "closed",
|
||||
"locked": false,
|
||||
"title": "Refactoring. Fixed some imminent issues.",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"body": "",
|
||||
"created_at": "2016-03-05T01:05:00Z",
|
||||
"updated_at": "2016-03-05T01:05:15Z",
|
||||
"closed_at": "2016-03-05T01:05:15Z",
|
||||
"merged_at": "2016-03-05T01:05:15Z",
|
||||
"merge_commit_sha": "eac02076546716660d7b9ad7a354172257012035",
|
||||
"assignee": null,
|
||||
"milestone": null,
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits",
|
||||
"review_comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments",
|
||||
"review_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"head": {
|
||||
"label": "olipo186:development",
|
||||
"ref": "development",
|
||||
"sha": "fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"label": "olipo186:master",
|
||||
"ref": "master",
|
||||
"sha": "5b525b98a9226e1232e233fcbaea684a49c52255",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/olipo186/Git-Auto-Deploy/pull/69"
|
||||
},
|
||||
"issue": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments"
|
||||
},
|
||||
"review_comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments"
|
||||
},
|
||||
"review_comment": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6"
|
||||
}
|
||||
},
|
||||
"merged": true,
|
||||
"mergeable": null,
|
||||
"mergeable_state": "unknown",
|
||||
"merged_by": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"comments": 0,
|
||||
"review_comments": 0,
|
||||
"commits": 2,
|
||||
"additions": 139,
|
||||
"deletions": 88,
|
||||
"changed_files": 5
|
||||
},
|
||||
"repository": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
},
|
||||
"sender": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,461 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"filters": [
|
||||
{
|
||||
"pull_request": false,
|
||||
"action": "closed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": []
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-length": "6602",
|
||||
"content-type": "application/json",
|
||||
"host": "host:8001",
|
||||
"user-agent": "GitHub-Hookshot/e4028f5",
|
||||
"x-github-delivery": "xxx",
|
||||
"x-github-event": "pull_request",
|
||||
"x-hub-signature": "xxx"
|
||||
},
|
||||
"payload": {
|
||||
"action": "closed",
|
||||
"number": 69,
|
||||
"pull_request": {
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69",
|
||||
"id": 61793882,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69",
|
||||
"diff_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.diff",
|
||||
"patch_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.patch",
|
||||
"issue_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69",
|
||||
"number": 69,
|
||||
"state": "closed",
|
||||
"locked": false,
|
||||
"title": "Refactoring. Fixed some imminent issues.",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"body": "",
|
||||
"created_at": "2016-03-05T01:05:00Z",
|
||||
"updated_at": "2016-03-05T01:05:15Z",
|
||||
"closed_at": "2016-03-05T01:05:15Z",
|
||||
"merged_at": "2016-03-05T01:05:15Z",
|
||||
"merge_commit_sha": "eac02076546716660d7b9ad7a354172257012035",
|
||||
"assignee": null,
|
||||
"milestone": null,
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits",
|
||||
"review_comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments",
|
||||
"review_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"head": {
|
||||
"label": "olipo186:development",
|
||||
"ref": "development",
|
||||
"sha": "fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"label": "olipo186:master",
|
||||
"ref": "master",
|
||||
"sha": "5b525b98a9226e1232e233fcbaea684a49c52255",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/olipo186/Git-Auto-Deploy/pull/69"
|
||||
},
|
||||
"issue": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments"
|
||||
},
|
||||
"review_comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments"
|
||||
},
|
||||
"review_comment": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6"
|
||||
}
|
||||
},
|
||||
"merged": true,
|
||||
"mergeable": null,
|
||||
"mergeable_state": "unknown",
|
||||
"merged_by": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"comments": 0,
|
||||
"review_comments": 0,
|
||||
"commits": 2,
|
||||
"additions": 139,
|
||||
"deletions": 88,
|
||||
"changed_files": 5
|
||||
},
|
||||
"repository": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
},
|
||||
"sender": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,467 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"filters": [
|
||||
{
|
||||
"pull_request": true,
|
||||
"action": "closed",
|
||||
"pull_request.head.ref": "development",
|
||||
"pull_request.base.ref": "master"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-length": "6602",
|
||||
"content-type": "application/json",
|
||||
"host": "host:8001",
|
||||
"user-agent": "GitHub-Hookshot/e4028f5",
|
||||
"x-github-delivery": "xxx",
|
||||
"x-github-event": "pull_request",
|
||||
"x-hub-signature": "xxx"
|
||||
},
|
||||
"payload": {
|
||||
"action": "closed",
|
||||
"number": 69,
|
||||
"pull_request": {
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69",
|
||||
"id": 61793882,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69",
|
||||
"diff_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.diff",
|
||||
"patch_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.patch",
|
||||
"issue_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69",
|
||||
"number": 69,
|
||||
"state": "closed",
|
||||
"locked": false,
|
||||
"title": "Refactoring. Fixed some imminent issues.",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"body": "",
|
||||
"created_at": "2016-03-05T01:05:00Z",
|
||||
"updated_at": "2016-03-05T01:05:15Z",
|
||||
"closed_at": "2016-03-05T01:05:15Z",
|
||||
"merged_at": "2016-03-05T01:05:15Z",
|
||||
"merge_commit_sha": "eac02076546716660d7b9ad7a354172257012035",
|
||||
"assignee": null,
|
||||
"milestone": null,
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits",
|
||||
"review_comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments",
|
||||
"review_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"head": {
|
||||
"label": "olipo186:development",
|
||||
"ref": "development",
|
||||
"sha": "fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"label": "olipo186:master",
|
||||
"ref": "master",
|
||||
"sha": "5b525b98a9226e1232e233fcbaea684a49c52255",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/olipo186/Git-Auto-Deploy/pull/69"
|
||||
},
|
||||
"issue": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments"
|
||||
},
|
||||
"review_comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments"
|
||||
},
|
||||
"review_comment": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6"
|
||||
}
|
||||
},
|
||||
"merged": true,
|
||||
"mergeable": null,
|
||||
"mergeable_state": "unknown",
|
||||
"merged_by": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"comments": 0,
|
||||
"review_comments": 0,
|
||||
"commits": 2,
|
||||
"additions": 139,
|
||||
"deletions": 88,
|
||||
"changed_files": 5
|
||||
},
|
||||
"repository": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:16Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 11,
|
||||
"forks": 58,
|
||||
"open_issues": 11,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
},
|
||||
"sender": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,447 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"payload-filter": [
|
||||
{
|
||||
"action": "opened",
|
||||
"pull_request.user.login": "olipo186"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-length": "6602",
|
||||
"content-type": "application/json",
|
||||
"host": "host:8001",
|
||||
"user-agent": "GitHub-Hookshot/e4028f5",
|
||||
"x-github-delivery": "xxx",
|
||||
"x-github-event": "pull_request",
|
||||
"x-hub-signature": "xxx"
|
||||
},
|
||||
"payload": {
|
||||
"action": "opened",
|
||||
"number": 69,
|
||||
"pull_request": {
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69",
|
||||
"id": 61793882,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69",
|
||||
"diff_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.diff",
|
||||
"patch_url": "https://github.com/olipo186/Git-Auto-Deploy/pull/69.patch",
|
||||
"issue_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69",
|
||||
"number": 69,
|
||||
"state": "open",
|
||||
"locked": false,
|
||||
"title": "Refactoring. Fixed some imminent issues.",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"body": "",
|
||||
"created_at": "2016-03-05T01:05:00Z",
|
||||
"updated_at": "2016-03-05T01:05:01Z",
|
||||
"closed_at": null,
|
||||
"merged_at": null,
|
||||
"merge_commit_sha": "eac02076546716660d7b9ad7a354172257012035",
|
||||
"assignee": null,
|
||||
"milestone": null,
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits",
|
||||
"review_comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments",
|
||||
"review_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"head": {
|
||||
"label": "olipo186:development",
|
||||
"ref": "development",
|
||||
"sha": "fd16c583425112d8d3be350fe40de5cae17f35c6",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:01Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 12,
|
||||
"forks": 58,
|
||||
"open_issues": 12,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"label": "olipo186:master",
|
||||
"ref": "master",
|
||||
"sha": "5b525b98a9226e1232e233fcbaea684a49c52255",
|
||||
"user": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:01Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 12,
|
||||
"forks": 58,
|
||||
"open_issues": 12,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
}
|
||||
},
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/olipo186/Git-Auto-Deploy/pull/69"
|
||||
},
|
||||
"issue": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/69/comments"
|
||||
},
|
||||
"review_comments": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/comments"
|
||||
},
|
||||
"review_comment": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/comments{/number}"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls/69/commits"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/fd16c583425112d8d3be350fe40de5cae17f35c6"
|
||||
}
|
||||
},
|
||||
"merged": false,
|
||||
"mergeable": true,
|
||||
"mergeable_state": "clean",
|
||||
"merged_by": null,
|
||||
"comments": 0,
|
||||
"review_comments": 0,
|
||||
"commits": 2,
|
||||
"additions": 139,
|
||||
"deletions": 88,
|
||||
"changed_files": 5
|
||||
},
|
||||
"repository": {
|
||||
"id": 10534595,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"owner": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy",
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"created_at": "2013-06-06T19:25:38Z",
|
||||
"updated_at": "2016-03-04T15:08:35Z",
|
||||
"pushed_at": "2016-03-05T01:05:01Z",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"size": 365,
|
||||
"stargazers_count": 204,
|
||||
"watchers_count": 204,
|
||||
"language": "Python",
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": true,
|
||||
"forks_count": 58,
|
||||
"mirror_url": null,
|
||||
"open_issues_count": 12,
|
||||
"forks": 58,
|
||||
"open_issues": 12,
|
||||
"watchers": 204,
|
||||
"default_branch": "master"
|
||||
},
|
||||
"sender": {
|
||||
"login": "olipo186",
|
||||
"id": 1056476,
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/olipo186",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy.git"
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"content-length": "6602",
|
||||
"content-type": "application/json",
|
||||
"host": "host:8001",
|
||||
"user-agent": "GitHub-Hookshot/e4028f5",
|
||||
"x-github-delivery": "xxx",
|
||||
"x-github-event": "push",
|
||||
"x-hub-signature": "xxx"
|
||||
},
|
||||
"payload": {
|
||||
"after": "b60ff44438d884b200d70de9c45fec5a15f2c0fa",
|
||||
"base_ref": null,
|
||||
"before": "6aa6dd514d5b80c9f36fec6c56d4094fd182de61",
|
||||
"commits": [
|
||||
{
|
||||
"added": [],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant",
|
||||
"username": "olipo186"
|
||||
},
|
||||
"committer": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant",
|
||||
"username": "olipo186"
|
||||
},
|
||||
"distinct": true,
|
||||
"id": "b60ff44438d884b200d70de9c45fec5a15f2c0fa",
|
||||
"message": "Updated docs",
|
||||
"modified": [
|
||||
"gitautodeploy/httpserver.py"
|
||||
],
|
||||
"removed": [],
|
||||
"timestamp": "2016-05-08T00:08:09+02:00",
|
||||
"tree_id": "af9230d6223d6f6849b0f86ff7b5bef8b777a85b",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy/commit/b60ff44438d884b200d70de9c45fec5a15f2c0fa"
|
||||
}
|
||||
],
|
||||
"compare": "https://github.com/olipo186/Git-Auto-Deploy/compare/6aa6dd514d5b...b60ff44438d8",
|
||||
"created": false,
|
||||
"deleted": false,
|
||||
"forced": false,
|
||||
"head_commit": {
|
||||
"added": [],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant",
|
||||
"username": "olipo186"
|
||||
},
|
||||
"committer": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant",
|
||||
"username": "olipo186"
|
||||
},
|
||||
"distinct": true,
|
||||
"id": "b60ff44438d884b200d70de9c45fec5a15f2c0fa",
|
||||
"message": "Updated docs",
|
||||
"modified": [
|
||||
"gitautodeploy/httpserver.py"
|
||||
],
|
||||
"removed": [],
|
||||
"timestamp": "2016-05-08T00:08:09+02:00",
|
||||
"tree_id": "af9230d6223d6f6849b0f86ff7b5bef8b777a85b",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy/commit/b60ff44438d884b200d70de9c45fec5a15f2c0fa"
|
||||
},
|
||||
"pusher": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "olipo186"
|
||||
},
|
||||
"ref": "refs/heads/master",
|
||||
"repository": {
|
||||
"archive_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/branches{/branch}",
|
||||
"clone_url": "https://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"collaborators_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/contributors",
|
||||
"created_at": 1370546738,
|
||||
"default_branch": "master",
|
||||
"deployments_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/deployments",
|
||||
"description": "Deploy your GitHub, GitLab or Bitbucket projects automatically on Git push events or webhooks using this small HTTP server written in Python. Continuous deployment in it's most simple form.",
|
||||
"downloads_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/downloads",
|
||||
"events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/events",
|
||||
"fork": false,
|
||||
"forks": 71,
|
||||
"forks_count": 71,
|
||||
"forks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/forks",
|
||||
"full_name": "olipo186/Git-Auto-Deploy",
|
||||
"git_commits_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/tags{/sha}",
|
||||
"git_url": "git://github.com/olipo186/Git-Auto-Deploy.git",
|
||||
"has_downloads": true,
|
||||
"has_issues": true,
|
||||
"has_pages": true,
|
||||
"has_wiki": true,
|
||||
"homepage": "http://olipo186.github.io/Git-Auto-Deploy/",
|
||||
"hooks_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/hooks",
|
||||
"html_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"id": 10534595,
|
||||
"issue_comment_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/labels{/name}",
|
||||
"language": "Python",
|
||||
"languages_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/languages",
|
||||
"master_branch": "master",
|
||||
"merges_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/merges",
|
||||
"milestones_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/milestones{/number}",
|
||||
"mirror_url": null,
|
||||
"name": "Git-Auto-Deploy",
|
||||
"notifications_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/notifications{?since,all,participating}",
|
||||
"open_issues": 9,
|
||||
"open_issues_count": 9,
|
||||
"owner": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "olipo186"
|
||||
},
|
||||
"private": false,
|
||||
"pulls_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/pulls{/number}",
|
||||
"pushed_at": 1462658898,
|
||||
"releases_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/releases{/id}",
|
||||
"size": 502,
|
||||
"ssh_url": "git@github.com:olipo186/Git-Auto-Deploy.git",
|
||||
"stargazers": 259,
|
||||
"stargazers_count": 259,
|
||||
"stargazers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/subscription",
|
||||
"svn_url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"tags_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/tags",
|
||||
"teams_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/teams",
|
||||
"trees_url": "https://api.github.com/repos/olipo186/Git-Auto-Deploy/git/trees{/sha}",
|
||||
"updated_at": "2016-05-07T11:10:07Z",
|
||||
"url": "https://github.com/olipo186/Git-Auto-Deploy",
|
||||
"watchers": 259,
|
||||
"watchers_count": 259
|
||||
},
|
||||
"sender": {
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1056476?v=3",
|
||||
"events_url": "https://api.github.com/users/olipo186/events{/privacy}",
|
||||
"followers_url": "https://api.github.com/users/olipo186/followers",
|
||||
"following_url": "https://api.github.com/users/olipo186/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/olipo186/gists{/gist_id}",
|
||||
"gravatar_id": "",
|
||||
"html_url": "https://github.com/olipo186",
|
||||
"id": 1056476,
|
||||
"login": "olipo186",
|
||||
"organizations_url": "https://api.github.com/users/olipo186/orgs",
|
||||
"received_events_url": "https://api.github.com/users/olipo186/received_events",
|
||||
"repos_url": "https://api.github.com/users/olipo186/repos",
|
||||
"site_admin": false,
|
||||
"starred_url": "https://api.github.com/users/olipo186/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/olipo186/subscriptions",
|
||||
"type": "User",
|
||||
"url": "https://api.github.com/users/olipo186"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "http://gitlab.example.com/oliver/poignant-se.git"
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"connection": "close",
|
||||
"content-length": "2336",
|
||||
"content-type": "application/json",
|
||||
"host": "example.com:8001",
|
||||
"x-gitlab-event": "Push Hook"
|
||||
},
|
||||
"payload": {
|
||||
"after": "bb26e21adb5a97d3f7b45d0c29506d52793c8f3b",
|
||||
"before": "fdd525130a91991648783608f9d55c359ed41abf",
|
||||
"checkout_sha": "bb26e21adb5a97d3f7b45d0c29506d52793c8f3b",
|
||||
"commits": [
|
||||
{
|
||||
"added": [],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "bb26e21adb5a97d3f7b45d0c29506d52793c8f3b",
|
||||
"message": "aa",
|
||||
"modified": [
|
||||
"README.md"
|
||||
],
|
||||
"removed": [],
|
||||
"timestamp": "2016-04-14T00:19:23+02:00",
|
||||
"url": "http://gitlab.example.com/oliver/poignant-se/commit/bb26e21adb5a97d3f7b45d0c29506d52793c8f3b"
|
||||
},
|
||||
{
|
||||
"added": [],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "dcfccd6cb19ef50c6b7e27a657f9e37bd041a913",
|
||||
"message": "ss",
|
||||
"modified": [
|
||||
"README.md"
|
||||
],
|
||||
"removed": [],
|
||||
"timestamp": "2016-04-14T00:18:53+02:00",
|
||||
"url": "http://gitlab.example.com/oliver/poignant-se/commit/dcfccd6cb19ef50c6b7e27a657f9e37bd041a913"
|
||||
},
|
||||
{
|
||||
"added": [],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "fdd525130a91991648783608f9d55c359ed41abf",
|
||||
"message": "aa",
|
||||
"modified": [
|
||||
"README.md"
|
||||
],
|
||||
"removed": [],
|
||||
"timestamp": "2016-04-14T00:08:43+02:00",
|
||||
"url": "http://gitlab.example.com/oliver/poignant-se/commit/fdd525130a91991648783608f9d55c359ed41abf"
|
||||
}
|
||||
],
|
||||
"event_name": "push",
|
||||
"message": null,
|
||||
"object_kind": "push",
|
||||
"project": {
|
||||
"avatar_url": null,
|
||||
"default_branch": "master",
|
||||
"description": "",
|
||||
"git_http_url": "http://gitlab.example.com/oliver/poignant-se.git",
|
||||
"git_ssh_url": "ssh://git@example.com/oliver/poignant-se.git",
|
||||
"homepage": "http://gitlab.example.com/oliver/poignant-se",
|
||||
"http_url": "http://gitlab.example.com/oliver/poignant-se.git",
|
||||
"name": "poignant-se",
|
||||
"namespace": "oliver",
|
||||
"path_with_namespace": "oliver/poignant-se",
|
||||
"ssh_url": "ssh://git@example.com/oliver/poignant-se.git",
|
||||
"url": "ssh://git@example.com/oliver/poignant-se.git",
|
||||
"visibility_level": 0,
|
||||
"web_url": "http://gitlab.example.com/oliver/poignant-se"
|
||||
},
|
||||
"project_id": 28,
|
||||
"ref": "refs/heads/master",
|
||||
"repository": {
|
||||
"description": "",
|
||||
"git_http_url": "http://gitlab.example.com/oliver/poignant-se.git",
|
||||
"git_ssh_url": "ssh://git@example.com/oliver/poignant-se.git",
|
||||
"homepage": "http://gitlab.example.com/oliver/poignant-se",
|
||||
"name": "poignant-se",
|
||||
"url": "ssh://git@example.com/oliver/poignant-se.git",
|
||||
"visibility_level": 0
|
||||
},
|
||||
"total_commits_count": 3,
|
||||
"user_avatar": "http://www.gravatar.com/avatar/bdf723d6a6f847ec075a6809aa4808fb?s=80&d=identicon",
|
||||
"user_email": "oliver@poignant.se",
|
||||
"user_id": 7,
|
||||
"user_name": "Oliver Poignant"
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"secret-token": "wrong-token"
|
||||
},
|
||||
"expected": {
|
||||
"status": 400
|
||||
},
|
||||
"headers": {
|
||||
"connection": "close",
|
||||
"content-length": "2384",
|
||||
"content-type": "application/json",
|
||||
"host": "local:8001",
|
||||
"x-gitlab-event": "Push Hook",
|
||||
"x-gitlab-token": "my-secret-token"
|
||||
},
|
||||
"payload": {
|
||||
"after": "d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c",
|
||||
"before": "4a2f7017bbba0719158102884b88d12d0f98359d",
|
||||
"checkout_sha": "d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c",
|
||||
"commits": [
|
||||
{
|
||||
"added": [
|
||||
"asjhadsjdsasssaasdsd",
|
||||
"npm-debug.log"
|
||||
],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c",
|
||||
"message": "fix\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2016-09-03T13:21:18+02:00",
|
||||
"url": "http://gitlab.local/oliver/poignant-se/commit/d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c"
|
||||
},
|
||||
{
|
||||
"added": [
|
||||
"asjhadsjdsasssaa"
|
||||
],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "7948359a6837d690cadb1d9cf10d503ba6e7d85f",
|
||||
"message": "fix\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2016-09-03T13:19:39+02:00",
|
||||
"url": "http://gitlab.local/oliver/poignant-se/commit/7948359a6837d690cadb1d9cf10d503ba6e7d85f"
|
||||
},
|
||||
{
|
||||
"added": [
|
||||
"asjhadsjdsasss"
|
||||
],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "4a2f7017bbba0719158102884b88d12d0f98359d",
|
||||
"message": "fix\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2016-09-03T13:17:43+02:00",
|
||||
"url": "http://gitlab.local/oliver/poignant-se/commit/4a2f7017bbba0719158102884b88d12d0f98359d"
|
||||
}
|
||||
],
|
||||
"event_name": "push",
|
||||
"message": null,
|
||||
"object_kind": "push",
|
||||
"project": {
|
||||
"avatar_url": null,
|
||||
"default_branch": "master",
|
||||
"description": "",
|
||||
"git_http_url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"git_ssh_url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"homepage": "http://gitlab.local/oliver/poignant-se",
|
||||
"http_url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"name": "poignant-se",
|
||||
"namespace": "oliver",
|
||||
"path_with_namespace": "oliver/poignant-se",
|
||||
"ssh_url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"visibility_level": 0,
|
||||
"web_url": "http://gitlab.local/oliver/poignant-se"
|
||||
},
|
||||
"project_id": 28,
|
||||
"ref": "refs/heads/master",
|
||||
"repository": {
|
||||
"description": "",
|
||||
"git_http_url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"git_ssh_url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"homepage": "http://gitlab.local/oliver/poignant-se",
|
||||
"name": "poignant-se",
|
||||
"url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"visibility_level": 0
|
||||
},
|
||||
"total_commits_count": 3,
|
||||
"user_avatar": "http://www.gravatar.com/avatar/bdf723d6a6f847ec075a6809aa4808fb?s=80&d=identicon",
|
||||
"user_email": "oliver@poignant.se",
|
||||
"user_id": 7,
|
||||
"user_name": "Oliver Poignant"
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
{
|
||||
"config": {
|
||||
"branch": "master",
|
||||
"deploy": "echo test!",
|
||||
"remote": "origin",
|
||||
"url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"secret-token": "my-secret-token"
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"data": [
|
||||
{
|
||||
"deploy": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"headers": {
|
||||
"connection": "close",
|
||||
"content-length": "2384",
|
||||
"content-type": "application/json",
|
||||
"host": "local:8001",
|
||||
"x-gitlab-event": "Push Hook",
|
||||
"x-gitlab-token": "my-secret-token"
|
||||
},
|
||||
"payload": {
|
||||
"after": "d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c",
|
||||
"before": "4a2f7017bbba0719158102884b88d12d0f98359d",
|
||||
"checkout_sha": "d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c",
|
||||
"commits": [
|
||||
{
|
||||
"added": [
|
||||
"asjhadsjdsasssaasdsd",
|
||||
"npm-debug.log"
|
||||
],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c",
|
||||
"message": "fix\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2016-09-03T13:21:18+02:00",
|
||||
"url": "http://gitlab.local/oliver/poignant-se/commit/d8f7e6c7a7bcdac418fac7aebb9e5c38aefca14c"
|
||||
},
|
||||
{
|
||||
"added": [
|
||||
"asjhadsjdsasssaa"
|
||||
],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "7948359a6837d690cadb1d9cf10d503ba6e7d85f",
|
||||
"message": "fix\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2016-09-03T13:19:39+02:00",
|
||||
"url": "http://gitlab.local/oliver/poignant-se/commit/7948359a6837d690cadb1d9cf10d503ba6e7d85f"
|
||||
},
|
||||
{
|
||||
"added": [
|
||||
"asjhadsjdsasss"
|
||||
],
|
||||
"author": {
|
||||
"email": "oliver@poignant.se",
|
||||
"name": "Oliver Poignant"
|
||||
},
|
||||
"id": "4a2f7017bbba0719158102884b88d12d0f98359d",
|
||||
"message": "fix\n",
|
||||
"modified": [],
|
||||
"removed": [],
|
||||
"timestamp": "2016-09-03T13:17:43+02:00",
|
||||
"url": "http://gitlab.local/oliver/poignant-se/commit/4a2f7017bbba0719158102884b88d12d0f98359d"
|
||||
}
|
||||
],
|
||||
"event_name": "push",
|
||||
"message": null,
|
||||
"object_kind": "push",
|
||||
"project": {
|
||||
"avatar_url": null,
|
||||
"default_branch": "master",
|
||||
"description": "",
|
||||
"git_http_url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"git_ssh_url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"homepage": "http://gitlab.local/oliver/poignant-se",
|
||||
"http_url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"name": "poignant-se",
|
||||
"namespace": "oliver",
|
||||
"path_with_namespace": "oliver/poignant-se",
|
||||
"ssh_url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"visibility_level": 0,
|
||||
"web_url": "http://gitlab.local/oliver/poignant-se"
|
||||
},
|
||||
"project_id": 28,
|
||||
"ref": "refs/heads/master",
|
||||
"repository": {
|
||||
"description": "",
|
||||
"git_http_url": "http://gitlab.local/oliver/poignant-se.git",
|
||||
"git_ssh_url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"homepage": "http://gitlab.local/oliver/poignant-se",
|
||||
"name": "poignant-se",
|
||||
"url": "ssh://git@local:8810/oliver/poignant-se.git",
|
||||
"visibility_level": 0
|
||||
},
|
||||
"total_commits_count": 3,
|
||||
"user_avatar": "http://www.gravatar.com/avatar/bdf723d6a6f847ec075a6809aa4808fb?s=80&d=identicon",
|
||||
"user_email": "oliver@poignant.se",
|
||||
"user_id": 7,
|
||||
"user_name": "Oliver Poignant"
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
class GitWrapper(object):
|
||||
|
||||
@staticmethod
|
||||
def pull(*args, **kwargs):
|
||||
"""Fake git pull"""
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def clone(*args, **kwargs):
|
||||
"""Fake git clone"""
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def deploy(*args, **kwargs):
|
||||
"""Fake deploy"""
|
||||
return 0
|
@ -0,0 +1,6 @@
|
||||
class ProcessWrapper():
|
||||
|
||||
@staticmethod
|
||||
def call(*args, **kwargs):
|
||||
"""Fake process call"""
|
||||
return 0
|
@ -0,0 +1,87 @@
|
||||
import unittest
|
||||
from utils import WebhookTestCaseBase
|
||||
|
||||
|
||||
class WebhookTestCase(WebhookTestCaseBase):
|
||||
|
||||
test_case = None
|
||||
test_name = None
|
||||
|
||||
def set_test_case(self, test_case, test_name):
|
||||
self.test_case = test_case
|
||||
self.test_name = test_name
|
||||
|
||||
def shortDescription(self):
|
||||
return self.test_name[:-15]
|
||||
|
||||
def runTest(self):
|
||||
import requests
|
||||
import json
|
||||
|
||||
# GAD configuration for this test case
|
||||
config = {
|
||||
'http-port': 0,
|
||||
'https-enabled': False,
|
||||
'wss-enabled': False,
|
||||
'web-ui-require-https': False,
|
||||
'intercept-stdout': False,
|
||||
'log-level': 'ERROR',
|
||||
'repositories': []
|
||||
}
|
||||
|
||||
if 'url' in self.test_case['config']:
|
||||
config['repositories'].append(self.test_case['config'])
|
||||
|
||||
# Start GAD instance
|
||||
self.start_gad(config)
|
||||
|
||||
# Send webhook request
|
||||
session = requests.session()
|
||||
response = requests.post('http://localhost:%s/' % self.gad_port(), data=json.dumps(self.test_case['payload']), headers=self.test_case['headers'])
|
||||
|
||||
# Verify response status
|
||||
self.assertEqual(response.status_code, self.test_case['expected']['status'])
|
||||
|
||||
# Compare results
|
||||
#if 'data' in self.test_case['expected']:
|
||||
# actual = json.dumps(response.json())
|
||||
# expected = json.dumps(self.test_case['expected']['data'])
|
||||
# self.assertEqual(actual, expected)
|
||||
|
||||
# Wait for GAD to handle the request
|
||||
self.await_gad()
|
||||
|
||||
def suite():
|
||||
import os
|
||||
import json
|
||||
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
# Look for test cases in samples dir
|
||||
samples_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'samples')
|
||||
for item in os.listdir(samples_dir):
|
||||
|
||||
if not item[-15:] == '.test-case.json':
|
||||
continue
|
||||
|
||||
file = open(os.path.join(samples_dir, item), 'r')
|
||||
test_case = json.loads(file.read())
|
||||
|
||||
t = WebhookTestCase()
|
||||
t.set_test_case(test_case, item)
|
||||
|
||||
suite.addTest(t)
|
||||
|
||||
return suite
|
||||
|
||||
def main():
|
||||
from unittest import TestResult
|
||||
#result = TestResult()
|
||||
#suite().run(result)
|
||||
#unittest.main()
|
||||
#print result
|
||||
s = suite()
|
||||
result = unittest.TextTestRunner(verbosity=2).run(s)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,118 @@
|
||||
import unittest
|
||||
|
||||
class WebhookTestCaseBase(unittest.TestCase):
|
||||
|
||||
thread = None
|
||||
|
||||
def setUp(self):
|
||||
import sys
|
||||
|
||||
# Use our custom importer to replace certain modules with stub modules to
|
||||
# enable testing of other parts of GAD
|
||||
sys.meta_path.append(StubImporter())
|
||||
|
||||
def start_gad(self, test_config):
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
|
||||
# Add repo root to sys path
|
||||
repo_root = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
|
||||
sys.path.insert(1, repo_root)
|
||||
|
||||
from gitautodeploy.cli.config import get_config_defaults, init_config
|
||||
|
||||
config = get_config_defaults()
|
||||
config.update(test_config)
|
||||
|
||||
init_config(config)
|
||||
|
||||
# Create a GAD instance
|
||||
self.thread = GADRunnerThread(config)
|
||||
|
||||
# Run GAD in a thread
|
||||
self.thread.start()
|
||||
|
||||
def gad_port(self):
|
||||
return self.thread.port
|
||||
|
||||
def await_gad(self):
|
||||
"""Waits for GAD and all it's threads to complete."""
|
||||
import threading
|
||||
|
||||
# Wait for GAD runner thread to finish
|
||||
self.thread.join()
|
||||
|
||||
# Wait for all request handler threads to finish
|
||||
main_thread = threading.currentThread()
|
||||
for current_thread in threading.enumerate():
|
||||
if current_thread == main_thread:
|
||||
continue
|
||||
# print "Waiting for thread %s to finish" % current_thread
|
||||
current_thread.join()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
class StubImporter(object):
|
||||
|
||||
overload_modules = ['gitautodeploy.wrappers.git', 'gitautodeploy.wrappers.process']
|
||||
|
||||
def find_module(self, full_name, package_path):
|
||||
|
||||
# Intervene when any wrapper module is imported
|
||||
if full_name in self.overload_modules:
|
||||
|
||||
# Return a loader
|
||||
return self
|
||||
|
||||
return None
|
||||
|
||||
def load_module(self, full_name):
|
||||
"""Load matching module from stubs package (test stub) instead of main gitautodeploy package."""
|
||||
import imp
|
||||
import sys
|
||||
import os
|
||||
|
||||
repo_root = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
|
||||
|
||||
module_name = full_name.split('.')[-1]
|
||||
module_info = imp.find_module(module_name, [repo_root+'/test/stubs'])
|
||||
module = imp.load_module(module_name, *module_info)
|
||||
sys.modules[module_name] = module
|
||||
|
||||
# print "returning module %s" % module_name
|
||||
|
||||
# module.history.append('WAH')
|
||||
|
||||
# Return a module
|
||||
return module
|
||||
|
||||
|
||||
import threading
|
||||
|
||||
class GADRunnerThread(threading.Thread):
|
||||
def __init__(self, config):
|
||||
super(GADRunnerThread, self).__init__()
|
||||
import sys
|
||||
import time
|
||||
|
||||
from gitautodeploy import GitAutoDeploy
|
||||
|
||||
self._app = GitAutoDeploy()
|
||||
self._app.setup(config)
|
||||
|
||||
# Setup HTTP server, but do not start serving
|
||||
self._app.serve_http(serve_forever=False)
|
||||
|
||||
# Store PID and port in thread instance
|
||||
self.pid = self._app._pid
|
||||
self.port = self._app._http_port
|
||||
|
||||
def run(self):
|
||||
self._app._http_server.handle_request()
|
||||
|
||||
def exit(self):
|
||||
self._app.stop()
|
||||
self._app.close()
|
@ -0,0 +1,12 @@
|
||||
// This is a custom Jest transformer turning style imports into empty objects.
|
||||
// http://facebook.github.io/jest/docs/tutorial-webpack.html
|
||||
|
||||
module.exports = {
|
||||
process() {
|
||||
return 'module.exports = {};';
|
||||
},
|
||||
getCacheKey(fileData, filename) {
|
||||
// The output is always the same.
|
||||
return 'cssTransform';
|
||||
},
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
const path = require('path');
|
||||
|
||||
// This is a custom Jest transformer turning file imports into filenames.
|
||||
// http://facebook.github.io/jest/docs/tutorial-webpack.html
|
||||
|
||||
module.exports = {
|
||||
process(src, filename) {
|
||||
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
|
||||
},
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
if (typeof Promise === 'undefined') {
|
||||
// Rejection tracking prevents a common issue where React gets into an
|
||||
// inconsistent state due to an error, but it gets swallowed by a Promise,
|
||||
// and the user has no idea what causes React's erratic future behavior.
|
||||
require('promise/lib/rejection-tracking').enable();
|
||||
window.Promise = require('promise/lib/es6-extensions.js');
|
||||
}
|
||||
|
||||
// fetch() polyfill for making API calls.
|
||||
require('whatwg-fetch');
|
||||
|
||||
// Object.assign() is commonly used with React.
|
||||
// It will use the native implementation if it's present and isn't buggy.
|
||||
Object.assign = require('object-assign');
|
@ -0,0 +1,214 @@
|
||||
var autoprefixer = require('autoprefixer');
|
||||
var webpack = require('webpack');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
||||
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||
var getClientEnvironment = require('./env');
|
||||
var paths = require('./paths');
|
||||
|
||||
var __DEV__ = true;
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// In development, we always serve from the root. This makes config easier.
|
||||
var publicPath = '/';
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
|
||||
var publicUrl = '';
|
||||
// Get environment variables to inject into our app.
|
||||
var env = getClientEnvironment(publicUrl);
|
||||
|
||||
// This is the development configuration.
|
||||
// It is focused on developer experience and fast rebuilds.
|
||||
// The production configuration is different and lives in a separate file.
|
||||
module.exports = {
|
||||
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
|
||||
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
|
||||
devtool: 'cheap-module-source-map',
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
|
||||
entry: [
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// We ship a few polyfills by default:
|
||||
require.resolve('./polyfills'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
],
|
||||
output: {
|
||||
// Next line is not used in dev but WebpackDevServer crashes without it:
|
||||
path: paths.appBuild,
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: true,
|
||||
// This does not produce a real file. It's just the virtual path that is
|
||||
// served by WebpackDevServer in development. This is the JS bundle
|
||||
// containing code from all our entry points, and the Webpack runtime.
|
||||
filename: 'static/js/bundle.js',
|
||||
// This is the URL that app is served from. We use "/" in development.
|
||||
publicPath: publicPath
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We read `NODE_PATH` environment variable in `paths.js` and pass paths here.
|
||||
// We use `fallback` instead of `root` because we want `node_modules` to "win"
|
||||
// if there any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
fallback: paths.nodePaths,
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/290
|
||||
extensions: ['.js', '.json', '.jsx', ''],
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web'
|
||||
}
|
||||
},
|
||||
|
||||
module: {
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
preLoaders: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
loader: 'eslint',
|
||||
include: paths.appSrc,
|
||||
}
|
||||
],
|
||||
loaders: [
|
||||
// Default loader: load all assets that are not handled
|
||||
// by other loaders with the url loader.
|
||||
// Note: This list needs to be updated with every change of extensions
|
||||
// the other loaders match.
|
||||
// E.g., when adding a loader for a new supported file extension,
|
||||
// we need to add the supported extension to this loader too.
|
||||
// Add one new line in `exclude` for each loader.
|
||||
//
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: /\.scss$/,
|
||||
include: paths.appSrc,
|
||||
loaders: ["style", "css", "sass"]
|
||||
},
|
||||
{
|
||||
exclude: [
|
||||
/\.html$/,
|
||||
/\.(js|jsx)$/,
|
||||
/\.css$/,
|
||||
/\.json$/,
|
||||
/\.svg$/,
|
||||
/\.scss$/
|
||||
],
|
||||
loader: 'url',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'static/media/[name].[hash:8].[ext]'
|
||||
}
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
include: paths.appSrc,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true
|
||||
}
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use a plugin to extract that CSS to a file, but
|
||||
// in development "style" loader enables hot editing of CSS.
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: 'style!css?importLoaders=1!postcss'
|
||||
},
|
||||
// JSON is not enabled by default in Webpack but both Node and Browserify
|
||||
// allow it implicitly so we also enable it.
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
},
|
||||
// "file" loader for svg
|
||||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'file',
|
||||
query: {
|
||||
name: 'static/media/[name].[hash:8].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// We use PostCSS for autoprefixing only.
|
||||
postcss: function() {
|
||||
return [
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
'>1%',
|
||||
'last 4 versions',
|
||||
'Firefox ESR',
|
||||
'not ie < 9', // React doesn't support IE8 anyway
|
||||
]
|
||||
}),
|
||||
];
|
||||
},
|
||||
plugins: [
|
||||
// Makes the public URL available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin({
|
||||
PUBLIC_URL: publicUrl
|
||||
}),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
}),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
|
||||
new webpack.DefinePlugin(env),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/240
|
||||
new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/186
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty'
|
||||
}
|
||||
};
|
@ -0,0 +1,252 @@
|
||||
var autoprefixer = require('autoprefixer');
|
||||
var webpack = require('webpack');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
var ManifestPlugin = require('webpack-manifest-plugin');
|
||||
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
var url = require('url');
|
||||
var paths = require('./paths');
|
||||
var getClientEnvironment = require('./env');
|
||||
|
||||
|
||||
|
||||
function ensureSlash(path, needsSlash) {
|
||||
var hasSlash = path.endsWith('/');
|
||||
if (hasSlash && !needsSlash) {
|
||||
return path.substr(path, path.length - 1);
|
||||
} else if (!hasSlash && needsSlash) {
|
||||
return path + '/';
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
// We use "homepage" field to infer "public path" at which the app is served.
|
||||
// Webpack needs to know it to put the right <script> hrefs into HTML even in
|
||||
// single-page apps that may serve index.html for nested URLs like /todos/42.
|
||||
// We can't use a relative path in HTML because we don't want to load something
|
||||
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
|
||||
var homepagePath = require(paths.appPackageJson).homepage;
|
||||
var homepagePathname = homepagePath ? url.parse(homepagePath).pathname : '/';
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
var publicPath = ensureSlash(homepagePathname, true);
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
|
||||
var publicUrl = ensureSlash(homepagePathname, false);
|
||||
// Get environment variables to inject into our app.
|
||||
var env = getClientEnvironment(publicUrl);
|
||||
|
||||
// Assert this just to be safe.
|
||||
// Development builds of React are slow and not intended for production.
|
||||
if (env['process.env'].NODE_ENV !== '"production"') {
|
||||
throw new Error('Production builds must have NODE_ENV=production.');
|
||||
}
|
||||
|
||||
// This is the production configuration.
|
||||
// It compiles slowly and is focused on producing a fast and minimal bundle.
|
||||
// The development configuration is different and lives in a separate file.
|
||||
module.exports = {
|
||||
// Don't attempt to continue if there are any errors.
|
||||
bail: true,
|
||||
// We generate sourcemaps in production. This is slow but gives good results.
|
||||
// You can exclude the *.map files from the build during deployment.
|
||||
devtool: 'source-map',
|
||||
// In production, we only want to load the polyfills and the app code.
|
||||
entry: [
|
||||
require.resolve('./polyfills'),
|
||||
paths.appIndexJs
|
||||
],
|
||||
output: {
|
||||
// The build folder.
|
||||
path: paths.appBuild,
|
||||
// Generated JS file names (with nested folders).
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// We don't currently advertise code splitting but Webpack supports it.
|
||||
filename: 'static/js/[name].[chunkhash:8].js',
|
||||
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
publicPath: publicPath
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We read `NODE_PATH` environment variable in `paths.js` and pass paths here.
|
||||
// We use `fallback` instead of `root` because we want `node_modules` to "win"
|
||||
// if there any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
fallback: paths.nodePaths,
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/290
|
||||
extensions: ['.js', '.json', '.jsx', ''],
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web'
|
||||
}
|
||||
},
|
||||
|
||||
module: {
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
preLoaders: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
loader: 'eslint',
|
||||
include: paths.appSrc
|
||||
}
|
||||
],
|
||||
loaders: [
|
||||
// Default loader: load all assets that are not handled
|
||||
// by other loaders with the url loader.
|
||||
// Note: This list needs to be updated with every change of extensions
|
||||
// the other loaders match.
|
||||
// E.g., when adding a loader for a new supported file extension,
|
||||
// we need to add the supported extension to this loader too.
|
||||
// Add one new line in `exclude` for each loader.
|
||||
//
|
||||
// "file" loader makes sure those assets end up in the `build` folder.
|
||||
// When you `import` an asset, you get its filename.
|
||||
// "url" loader works just like "file" loader but it also embeds
|
||||
// assets smaller than specified size as data URLs to avoid requests.
|
||||
{
|
||||
test: /\.scss$/,
|
||||
include: paths.appSrc,
|
||||
loaders: ["style", "css", "sass"]
|
||||
},
|
||||
{
|
||||
exclude: [
|
||||
/\.html$/,
|
||||
/\.(js|jsx)$/,
|
||||
/\.css$/,
|
||||
/\.json$/,
|
||||
/\.svg$/,
|
||||
/\.scss$/
|
||||
],
|
||||
loader: 'url',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'static/media/[name].[hash:8].[ext]'
|
||||
}
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
include: paths.appSrc,
|
||||
loader: 'babel',
|
||||
|
||||
},
|
||||
// The notation here is somewhat confusing.
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader normally turns CSS into JS modules injecting <style>,
|
||||
// but unlike in development configuration, we do something different.
|
||||
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders
|
||||
// (second argument), then grabs the result CSS and puts it into a
|
||||
// separate file in our build process. This way we actually ship
|
||||
// a single CSS file in production instead of JS code injecting <style>
|
||||
// tags. If you use code splitting, however, any async bundles will still
|
||||
// use the "style" loader inside the async code so CSS from them won't be
|
||||
// in the main CSS file.
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: ExtractTextPlugin.extract('style', 'css?importLoaders=1!postcss')
|
||||
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
|
||||
},
|
||||
// JSON is not enabled by default in Webpack but both Node and Browserify
|
||||
// allow it implicitly so we also enable it.
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
},
|
||||
// "file" loader for svg
|
||||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'file',
|
||||
query: {
|
||||
name: 'static/media/[name].[hash:8].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// We use PostCSS for autoprefixing only.
|
||||
postcss: function() {
|
||||
return [
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
'>1%',
|
||||
'last 4 versions',
|
||||
'Firefox ESR',
|
||||
'not ie < 9', // React doesn't support IE8 anyway
|
||||
]
|
||||
}),
|
||||
];
|
||||
},
|
||||
plugins: [
|
||||
// Makes the public URL available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In production, it will be an empty string unless you specify "homepage"
|
||||
// in `package.json`, in which case it will be the pathname of that URL.
|
||||
new InterpolateHtmlPlugin({
|
||||
PUBLIC_URL: publicUrl
|
||||
}),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true
|
||||
}
|
||||
}),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
|
||||
// It is absolutely essential that NODE_ENV was set to production here.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env),
|
||||
// This helps ensure the builds are consistent if source hasn't changed:
|
||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||
// Try to dedupe duplicated modules, if any:
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
// Minify the code.
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
screw_ie8: true, // React doesn't support IE8
|
||||
warnings: false
|
||||
},
|
||||
mangle: {
|
||||
screw_ie8: true
|
||||
},
|
||||
output: {
|
||||
comments: false,
|
||||
screw_ie8: true
|
||||
}
|
||||
}),
|
||||
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
|
||||
new ExtractTextPlugin('static/css/[name].[contenthash:8].css'),
|
||||
// Generate a manifest file which contains a mapping of all asset filenames
|
||||
// to their corresponding output file so that tools can pick it up without
|
||||
// having to parse `index.html`.
|
||||
new ManifestPlugin({
|
||||
fileName: 'asset-manifest.json'
|
||||
})
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty'
|
||||
}
|
||||
};
|
@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "webui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.5.1",
|
||||
"babel-core": "6.17.0",
|
||||
"babel-eslint": "7.1.1",
|
||||
"babel-jest": "17.0.2",
|
||||
"babel-loader": "6.2.7",
|
||||
"babel-preset-react-app": "^2.0.1",
|
||||
"case-sensitive-paths-webpack-plugin": "1.1.4",
|
||||
"chalk": "1.1.3",
|
||||
"connect-history-api-fallback": "1.3.0",
|
||||
"cross-spawn": "4.0.2",
|
||||
"css-loader": "0.26.0",
|
||||
"detect-port": "1.0.1",
|
||||
"dotenv": "2.0.0",
|
||||
"eslint": "3.8.1",
|
||||
"eslint-config-react-app": "^0.5.0",
|
||||
"eslint-loader": "1.6.0",
|
||||
"eslint-plugin-flowtype": "2.21.0",
|
||||
"eslint-plugin-import": "2.0.1",
|
||||
"eslint-plugin-jsx-a11y": "2.2.3",
|
||||
"eslint-plugin-react": "6.4.1",
|
||||
"extract-text-webpack-plugin": "1.0.1",
|
||||
"file-loader": "0.9.0",
|
||||
"filesize": "3.3.0",
|
||||
"fs-extra": "0.30.0",
|
||||
"gzip-size": "3.0.0",
|
||||
"html-webpack-plugin": "2.24.0",
|
||||
"http-proxy-middleware": "0.17.2",
|
||||
"jest": "17.0.2",
|
||||
"json-loader": "0.5.4",
|
||||
"node-sass": "^4.1.1",
|
||||
"object-assign": "4.1.0",
|
||||
"path-exists": "2.1.0",
|
||||
"postcss-loader": "1.0.0",
|
||||
"promise": "7.1.1",
|
||||
"react-dev-utils": "^0.4.2",
|
||||
"recursive-readdir": "2.1.0",
|
||||
"sass-loader": "^4.1.1",
|
||||
"strip-ansi": "3.0.1",
|
||||
"style-loader": "0.13.1",
|
||||
"url-loader": "0.5.7",
|
||||
"webpack": "1.14.0",
|
||||
"webpack-dev-server": "1.16.2",
|
||||
"webpack-manifest-plugin": "1.1.0",
|
||||
"whatwg-fetch": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.15.3",
|
||||
"moment": "^2.17.1",
|
||||
"react": "^15.4.1",
|
||||
"react-dom": "^15.4.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "node scripts/test.js --env=jsdom"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx}"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/config/polyfills.js"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"<rootDir>[/\\\\](build|docs|node_modules)[/\\\\]"
|
||||
],
|
||||
"testEnvironment": "node",
|
||||
"testURL": "http://localhost",
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx)$": "<rootDir>/node_modules/babel-jest",
|
||||
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||
"^(?!.*\\.(js|jsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^react-native$": "react-native-web"
|
||||
}
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tag above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Git-Auto-Deploy Web UI</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i" rel="stylesheet">
|
||||
<link rel="stylesheet" href="//cdn.materialdesignicons.com/1.7.22/css/materialdesignicons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start`.
|
||||
To create a production bundle, use `npm run build`.
|
||||
-->
|
||||
<!--
|
||||
<style>
|
||||
svg { border: 1px solid red;}
|
||||
</Style>
|
||||
|
||||
<svg width="100px" height="300px" viewBox="0 0 10 30">
|
||||
<path fill="#7AA20D" d="
|
||||
M0,0
|
||||
c0,5,0,5,5,10
|
||||
c6,6,6,4,0,10
|
||||
c-5,5,-5,5,-5,10
|
||||
" />
|
||||
</svg>
|
||||
|
||||
<svg width="120px" height="340px" viewBox="0 0 12 34">
|
||||
<path fill="#7AA20D" d="
|
||||
M0,0
|
||||
c0,5,0,5,5,10
|
||||
l2,2
|
||||
c6,6,6,4,0,10
|
||||
l-2,2
|
||||
c-5,5,-5,5,-5,10
|
||||
" />
|
||||
</svg>
|
||||
|
||||
<svg width="150px" height="400px" viewBox="0 0 15 40">
|
||||
<path fill="#7AA20D" d="
|
||||
M0,0
|
||||
c0,5,0,5,5,10
|
||||
l5,5
|
||||
c6,6,6,4,0,10
|
||||
l-5,5
|
||||
c-5,5,-5,5,-5,10
|
||||
" />
|
||||
</svg>
|
||||
|
||||
|
||||
<svg width="200px" height="500px" viewBox="0 0 20 50">
|
||||
<path fill="#7AA20D" d="
|
||||
M0,0
|
||||
c0,5,0,5,5,10
|
||||
l10,10
|
||||
c6,6,6,4,0,10
|
||||
l-10,10
|
||||
c-5,5,-5,5,-5,10
|
||||
" />
|
||||
</svg>
|
||||
|
||||
<br>
|
||||
<svg width="150px" height="700px" viewBox="0 0 15 70">
|
||||
<path fill="#7AA20D" d="
|
||||
M0,0
|
||||
c0,20,0,20,10,30
|
||||
c5,5,5,5,0,10
|
||||
c-10,10,-10,10,-10,30z
|
||||
" />
|
||||
</svg>
|
||||
|
||||
<svg width="258px" height="384px">
|
||||
<path fill="#7AA20D" stroke="#7AA20D" stroke-width="2" stroke-linejoin="round" d="
|
||||
M 100,100
|
||||
l 0,10
|
||||
c 0,0 -5,20 20,20
|
||||
l 0,10
|
||||
c 0,0 -20,0 -20,20
|
||||
l 0,50
|
||||
l -50,0
|
||||
l 0,-110z
|
||||
" />
|
||||
</svg>
|
||||
<svg width="258px" height="384px">
|
||||
<path fill="#7AA20D" stroke="#7AA20D" stroke-width="9" stroke-linejoin="round" d="
|
||||
M248.761, 92
|
||||
c0, 9.801, -7.93, 17.731, -17.71, 17.731
|
||||
c-0.319, 0, -0.617, 0, -0.935, -0.021
|
||||
c-10.035, 37.291, -51.174, 65.206, -100.414, 65.206
|
||||
c-49.261, 0, -90.443, -27.979, -100.435, -65.334
|
||||
c-0.765, 0.106, -1.531, 0.149, -2.317, 0.149
|
||||
c-9.78, 0, -17.71, -7.93, -17.71, -17.731
|
||||
c0-9.78, 7.93, -17.71, 17.71, -17.71
|
||||
c0.787, 0, 1.552, 0.042, 2.317, 0.149
|
||||
C39.238, 37.084, 80.419, 9.083, 129.702, 9.083
|
||||
c49.24, 0, 90.379, 27.937, 100.414, 65.228
|
||||
h0.021
|
||||
c0.298, -0.021, 0.617, -0.021, 0.914, -0.021
|
||||
C240.831, 74.29, 248.761, 82.22, 248.761,
|
||||
92z" />
|
||||
</svg>
|
||||
-->
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,224 @@
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
// Load environment variables from .env file. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set.
|
||||
// https://github.com/motdotla/dotenv
|
||||
require('dotenv').config({silent: true});
|
||||
|
||||
var chalk = require('chalk');
|
||||
var fs = require('fs-extra');
|
||||
var path = require('path');
|
||||
var pathExists = require('path-exists');
|
||||
var filesize = require('filesize');
|
||||
var gzipSize = require('gzip-size').sync;
|
||||
var webpack = require('webpack');
|
||||
var config = require('../config/webpack.config.prod');
|
||||
var paths = require('../config/paths');
|
||||
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
var recursive = require('recursive-readdir');
|
||||
var stripAnsi = require('strip-ansi');
|
||||
|
||||
var useYarn = pathExists.sync(paths.yarnLockFile);
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Input: /User/dan/app/build/static/js/main.82be8.js
|
||||
// Output: /static/js/main.js
|
||||
function removeFileNameHash(fileName) {
|
||||
return fileName
|
||||
.replace(paths.appBuild, '')
|
||||
.replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
|
||||
}
|
||||
|
||||
// Input: 1024, 2048
|
||||
// Output: "(+1 KB)"
|
||||
function getDifferenceLabel(currentSize, previousSize) {
|
||||
var FIFTY_KILOBYTES = 1024 * 50;
|
||||
var difference = currentSize - previousSize;
|
||||
var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
|
||||
if (difference >= FIFTY_KILOBYTES) {
|
||||
return chalk.red('+' + fileSize);
|
||||
} else if (difference < FIFTY_KILOBYTES && difference > 0) {
|
||||
return chalk.yellow('+' + fileSize);
|
||||
} else if (difference < 0) {
|
||||
return chalk.green(fileSize);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
recursive(paths.appBuild, (err, fileNames) => {
|
||||
var previousSizeMap = (fileNames || [])
|
||||
.filter(fileName => /\.(js|css)$/.test(fileName))
|
||||
.reduce((memo, fileName) => {
|
||||
var contents = fs.readFileSync(fileName);
|
||||
var key = removeFileNameHash(fileName);
|
||||
memo[key] = gzipSize(contents);
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
fs.emptyDirSync(paths.appBuild);
|
||||
|
||||
// Start the webpack build
|
||||
build(previousSizeMap);
|
||||
|
||||
// Merge with the public folder
|
||||
copyPublicFolder();
|
||||
});
|
||||
|
||||
// Print a detailed summary of build files.
|
||||
function printFileSizes(stats, previousSizeMap) {
|
||||
var assets = stats.toJson().assets
|
||||
.filter(asset => /\.(js|css)$/.test(asset.name))
|
||||
.map(asset => {
|
||||
var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
|
||||
var size = gzipSize(fileContents);
|
||||
var previousSize = previousSizeMap[removeFileNameHash(asset.name)];
|
||||
var difference = getDifferenceLabel(size, previousSize);
|
||||
return {
|
||||
folder: path.join('build', path.dirname(asset.name)),
|
||||
name: path.basename(asset.name),
|
||||
size: size,
|
||||
sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '')
|
||||
};
|
||||
});
|
||||
assets.sort((a, b) => b.size - a.size);
|
||||
var longestSizeLabelLength = Math.max.apply(null,
|
||||
assets.map(a => stripAnsi(a.sizeLabel).length)
|
||||
);
|
||||
assets.forEach(asset => {
|
||||
var sizeLabel = asset.sizeLabel;
|
||||
var sizeLength = stripAnsi(sizeLabel).length;
|
||||
if (sizeLength < longestSizeLabelLength) {
|
||||
var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
|
||||
sizeLabel += rightPadding;
|
||||
}
|
||||
console.log(
|
||||
' ' + sizeLabel +
|
||||
' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Print out errors
|
||||
function printErrors(summary, errors) {
|
||||
console.log(chalk.red(summary));
|
||||
console.log();
|
||||
errors.forEach(err => {
|
||||
console.log(err.message || err);
|
||||
console.log();
|
||||
});
|
||||
}
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousSizeMap) {
|
||||
console.log('Creating an optimized production build...');
|
||||
webpack(config).run((err, stats) => {
|
||||
if (err) {
|
||||
printErrors('Failed to compile.', [err]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (stats.compilation.errors.length) {
|
||||
printErrors('Failed to compile.', stats.compilation.errors);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.env.CI && stats.compilation.warnings.length) {
|
||||
printErrors('Failed to compile.', stats.compilation.warnings);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.green('Compiled successfully.'));
|
||||
console.log();
|
||||
|
||||
console.log('File sizes after gzip:');
|
||||
console.log();
|
||||
printFileSizes(stats, previousSizeMap);
|
||||
console.log();
|
||||
|
||||
var openCommand = process.platform === 'win32' ? 'start' : 'open';
|
||||
var appPackage = require(paths.appPackageJson);
|
||||
var homepagePath = appPackage.homepage;
|
||||
var publicPath = config.output.publicPath;
|
||||
if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) {
|
||||
// "homepage": "http://user.github.io/project"
|
||||
console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
|
||||
console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
|
||||
console.log();
|
||||
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
|
||||
console.log('To publish it at ' + chalk.green(homepagePath) + ', run:');
|
||||
// If script deploy has been added to package.json, skip the instructions
|
||||
if (typeof appPackage.scripts.deploy === 'undefined') {
|
||||
console.log();
|
||||
if (useYarn) {
|
||||
console.log(' ' + chalk.cyan('yarn') + ' add --dev gh-pages');
|
||||
} else {
|
||||
console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages');
|
||||
}
|
||||
console.log();
|
||||
console.log('Add the following script in your ' + chalk.cyan('package.json') + '.');
|
||||
console.log();
|
||||
console.log(' ' + chalk.dim('// ...'));
|
||||
console.log(' ' + chalk.yellow('"scripts"') + ': {');
|
||||
console.log(' ' + chalk.dim('// ...'));
|
||||
console.log(' ' + chalk.yellow('"deploy"') + ': ' + chalk.yellow('"npm run build&&gh-pages -d build"'));
|
||||
console.log(' }');
|
||||
console.log();
|
||||
console.log('Then run:');
|
||||
}
|
||||
console.log();
|
||||
console.log(' ' + chalk.cyan(useYarn ? 'yarn' : 'npm') + ' run deploy');
|
||||
console.log();
|
||||
} else if (publicPath !== '/') {
|
||||
// "homepage": "http://mywebsite.com/project"
|
||||
console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
|
||||
console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
|
||||
console.log();
|
||||
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
|
||||
console.log();
|
||||
} else {
|
||||
// no homepage or "homepage": "http://mywebsite.com"
|
||||
console.log('The project was built assuming it is hosted at the server root.');
|
||||
if (homepagePath) {
|
||||
// "homepage": "http://mywebsite.com"
|
||||
console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
|
||||
console.log();
|
||||
} else {
|
||||
// no homepage
|
||||
console.log('To override this, specify the ' + chalk.green('homepage') + ' in your ' + chalk.cyan('package.json') + '.');
|
||||
console.log('For example, add this to build it for GitHub Pages:')
|
||||
console.log();
|
||||
console.log(' ' + chalk.green('"homepage"') + chalk.cyan(': ') + chalk.green('"http://myname.github.io/myapp"') + chalk.cyan(','));
|
||||
console.log();
|
||||
}
|
||||
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
|
||||
console.log('You may also serve it locally with a static server:')
|
||||
console.log();
|
||||
if (useYarn) {
|
||||
console.log(' ' + chalk.cyan('yarn') + ' global add pushstate-server');
|
||||
} else {
|
||||
console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server');
|
||||
}
|
||||
console.log(' ' + chalk.cyan('pushstate-server') + ' build');
|
||||
console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000');
|
||||
console.log();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml
|
||||
});
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PUBLIC_URL = '';
|
||||
|
||||
// Load environment variables from .env file. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set.
|
||||
// https://github.com/motdotla/dotenv
|
||||
require('dotenv').config({silent: true});
|
||||
|
||||
const jest = require('jest');
|
||||
const argv = process.argv.slice(2);
|
||||
|
||||
// Watch unless on CI or in coverage mode
|
||||
if (!process.env.CI && argv.indexOf('--coverage') < 0) {
|
||||
argv.push('--watch');
|
||||
}
|
||||
|
||||
// A temporary hack to clear terminal correctly.
|
||||
// You can remove this after updating to Jest 18 when it's out.
|
||||
// https://github.com/facebook/jest/pull/2230
|
||||
var realWrite = process.stdout.write;
|
||||
var CLEAR = process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H';
|
||||
process.stdout.write = function(chunk, encoding, callback) {
|
||||
if (chunk === '\x1B[2J\x1B[H') {
|
||||
chunk = CLEAR;
|
||||
}
|
||||
return realWrite.call(this, chunk, encoding, callback);
|
||||
};
|
||||
|
||||
|
||||
jest.run(argv);
|
@ -0,0 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import './App.scss';
|
||||
import Timeline from './Timeline';
|
||||
import Navigation from './Navigation';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
/*
|
||||
if(window.location.protocol !== "https:" && process.env.NODE_ENV !== 'development') {
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="insecure-scheme">The Web UI is not available over HTTP. To use it; enable SSL, restart GAD and browse to this page using HTTPS.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
*/
|
||||
return (
|
||||
<div className="App">
|
||||
<Navigation />
|
||||
<Timeline />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue