├── tests ├── test-wsuwp-deployment.php ├── bootstrap.php ├── test-plugin-payload.txt └── test-theme-payload.txt ├── phpunit.xml ├── .gitignore ├── package.json ├── composer.json ├── wsuwp-deployment.php ├── phpcs.ruleset.xml ├── Gruntfile.js ├── .travis.yml ├── bin └── install-wp-tests.sh ├── README.md ├── includes ├── wsuwp-deployment.php └── class-wsuwp-deployment.php ├── deploy-prod.sh └── LICENSE /tests/test-wsuwp-deployment.php: -------------------------------------------------------------------------------- 1 | assertTrue( true ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Because OSX 2 | .DS_Store 3 | 4 | # Ignore Sublime project files. 5 | *.sublime* 6 | 7 | # Ignore Vagrant instance files. 8 | .vagrant 9 | 10 | # Ignore PHPStorm project files. 11 | /.idea 12 | 13 | # Ignore node modules created by NPM 14 | /node_modules 15 | 16 | # Ignore log file created during deployment. 17 | /prod-deployments.log 18 | 19 | # Ignore files added with composer. 20 | /vendor 21 | composer.lock 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wsuwp-deployment", 3 | "version": "2.1.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/washingtonstateuniversity/wsuwp-deployment" 7 | }, 8 | "author": "washingtonstateuniversity", 9 | "license": "GPL-2.0+", 10 | "devDependencies": { 11 | "grunt": "~1.0.1", 12 | "grunt-contrib-jshint": "^1.0.0", 13 | "grunt-jscs": "^3.0.1", 14 | "grunt-phpcs": "^0.4.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | . 18 | 19 | Sniffs for PHP coding standards used by WSUWP Plugins 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ) { 2 | grunt.initConfig( { 3 | pkg: grunt.file.readJSON( "package.json" ), 4 | 5 | phpcs: { 6 | plugin: { 7 | src: [ "./*.php", "./includes/*.php" ] 8 | }, 9 | options: { 10 | bin: "vendor/bin/phpcs --extensions=php --ignore=\"*/vendor/*,*/node_modules/*\"", 11 | standard: "phpcs.ruleset.xml" 12 | } 13 | }, 14 | 15 | jscs: { 16 | scripts: { 17 | src: [ "Gruntfile.js" ], 18 | options: { 19 | preset: "jquery", 20 | requireCamelCaseOrUpperCaseIdentifiers: false, // We rely on name_name too much to change them all. 21 | maximumLineLength: 250 22 | } 23 | } 24 | }, 25 | 26 | jshint: { 27 | grunt_script: { 28 | src: [ "Gruntfile.js" ], 29 | options: { 30 | curly: true, 31 | eqeqeq: true, 32 | noarg: true, 33 | quotmark: "double", 34 | undef: true, 35 | unused: false, 36 | node: true // Define globals available when running in Node. 37 | } 38 | } 39 | } 40 | } ); 41 | 42 | grunt.loadNpmTasks( "grunt-jscs" ); 43 | grunt.loadNpmTasks( "grunt-contrib-jshint" ); 44 | grunt.loadNpmTasks( "grunt-phpcs" ); 45 | 46 | // Default task(s). 47 | grunt.registerTask( "default", [ "phpcs", "jscs", "jshint" ] ); 48 | }; 49 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | 4 | # Cache some data across builds for performance. 5 | cache: 6 | apt: true 7 | directories: 8 | - node_modules 9 | 10 | # Set the default environment. 11 | env: 12 | global: 13 | - WP_TRAVISCI=travis:phpunit 14 | - WP_VERSION=latest 15 | - WP_MULTISITE=1 16 | 17 | notifications: 18 | email: 19 | on_success: never 20 | on_failure: change 21 | slack: 22 | on_pull_requests: true 23 | on_success: change 24 | on_failure: always 25 | on_start: never 26 | on_cancel: always 27 | rooms: 28 | - wsu-ucomm:n2TLZRJd84rMOMbkKthSEMgS 29 | 30 | branches: 31 | only: 32 | - master 33 | 34 | matrix: 35 | include: 36 | - php: 5.6 37 | - php: 7.0 38 | - php: 7.0 39 | env: WP_MULTISITE=0 40 | - php: 7.0 41 | env: WP_VERSION=nightly 42 | - php: 7.0 43 | env: WP_TRAVISCI=travis:grunt 44 | 45 | before_script: 46 | - | 47 | # Remove Xdebug for a huge performance increase, but not from nightly or hhvm: 48 | stable='^[0-9\.]+$' 49 | if [[ "$TRAVIS_PHP_VERSION" =~ $stable ]]; then 50 | phpenv config-rm xdebug.ini 51 | fi 52 | - | 53 | # Export Composer's global bin dir to PATH, but not on PHP 5.2: 54 | if [[ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]]; then 55 | composer config --list --global 56 | export PATH=`composer config --list --global | grep '\[home\]' | { read a; echo "${a#* }/vendor/bin:$PATH"; }` 57 | fi 58 | - | 59 | # Install the specified version of PHPUnit depending on the PHP version: 60 | if [[ "$WP_TRAVISCI" == "travis:phpunit" ]]; then 61 | case "$TRAVIS_PHP_VERSION" in 62 | 7.1|7.0|hhvm|nightly) 63 | echo "Using PHPUnit 5.7" 64 | composer global require "phpunit/phpunit=5.7.*" 65 | ;; 66 | 5.6|5.5|5.4|5.3) 67 | echo "Using PHPUnit 4.8" 68 | composer global require "phpunit/phpunit=4.8.*" 69 | ;; 70 | *) 71 | echo "No PHPUnit version handling for PHP version $TRAVIS_PHP_VERSION" 72 | exit 1 73 | ;; 74 | esac 75 | bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION 76 | fi 77 | - | 78 | if [[ "$WP_TRAVISCI" == "travis:grunt" ]]; then 79 | composer install 80 | fi 81 | - npm --version 82 | - node --version 83 | - nvm install stable 84 | - npm install -g grunt-cli 85 | - npm install 86 | - npm prune 87 | - mysql --version 88 | - phpenv versions 89 | - php --version 90 | - npm --version 91 | - node --version 92 | 93 | script: 94 | - | 95 | if [[ "$WP_TRAVISCI" == "travis:phpunit" ]] ; then 96 | phpunit --version 97 | phpunit 98 | fi 99 | - | 100 | if [[ "$WP_TRAVISCI" == "travis:grunt" ]] ; then 101 | grunt --version 102 | grunt default 103 | fi 104 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | 14 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} 15 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} 16 | 17 | download() { 18 | if [ `which curl` ]; then 19 | curl -s "$1" > "$2"; 20 | elif [ `which wget` ]; then 21 | wget -nv -O "$2" "$1" 22 | fi 23 | } 24 | 25 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then 26 | WP_TESTS_TAG="tags/$WP_VERSION" 27 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 28 | WP_TESTS_TAG="trunk" 29 | else 30 | # http serves a single offer, whereas https serves multiple. we only want one 31 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 32 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 33 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 34 | if [[ -z "$LATEST_VERSION" ]]; then 35 | echo "Latest WordPress version could not be found" 36 | exit 1 37 | fi 38 | WP_TESTS_TAG="tags/$LATEST_VERSION" 39 | fi 40 | 41 | set -ex 42 | 43 | install_wp() { 44 | 45 | if [ -d $WP_CORE_DIR ]; then 46 | return; 47 | fi 48 | 49 | mkdir -p $WP_CORE_DIR 50 | 51 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 52 | mkdir -p /tmp/wordpress-nightly 53 | download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip 54 | unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ 55 | mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR 56 | else 57 | if [ $WP_VERSION == 'latest' ]; then 58 | local ARCHIVE_NAME='latest' 59 | else 60 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 61 | fi 62 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz 63 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 64 | fi 65 | 66 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 67 | } 68 | 69 | install_test_suite() { 70 | # portable in-place argument for both GNU sed and Mac OSX sed 71 | if [[ $(uname -s) == 'Darwin' ]]; then 72 | local ioption='-i .bak' 73 | else 74 | local ioption='-i' 75 | fi 76 | 77 | # set up testing suite if it doesn't yet exist 78 | if [ ! -d $WP_TESTS_DIR ]; then 79 | # set up testing suite 80 | mkdir -p $WP_TESTS_DIR 81 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 82 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 83 | fi 84 | 85 | cd $WP_TESTS_DIR 86 | 87 | if [ ! -f wp-tests-config.php ]; then 88 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 89 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php 90 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 91 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 92 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 93 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 94 | fi 95 | 96 | } 97 | 98 | install_db() { 99 | # parse DB_HOST for port or socket references 100 | local PARTS=(${DB_HOST//\:/ }) 101 | local DB_HOSTNAME=${PARTS[0]}; 102 | local DB_SOCK_OR_PORT=${PARTS[1]}; 103 | local EXTRA="" 104 | 105 | if ! [ -z $DB_HOSTNAME ] ; then 106 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 107 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 108 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 109 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 110 | elif ! [ -z $DB_HOSTNAME ] ; then 111 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 112 | fi 113 | fi 114 | 115 | # create database 116 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 117 | } 118 | 119 | install_wp 120 | install_test_suite 121 | install_db 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WSUWP Deployments 2 | 3 | [![Build Status](https://travis-ci.org/washingtonstateuniversity/WSUWP-Deployment.svg?branch=master)](https://travis-ci.org/washingtonstateuniversity/WSUWP-Deployment) 4 | 5 | Manages the deployment of themes and plugins on WSU's instance of the [WSUWP Platform](https://github.com/washingtonstateuniversity/WSUWP-Platform). 6 | 7 | ## Overview 8 | 9 | WSU maintains all WordPress projects in GitHub repositories and uses this plugin to deploy those repositories to production. This plugin provides a custom post type on the main site's dashboard that manages webhooks for each deployment. 10 | 11 | When an administrator adds a new deployment, the plugin generates a URL that the maintainer of a GitHub repository can add to its webhook configuration. The repository maintainer configures the webhook to fire whenever someone tags a new release on the repository. 12 | 13 | Each time GitHub fires a webhook in response to a new tag, the plugin captures a deployment instance as a custom post type and provides a log of past deployments. 14 | 15 | Tags can consist of any alphanumeric string with any number of `.` or `-` characters. WSU typically uses a [semver](https://semver.org/) like version number (1.2.3) when tagging individual projects and an incremental number with leading zeros (00123) when tagging plugin and theme collections. 16 | 17 | ## Requirements 18 | 19 | * A `deploys` directory in `/var/www/wp-content/uploads`. 20 | * `mu-plugins`, `themes`, `plugins`, `build-plugins`, `build-themes`, and `platform` directories in that `deploys` directory. 21 | * The web application user must be able to write to the `deploys` directory and each of its individual sub-directories. 22 | * A regular cron event should fire `deploy-prod.sh` as another, non-web application user. WSU's cron configuration runs every minute: `*/1 * * * * sh /var/www/wp-content/plugins/wsuwp-deployment/deploy-prod.sh` 23 | * If deploying private repositories, define `WSUWP_PRIVATE_DEPLOY_TOKEN` in `wp-config.php`. This should be a token that provides authentication to private repositories in your organization. See the [GitHub REST API Authentication](https://developer.github.com/v3/#authentication) documentation. 24 | 25 | ## Deployment types 26 | 27 | Each deployment type matches the expected format of a repository with its expected location on the server. Deployments can be configured as public (default) or private. If private repositories are used, then `WSUWP_PRIVATE_DEPLOY_TOKEN` should be defined so that the plugin can retrieve the GitHub repository's archive file. 28 | 29 | The final slug at which individual themes and plugins deploy to is the slug generated when creating a deployment post type. For example, if a deployment is named "WSUWP Super Cool Plugin", it will, unless edited, deploy to `/wp-content/plugins/wsuwp-super-cool-plugin/`. 30 | 31 | ### Individual Theme 32 | 33 | Individual WordPress themes ([example](https://github.com/washingtonstateuniversity/WSUWP-spine-parent-theme)) are deployed to the `/var/www/wp-content/themes/{theme-slug}` directory. 34 | 35 | ### Individual Plugin 36 | 37 | Individual WordPress plugins ([example](https://github.com/washingtonstateuniversity/WSUWP-Content-Syndicate)) are deployed to the `/var/www/wp-content/plugins/{plugin-slug}` directory. 38 | 39 | ### Individual MU Plugin 40 | 41 | Individual WordPress must-use plugins ([example](https://github.com/washingtonstateuniversity/WSUWP-Plugin-MU-Simple-Filters)) are deployed to the `/var/www/wp-content/mu-plugins/{mu-plugin-slug}` directory. 42 | 43 | WSU uses an [MU plugin loader](https://github.com/washingtonstateuniversity/WSUWP-Plugin-Load-MU-Plugins) that works [alongside the WSUWP Platform](https://github.com/washingtonstateuniversity/WSUWP-Platform/blob/master/www/wp-content/mu-plugins/index.php#L25-L41) to load MU plugins from individual directories via a whitelist. MU plugins that exist as individual `.php` files in the `wp-content/mu-plugins/` directory are not handled by WSUWP Deployment. 44 | 45 | ### Plugin Collection 46 | 47 | Collections of plugins ([example](https://github.com/washingtonstateuniversity/WSUWP-Build-Plugins-Public)) are collectively synced to their respective `/var/www/wp-content/themes/{plugin-slug}` directories. 48 | 49 | ### MU Plugin Collection 50 | 51 | Collections of mu-plugins ([example](https://github.com/washingtonstateuniversity/WSUWP-MU-Plugin-Collection)) are collectively synced to their respective `/var/www/wp-content/mu-plugins/{mu-plugin-slug}` directories. 52 | 53 | ### Theme Collection 54 | 55 | Collections of themes ([example](https://github.com/washingtonstateuniversity/WSUWP-Build-Themes-Public)) are collectively synced to their respective `/var/www/wp-content/themes/{theme-slug}` directories. 56 | 57 | ## Development 58 | 59 | ### Testing payloads from GitHub 60 | 61 | Deployments can be tested locally by using `curl` to send a sample payload: 62 | 63 | * `curl --header "x-github-event: create" --data-urlencode payload@test-plugin-payload.txt http://wp.wsu.test/deployment/test-plugin-slug` 64 | 65 | Use `wp cron event run wsuwp_run_scheduled_deployment` to trigger the cron event. 66 | -------------------------------------------------------------------------------- /includes/wsuwp-deployment.php: -------------------------------------------------------------------------------- 1 | get_error_message() ); 48 | return; 49 | } 50 | 51 | $deploy_file = WP_CONTENT_DIR . '/uploads/deploys/' . $tag . '.zip'; 52 | 53 | // use copy and unlink because rename breaks streams. 54 | $move_new_file = @ copy( $temp_file, $deploy_file ); // @codingStandardsIgnoreLine 55 | @ unlink( $temp_file ); // @codingStandardsIgnoreLine 56 | 57 | if ( false === $move_new_file ) { 58 | send_slack_notification( 'Unable to move ' . $deploy_file ); 59 | return; 60 | } 61 | 62 | $unzip_result = unzip_file( $deploy_file, WP_CONTENT_DIR . '/uploads/deploys' ); 63 | 64 | if ( is_wp_error( $unzip_result ) ) { 65 | send_slack_notification( $unzip_result->get_error_message() ); 66 | return; 67 | } 68 | 69 | if ( 'plugin-individual' === $deploy_type ) { 70 | $destination = 'plugins/' . $directory; 71 | } elseif ( 'theme-individual' === $deploy_type ) { 72 | $destination = 'themes/' . $directory; 73 | } elseif ( 'mu-plugin-individual' === $deploy_type ) { 74 | $destination = 'mu-plugins/' . $directory; 75 | } elseif ( 'build-plugins-public' === $deploy_type || 'build-plugins-private' === $deploy_type ) { 76 | $destination = 'build-plugins/' . $directory; 77 | } elseif ( 'build-themes-public' === $deploy_type ) { 78 | $destination = 'build-themes/' . $directory; 79 | } elseif ( 'platform' === $deploy_type ) { 80 | $destination = 'platform/' . $directory; 81 | } else { 82 | send_slack_notification( 'Unsupported deploy attempt.' ); 83 | return; 84 | } 85 | 86 | // Given a URL like https://github.com/washingtonstateuniversity/WSUWP-spine-parent-theme/archive/0.27.16.zip 87 | // Determine a directory name like WSUWP-spine-parent-theme-0.27.16 88 | $url_pieces = explode( '/', $url ); 89 | $unzipped_directory = $url_pieces[4] . '-' . $tag; 90 | 91 | $skin = new \Automatic_Upgrader_Skin(); 92 | $upgrader = new \WP_Upgrader( $skin ); 93 | 94 | // "Install" the package to a shadow directory to be looped through by an external script. 95 | $install_result = $upgrader->install_package( array( 96 | 'source' => WP_CONTENT_DIR . '/uploads/deploys/' . $unzipped_directory, 97 | 'destination' => WP_CONTENT_DIR . '/uploads/deploys/' . $destination, 98 | 'clear_destination' => true, 99 | 'clear_working' => true, 100 | 'abort_if_destination_exists' => true, 101 | ) ); 102 | 103 | if ( is_wp_error( $install_result ) ) { 104 | send_slack_notification( $install_result->get_error_message() ); 105 | return; 106 | } 107 | 108 | $message = 'Version ' . $tag . ' of ' . $directory . ' has been staged for deployment on ' . gethostname() . ' by ' . $sender . '.'; 109 | send_slack_notification( $message ); 110 | } 111 | 112 | /** 113 | * Filter the headers used to download a zip file from GitHub to include 114 | * our authentication token when necessary. 115 | * 116 | * @since 3.0.0 117 | * 118 | * @param array $request An array of HTTP request arguments. 119 | * @param string $url The request URL. 120 | * 121 | * @return array Modified array of HTTP request arguments. 122 | */ 123 | function filter_authorization_header( $request, $url ) { 124 | if ( ! defined( 'WSUWP_PRIVATE_DEPLOY_TOKEN' ) ) { 125 | return $request; 126 | } 127 | 128 | $url = strtolower( $url ); 129 | 130 | // Whitelist individual private repositories. 131 | if ( 0 === strpos( $url, 'https://github.com/washingtonstateuniversity/wsuwp-build-plugins-private' ) ) { 132 | $request['headers']['Authorization'] = 'token ' . WSUWP_PRIVATE_DEPLOY_TOKEN; 133 | } elseif ( 0 === strpos( $url, 'https://github.com/washingtonstateuniversity/wsuwp-plugin-sso-authentication' ) ) { 134 | $request['headers']['Authorization'] = 'token ' . WSUWP_PRIVATE_DEPLOY_TOKEN; 135 | } elseif ( 0 === strpos( $url, 'https://github.com/washingtonstateuniversity/wsuwp-plugin-secrets' ) ) { 136 | $request['headers']['Authorization'] = 'token ' . WSUWP_PRIVATE_DEPLOY_TOKEN; 137 | } 138 | 139 | return $request; 140 | } 141 | 142 | /** 143 | * Send a notification to the WSU Web Slack. 144 | * 145 | * @since 3.0.0 146 | * 147 | * @param string $message 148 | */ 149 | function send_slack_notification( $message ) { 150 | $payload_json = wp_json_encode( array( 151 | 'channel' => '#webadmin', 152 | 'username' => 'cahnrswsuwp-deployment', 153 | 'text' => esc_js( $message ), 154 | 'icon_emoji' => ':rocket:', 155 | ) ); 156 | 157 | wp_remote_post( 'https://hooks.slack.com/services/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', array( 158 | 'body' => $payload_json, 159 | 'headers' => array( 160 | 'Content-Type' => 'application/json', 161 | ), 162 | ) ); 163 | } 164 | -------------------------------------------------------------------------------- /deploy-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Process staged deployments on a cron schedule. 4 | 5 | cd [wp-content-path]/uploads/deploys 6 | 7 | if [ ! -z "$(ls -A themes)" ]; then 8 | for theme in `ls -d themes/*/` 9 | do 10 | find "[wp-content-path]/uploads/deploys/$theme" -type d -exec chmod 775 {} \; 11 | find "[wp-content-path]/uploads/deploys/$theme" -type f -exec chmod 664 {} \; 12 | 13 | mkdir -p "[wp-content-path]/$theme" 14 | 15 | rsync -rgvzh --delete --exclude '.git' "[wp-content-path]/uploads/deploys/$theme" "[wp-content-path]/$theme" 16 | 17 | chown -R webadmin:webadmin "[wp-content-path]/$theme" 18 | 19 | rm -rf "[wp-content-path]/uploads/deploys/$theme" 20 | 21 | slack_payload="'payload={\"channel\": \"#wsuwp\", \"username\": \"wsuwp-deployment\", \"text\": \"$theme deployed\", \"icon_emoji\": \":rocket:\"}'" 22 | slack_command="curl -X POST --data-urlencode $slack_payload https://hooks.slack.com/services/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e" 23 | eval $slack_command 24 | done 25 | fi 26 | 27 | cd [wp-content-path]/uploads/deploys 28 | 29 | if [ ! -z "$(ls -A plugins)" ]; then 30 | for plugin in `ls -d plugins/*/` 31 | do 32 | find "[wp-content-path]/uploads/deploys/$plugin" -type d -exec chmod 775 {} \; 33 | find "[wp-content-path]/uploads/deploys/$plugin" -type f -exec chmod 664 {} \; 34 | 35 | mkdir -p "[wp-content-path]/$plugin" 36 | 37 | rsync -rgvzh --delete --exclude '.git' "[wp-content-path]/uploads/deploys/$plugin" "[wp-content-path]/$plugin" 38 | 39 | chown -R webadmin:webadmin "[wp-content-path]/$plugin" 40 | 41 | rm -rf "[wp-content-path]/uploads/deploys/$plugin" 42 | 43 | slack_payload="'payload={\"channel\": \"#wsuwp\", \"username\": \"wsuwp-deployment\", \"text\": \"$plugin deployed\", \"icon_emoji\": \":rocket:\"}'" 44 | slack_command="curl -X POST --data-urlencode $slack_payload https://hooks.slack.com/services/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e" 45 | eval $slack_command 46 | done 47 | fi 48 | 49 | cd [wp-content-path]/uploads/deploys 50 | 51 | if [ ! -z "$(ls -A mu-plugins)" ]; then 52 | for muplugin in `ls -d mu-plugins/*/` 53 | do 54 | find "[wp-content-path]/uploads/deploys/$muplugin" -type d -exec chmod 775 {} \; 55 | find "[wp-content-path]/uploads/deploys/$muplugin" -type f -exec chmod 664 {} \; 56 | 57 | mkdir -p "[wp-content-path]/$muplugin" 58 | 59 | rsync -rgvzh --delete --exclude '.git' "[wp-content-path]/uploads/deploys/$muplugin" "[wp-content-path]/$muplugin" 60 | 61 | chown -R webadmin:webadmin "[wp-content-path]/$muplugin" 62 | 63 | rm -rf "[wp-content-path]/uploads/deploys/$muplugin" 64 | 65 | slack_payload="'payload={\"channel\": \"#wsuwp\", \"username\": \"wsuwp-deployment\", \"text\": \"$muplugin deployed\", \"icon_emoji\": \":rocket:\"}'" 66 | slack_command="curl -X POST --data-urlencode $slack_payload https://hooks.slack.com/services/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e" 67 | eval $slack_command 68 | done 69 | fi 70 | 71 | if [ ! -z "$(ls -A build-plugins)" ]; then 72 | for plugin in `ls -d build-plugins/public-plugins-build/*/ | sed "s/build-plugins\/public-plugins-build\///g"` 73 | do 74 | find "[wp-content-path]/uploads/deploys/build-plugins/public-plugins-build/$plugin" -type d -exec chmod 775 {} \; 75 | find "[wp-content-path]/uploads/deploys/build-plugins/public-plugins-build/$plugin" -type f -exec chmod 664 {} \; 76 | 77 | mkdir -p "[wp-content-path]/plugins/$plugin" 78 | 79 | rsync -rgvzh --delete --exclude '.git' "[wp-content-path]/uploads/deploys/build-plugins/public-plugins-build/$plugin" "[wp-content-path]/plugins/$plugin" 80 | 81 | chown -R webadmin:webadmin "[wp-content-path]/plugins/$plugin" 82 | 83 | rm -rf "[wp-content-path]/uploads/deploys/build-plugins/public-plugins-build/$plugin" 84 | done 85 | 86 | for plugin in `ls -d build-plugins/private-plugins-build/*/ | sed "s/build-plugins\/private-plugins-build\///g"` 87 | do 88 | find "[wp-content-path]/uploads/deploys/build-plugins/private-plugins-build/$plugin" -type d -exec chmod 775 {} \; 89 | find "[wp-content-path]/uploads/deploys/build-plugins/private-plugins-build/$plugin" -type f -exec chmod 664 {} \; 90 | 91 | mkdir -p "[wp-content-path]/plugins/$plugin" 92 | 93 | rsync -rgvzh --delete --exclude '.git' "[wp-content-path]/uploads/deploys/build-plugins/private-plugins-build/$plugin" "[wp-content-path]/plugins/$plugin" 94 | 95 | chown -R webadmin:webadmin "[wp-content-path]/plugins/$plugin" 96 | 97 | rm -rf "[wp-content-path]/uploads/deploys/build-plugins/private-plugins-build/$plugin" 98 | done 99 | 100 | rm -rf "[wp-content-path]/uploads/deploys/build-plugins/private-plugins-build" 101 | rm -rf "[wp-content-path]/uploads/deploys/build-plugins/public-plugins-build" 102 | 103 | slack_payload="'payload={\"channel\": \"#wsuwp\", \"username\": \"wsuwp-deployment\", \"text\": \"public and private build plugins deployed\", \"icon_emoji\": \":rocket:\"}'" 104 | slack_command="curl -X POST --data-urlencode $slack_payload https://hooks.slack.com/services/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e" 105 | eval $slack_command 106 | fi 107 | 108 | if [ ! -z "$(ls -A build-themes)" ]; then 109 | for theme in `ls -d build-themes/public-themes-build/*/ | sed "s/build-themes\/public-themes-build\///g"` 110 | do 111 | find "[wp-content-path]/uploads/deploys/build-themes/public-themes-build/$theme" -type d -exec chmod 775 {} \; 112 | find "[wp-content-path]/uploads/deploys/build-themes/public-themes-build/$theme" -type f -exec chmod 664 {} \; 113 | 114 | mkdir -p "[wp-content-path]/themes/$theme" 115 | 116 | rsync -rgvzh --delete --exclude '.git' "[wp-content-path]/uploads/deploys/build-themes/public-themes-build/$theme" "[wp-content-path]/themes/$theme" 117 | 118 | chown -R webadmin:webadmin "[wp-content-path]/themes/$theme" 119 | 120 | rm -rf "[wp-content-path]/uploads/deploys/build-themes/public-themes-build/$theme" 121 | done 122 | 123 | rm -rf "[wp-content-path]/uploads/deploys/build-themes/public-themes-build" 124 | 125 | slack_payload="'payload={\"channel\": \"#wsuwp\", \"username\": \"wsuwp-deployment\", \"text\": \"public theme collection deployed\", \"icon_emoji\": \":rocket:\"}'" 126 | slack_command="curl -X POST --data-urlencode $slack_payload https://hooks.slack.com/services/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e" 127 | eval $slack_command 128 | fi 129 | 130 | if [ ! -z "$(ls -A platform)" ]; then 131 | find "[wp-content-path]/uploads/deploys/platform/wsuwp-platform/" -type d -exec chmod 775 {} \; 132 | find "[wp-content-path]/uploads/deploys/platform/wsuwp-platform/" -type f -exec chmod 664 {} \; 133 | 134 | rsync -rgvzh --delete [wp-content-path]/uploads/deploys/platform/wsuwp-platform/www/wordpress/ /var/www/wordpress/ 135 | 136 | cp -f [wp-content-path]/uploads/deploys/platform/wsuwp-platform/www/wp-content/*.php [wp-content-path]/ 137 | cp -f [wp-content-path]/uploads/deploys/platform/wsuwp-platform/www/wp-content/mu-plugins/*.php [wp-content-path]/mu-plugins/ 138 | 139 | chown -R webadmin:webadmin /var/www/wordpress/ 140 | chown -R webadmin:webadmin [wp-content-path]/*.php 141 | chown -R webadmin:webadmin [wp-content-path]/mu-plugins/*.php 142 | 143 | rm -rf "[wp-content-path]/uploads/deploys/platform/wsuwp-platform" 144 | 145 | slack_payload="'payload={\"channel\": \"#wsuwp\", \"username\": \"wsuwp-deployment\", \"text\": \"platform/wsuwp-platform deployed\", \"icon_emoji\": \":rocket:\"}'" 146 | slack_command="curl -X POST --data-urlencode $slack_payload https://hooks.slack.com/services/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e" 147 | eval $slack_command 148 | fi 149 | 150 | # Remove all previously deployed zip files. 151 | rm [wp-content-path]/uploads/deploys/*.zip 152 | -------------------------------------------------------------------------------- /tests/test-plugin-payload.txt: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "1.3.1", 3 | "ref_type": "tag", 4 | "master_branch": "master", 5 | "description": "A WordPress plugin to customize portions of the admin for Washington State University.", 6 | "pusher_type": "user", 7 | "repository": { 8 | "id": 22924753, 9 | "name": "WSUWP-Admin", 10 | "full_name": "washingtonstateuniversity/WSUWP-Admin", 11 | "owner": { 12 | "login": "washingtonstateuniversity", 13 | "id": 5225228, 14 | "avatar_url": "https://avatars1.githubusercontent.com/u/5225228?v=4", 15 | "gravatar_id": "", 16 | "url": "https://api.github.com/users/washingtonstateuniversity", 17 | "html_url": "https://github.com/washingtonstateuniversity", 18 | "followers_url": "https://api.github.com/users/washingtonstateuniversity/followers", 19 | "following_url": "https://api.github.com/users/washingtonstateuniversity/following{/other_user}", 20 | "gists_url": "https://api.github.com/users/washingtonstateuniversity/gists{/gist_id}", 21 | "starred_url": "https://api.github.com/users/washingtonstateuniversity/starred{/owner}{/repo}", 22 | "subscriptions_url": "https://api.github.com/users/washingtonstateuniversity/subscriptions", 23 | "organizations_url": "https://api.github.com/users/washingtonstateuniversity/orgs", 24 | "repos_url": "https://api.github.com/users/washingtonstateuniversity/repos", 25 | "events_url": "https://api.github.com/users/washingtonstateuniversity/events{/privacy}", 26 | "received_events_url": "https://api.github.com/users/washingtonstateuniversity/received_events", 27 | "type": "Organization", 28 | "site_admin": false 29 | }, 30 | "private": false, 31 | "html_url": "https://github.com/washingtonstateuniversity/WSUWP-Admin", 32 | "description": "A WordPress plugin to customize portions of the admin for Washington State University.", 33 | "fork": false, 34 | "url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin", 35 | "forks_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/forks", 36 | "keys_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/keys{/key_id}", 37 | "collaborators_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/collaborators{/collaborator}", 38 | "teams_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/teams", 39 | "hooks_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/hooks", 40 | "issue_events_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/issues/events{/number}", 41 | "events_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/events", 42 | "assignees_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/assignees{/user}", 43 | "branches_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/branches{/branch}", 44 | "tags_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/tags", 45 | "blobs_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/git/blobs{/sha}", 46 | "git_tags_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/git/tags{/sha}", 47 | "git_refs_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/git/refs{/sha}", 48 | "trees_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/git/trees{/sha}", 49 | "statuses_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/statuses/{sha}", 50 | "languages_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/languages", 51 | "stargazers_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/stargazers", 52 | "contributors_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/contributors", 53 | "subscribers_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/subscribers", 54 | "subscription_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/subscription", 55 | "commits_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/commits{/sha}", 56 | "git_commits_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/git/commits{/sha}", 57 | "comments_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/comments{/number}", 58 | "issue_comment_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/issues/comments{/number}", 59 | "contents_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/contents/{+path}", 60 | "compare_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/compare/{base}...{head}", 61 | "merges_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/merges", 62 | "archive_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/{archive_format}{/ref}", 63 | "downloads_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/downloads", 64 | "issues_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/issues{/number}", 65 | "pulls_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/pulls{/number}", 66 | "milestones_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/milestones{/number}", 67 | "notifications_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/notifications{?since,all,participating}", 68 | "labels_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/labels{/name}", 69 | "releases_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/releases{/id}", 70 | "deployments_url": "https://api.github.com/repos/washingtonstateuniversity/WSUWP-Admin/deployments", 71 | "created_at": "2014-08-13T17:35:20Z", 72 | "updated_at": "2017-08-26T17:47:39Z", 73 | "pushed_at": "2017-12-07T00:49:11Z", 74 | "git_url": "git://github.com/washingtonstateuniversity/WSUWP-Admin.git", 75 | "ssh_url": "git@github.com:washingtonstateuniversity/WSUWP-Admin.git", 76 | "clone_url": "https://github.com/washingtonstateuniversity/WSUWP-Admin.git", 77 | "svn_url": "https://github.com/washingtonstateuniversity/WSUWP-Admin", 78 | "homepage": null, 79 | "size": 190, 80 | "stargazers_count": 2, 81 | "watchers_count": 2, 82 | "language": "PHP", 83 | "has_issues": true, 84 | "has_projects": true, 85 | "has_downloads": true, 86 | "has_wiki": true, 87 | "has_pages": false, 88 | "forks_count": 0, 89 | "mirror_url": null, 90 | "archived": false, 91 | "open_issues_count": 3, 92 | "license": { 93 | "key": "gpl-2.0", 94 | "name": "GNU General Public License v2.0", 95 | "spdx_id": "GPL-2.0", 96 | "url": "https://api.github.com/licenses/gpl-2.0" 97 | }, 98 | "forks": 0, 99 | "open_issues": 3, 100 | "watchers": 2, 101 | "default_branch": "master" 102 | }, 103 | "organization": { 104 | "login": "washingtonstateuniversity", 105 | "id": 5225228, 106 | "url": "https://api.github.com/orgs/washingtonstateuniversity", 107 | "repos_url": "https://api.github.com/orgs/washingtonstateuniversity/repos", 108 | "events_url": "https://api.github.com/orgs/washingtonstateuniversity/events", 109 | "hooks_url": "https://api.github.com/orgs/washingtonstateuniversity/hooks", 110 | "issues_url": "https://api.github.com/orgs/washingtonstateuniversity/issues", 111 | "members_url": "https://api.github.com/orgs/washingtonstateuniversity/members{/member}", 112 | "public_members_url": "https://api.github.com/orgs/washingtonstateuniversity/public_members{/member}", 113 | "avatar_url": "https://avatars1.githubusercontent.com/u/5225228?v=4", 114 | "description": "" 115 | }, 116 | "sender": { 117 | "login": "jeremyfelt", 118 | "id": 286171, 119 | "avatar_url": "https://avatars1.githubusercontent.com/u/286171?v=4", 120 | "gravatar_id": "", 121 | "url": "https://api.github.com/users/jeremyfelt", 122 | "html_url": "https://github.com/jeremyfelt", 123 | "followers_url": "https://api.github.com/users/jeremyfelt/followers", 124 | "following_url": "https://api.github.com/users/jeremyfelt/following{/other_user}", 125 | "gists_url": "https://api.github.com/users/jeremyfelt/gists{/gist_id}", 126 | "starred_url": "https://api.github.com/users/jeremyfelt/starred{/owner}{/repo}", 127 | "subscriptions_url": "https://api.github.com/users/jeremyfelt/subscriptions", 128 | "organizations_url": "https://api.github.com/users/jeremyfelt/orgs", 129 | "repos_url": "https://api.github.com/users/jeremyfelt/repos", 130 | "events_url": "https://api.github.com/users/jeremyfelt/events{/privacy}", 131 | "received_events_url": "https://api.github.com/users/jeremyfelt/received_events", 132 | "type": "User", 133 | "site_admin": false 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/test-theme-payload.txt: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "0.5.3", 3 | "ref_type": "tag", 4 | "master_branch": "master", 5 | "description": "The theme for WSU's internal news", 6 | "pusher_type": "user", 7 | "repository": { 8 | "id": 104141405, 9 | "name": "news.wsu.edu-internal", 10 | "full_name": "washingtonstateuniversity/news.wsu.edu-internal", 11 | "owner": { 12 | "login": "washingtonstateuniversity", 13 | "id": 5225228, 14 | "avatar_url": "https://avatars1.githubusercontent.com/u/5225228?v=4", 15 | "gravatar_id": "", 16 | "url": "https://api.github.com/users/washingtonstateuniversity", 17 | "html_url": "https://github.com/washingtonstateuniversity", 18 | "followers_url": "https://api.github.com/users/washingtonstateuniversity/followers", 19 | "following_url": "https://api.github.com/users/washingtonstateuniversity/following{/other_user}", 20 | "gists_url": "https://api.github.com/users/washingtonstateuniversity/gists{/gist_id}", 21 | "starred_url": "https://api.github.com/users/washingtonstateuniversity/starred{/owner}{/repo}", 22 | "subscriptions_url": "https://api.github.com/users/washingtonstateuniversity/subscriptions", 23 | "organizations_url": "https://api.github.com/users/washingtonstateuniversity/orgs", 24 | "repos_url": "https://api.github.com/users/washingtonstateuniversity/repos", 25 | "events_url": "https://api.github.com/users/washingtonstateuniversity/events{/privacy}", 26 | "received_events_url": "https://api.github.com/users/washingtonstateuniversity/received_events", 27 | "type": "Organization", 28 | "site_admin": false 29 | }, 30 | "private": false, 31 | "html_url": "https://github.com/washingtonstateuniversity/news.wsu.edu-internal", 32 | "description": "The theme for WSU's internal news", 33 | "fork": false, 34 | "url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal", 35 | "forks_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/forks", 36 | "keys_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/keys{/key_id}", 37 | "collaborators_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/collaborators{/collaborator}", 38 | "teams_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/teams", 39 | "hooks_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/hooks", 40 | "issue_events_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/issues/events{/number}", 41 | "events_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/events", 42 | "assignees_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/assignees{/user}", 43 | "branches_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/branches{/branch}", 44 | "tags_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/tags", 45 | "blobs_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/git/blobs{/sha}", 46 | "git_tags_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/git/tags{/sha}", 47 | "git_refs_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/git/refs{/sha}", 48 | "trees_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/git/trees{/sha}", 49 | "statuses_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/statuses/{sha}", 50 | "languages_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/languages", 51 | "stargazers_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/stargazers", 52 | "contributors_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/contributors", 53 | "subscribers_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/subscribers", 54 | "subscription_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/subscription", 55 | "commits_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/commits{/sha}", 56 | "git_commits_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/git/commits{/sha}", 57 | "comments_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/comments{/number}", 58 | "issue_comment_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/issues/comments{/number}", 59 | "contents_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/contents/{+path}", 60 | "compare_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/compare/{base}...{head}", 61 | "merges_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/merges", 62 | "archive_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/{archive_format}{/ref}", 63 | "downloads_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/downloads", 64 | "issues_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/issues{/number}", 65 | "pulls_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/pulls{/number}", 66 | "milestones_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/milestones{/number}", 67 | "notifications_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/notifications{?since,all,participating}", 68 | "labels_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/labels{/name}", 69 | "releases_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/releases{/id}", 70 | "deployments_url": "https://api.github.com/repos/washingtonstateuniversity/news.wsu.edu-internal/deployments", 71 | "created_at": "2017-09-19T23:44:04Z", 72 | "updated_at": "2017-09-26T20:13:19Z", 73 | "pushed_at": "2017-12-05T23:49:03Z", 74 | "git_url": "git://github.com/washingtonstateuniversity/news.wsu.edu-internal.git", 75 | "ssh_url": "git@github.com:washingtonstateuniversity/news.wsu.edu-internal.git", 76 | "clone_url": "https://github.com/washingtonstateuniversity/news.wsu.edu-internal.git", 77 | "svn_url": "https://github.com/washingtonstateuniversity/news.wsu.edu-internal", 78 | "homepage": null, 79 | "size": 591, 80 | "stargazers_count": 0, 81 | "watchers_count": 0, 82 | "language": "HTML", 83 | "has_issues": true, 84 | "has_projects": true, 85 | "has_downloads": true, 86 | "has_wiki": true, 87 | "has_pages": false, 88 | "forks_count": 0, 89 | "mirror_url": null, 90 | "archived": false, 91 | "open_issues_count": 0, 92 | "license": { 93 | "key": "other", 94 | "name": "Other", 95 | "spdx_id": null, 96 | "url": null 97 | }, 98 | "forks": 0, 99 | "open_issues": 0, 100 | "watchers": 0, 101 | "default_branch": "master" 102 | }, 103 | "organization": { 104 | "login": "washingtonstateuniversity", 105 | "id": 5225228, 106 | "url": "https://api.github.com/orgs/washingtonstateuniversity", 107 | "repos_url": "https://api.github.com/orgs/washingtonstateuniversity/repos", 108 | "events_url": "https://api.github.com/orgs/washingtonstateuniversity/events", 109 | "hooks_url": "https://api.github.com/orgs/washingtonstateuniversity/hooks", 110 | "issues_url": "https://api.github.com/orgs/washingtonstateuniversity/issues", 111 | "members_url": "https://api.github.com/orgs/washingtonstateuniversity/members{/member}", 112 | "public_members_url": "https://api.github.com/orgs/washingtonstateuniversity/public_members{/member}", 113 | "avatar_url": "https://avatars1.githubusercontent.com/u/5225228?v=4", 114 | "description": "" 115 | }, 116 | "sender": { 117 | "login": "jeremyfelt", 118 | "id": 286171, 119 | "avatar_url": "https://avatars1.githubusercontent.com/u/286171?v=4", 120 | "gravatar_id": "", 121 | "url": "https://api.github.com/users/jeremyfelt", 122 | "html_url": "https://github.com/jeremyfelt", 123 | "followers_url": "https://api.github.com/users/jeremyfelt/followers", 124 | "following_url": "https://api.github.com/users/jeremyfelt/following{/other_user}", 125 | "gists_url": "https://api.github.com/users/jeremyfelt/gists{/gist_id}", 126 | "starred_url": "https://api.github.com/users/jeremyfelt/starred{/owner}{/repo}", 127 | "subscriptions_url": "https://api.github.com/users/jeremyfelt/subscriptions", 128 | "organizations_url": "https://api.github.com/users/jeremyfelt/orgs", 129 | "repos_url": "https://api.github.com/users/jeremyfelt/repos", 130 | "events_url": "https://api.github.com/users/jeremyfelt/events{/privacy}", 131 | "received_events_url": "https://api.github.com/users/jeremyfelt/received_events", 132 | "type": "User", 133 | "site_admin": false 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /includes/class-wsuwp-deployment.php: -------------------------------------------------------------------------------- 1 | setup_hooks(); 49 | } 50 | return self::$instance; 51 | } 52 | 53 | /** 54 | * Add hooks. 55 | * 56 | * @since 2.0.0 57 | */ 58 | public function setup_hooks() { 59 | add_action( 'init', array( $this, 'register_post_type' ) ); 60 | add_action( 'save_post', array( $this, 'save_repository_url' ), 10, 2 ); 61 | add_action( 'save_post', array( $this, 'save_deploy_type' ), 10, 2 ); 62 | } 63 | 64 | /** 65 | * Register the deployment and deployment instance post types to track 66 | * the deployments that have been created and then initiated. 67 | */ 68 | public function register_post_type() { 69 | // Only register the post type on the main network's main site. 70 | if ( ! is_main_network() || ! is_main_site() ) { 71 | return; 72 | } 73 | 74 | $labels = array( 75 | 'name' => 'Deployments', 76 | 'singular_name' => 'Deployment', 77 | 'add_new' => 'Add New', 78 | 'add_new_item' => 'Add New Deployment', 79 | 'edit_item' => 'Edit Deployment', 80 | 'new_item' => 'New Deployment', 81 | 'all_items' => 'All Deployments', 82 | 'view_item' => 'View Deployments', 83 | 'search_items' => 'Search Deployments', 84 | 'not_found' => 'No deployments found', 85 | 'not_found_in_trash' => 'No deployments found in Trash', 86 | 'menu_name' => 'Deployments', 87 | ); 88 | 89 | $args = array( 90 | 'labels' => $labels, 91 | 'public' => true, 92 | 'publicly_queryable' => true, 93 | 'show_ui' => true, 94 | 'show_in_menu' => true, 95 | 'query_var' => true, 96 | 'rewrite' => array( 97 | 'slug' => 'deployment', 98 | ), 99 | 'has_archive' => false, 100 | 'hierarchical' => false, 101 | 'supports' => array( 'title' ), 102 | ); 103 | register_post_type( $this->post_type_slug, $args ); 104 | 105 | $instance_labels = array( 106 | 'name' => 'Deployment Instances', 107 | 'singular_name' => 'Deployment Instance', 108 | 'add_new' => 'Add New', 109 | 'add_new_item' => 'Add New Deployment Instance', 110 | 'edit_item' => 'Edit Deployment Instance', 111 | 'new_item' => 'New Deployment Instance', 112 | 'all_items' => 'All Deployment Instances', 113 | 'view_item' => 'View Deployment Instances', 114 | 'search_items' => 'Search Deployment Instances', 115 | 'not_found' => 'No deployment instances found', 116 | 'not_found_in_trash' => 'No deployment instances found in Trash', 117 | 'menu_name' => 'Deployment Instances', 118 | ); 119 | 120 | $instance_args = array( 121 | 'labels' => $instance_labels, 122 | 'public' => false, 123 | 'publicly_queryable' => false, 124 | 'show_ui' => true, 125 | 'rewrite' => array( 126 | 'slug' => 'deployment-instance', 127 | ), 128 | 'has_archive' => false, 129 | 'hierarchical' => false, 130 | 'supports' => array( 'title' ), 131 | ); 132 | register_post_type( $this->deploy_instance_slug, $instance_args ); 133 | 134 | add_action( 'template_redirect', array( $this, 'template_redirect' ) ); 135 | add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 ); 136 | } 137 | 138 | /** 139 | * Capture the actual deployment information when notified from 140 | * version control. This avoids the complete load of the template. 141 | */ 142 | public function template_redirect() { 143 | if ( ! is_singular( $this->post_type_slug ) ) { 144 | return; 145 | } 146 | 147 | if ( isset( $_SERVER['HTTP_X_GITHUB_EVENT'] ) && 'release' === $_SERVER['HTTP_X_GITHUB_EVENT'] && ! empty( $_POST['payload'] ) ) { // @codingStandardsIgnoreLine 148 | $this->handle_release_webhook(); 149 | } elseif ( ! isset( $_SERVER['HTTP_X_GITHUB_EVENT'] ) ) { 150 | wp_safe_redirect( home_url() ); 151 | } 152 | 153 | die(); 154 | } 155 | 156 | 157 | /** 158 | * Handle the 'create' event passed via webhook from GitHub. 159 | */ 160 | private function handle_release_webhook() { 161 | // This seems overkill, but it is working. 162 | $payload = wp_unslash( $_POST['payload'] ); // @codingStandardsIgnoreLine 163 | $payload = json_decode( $payload ); 164 | 165 | $deployment_data = array( 166 | 'tag' => false, 167 | 'ref_type' => 'release', 168 | 'sender' => false, 169 | 'avatar_url' => false, 170 | ); 171 | 172 | // Check for a tag reference and store it. 173 | if ( isset( $payload->release->tag_name ) ) { 174 | $deployment_data['tag'] = $payload->release->tag_name; 175 | } else { 176 | die(); 177 | } 178 | 179 | 180 | if ( isset( $payload->sender ) ) { 181 | $deployment_data['sender'] = $payload->sender->login; 182 | $deployment_data['avatar_url'] = $payload->sender->avatar_url; 183 | } 184 | 185 | $deployment = get_post( get_the_ID() ); 186 | $time = time(); 187 | 188 | // Build the deployment instance. 189 | $title = date( 'Y-m-d H:i:s', $time ) . ' | ' . esc_html( $deployment->post_title ) . ' | ' . esc_html( $deployment_data['tag'] ) . ' | ' . esc_html( $deployment_data['sender'] ); 190 | $args = array( 191 | 'post_type' => $this->deploy_instance_slug, 192 | 'post_title' => $title, 193 | 'post_status' => 'publish', 194 | ); 195 | $instance_id = wp_insert_post( $args ); 196 | 197 | add_post_meta( $instance_id, '_deploy_data', $deployment_data, true ); 198 | 199 | $deployments = get_post_meta( get_the_ID(), '_deploy_instances', true ); 200 | if ( ! is_array( $deployments ) ) { 201 | $deployments = array(); 202 | } 203 | $deployments[ $time ] = absint( $instance_id ); 204 | update_post_meta( get_the_ID(), '_deploy_instances', $deployments ); 205 | 206 | $this->handle_deploy( $deployment_data, $deployment ); 207 | 208 | die(); 209 | } 210 | 211 | /** 212 | * Handle the 'create' event passed via webhook from GitHub. 213 | */ 214 | private function handle_create_webhook() { 215 | // This seems overkill, but it is working. 216 | $payload = wp_unslash( $_POST['payload'] ); // @codingStandardsIgnoreLine 217 | $payload = json_decode( $payload ); 218 | 219 | $deployment_data = array( 220 | 'tag' => false, 221 | 'ref_type' => false, 222 | 'sender' => false, 223 | 'avatar_url' => false, 224 | ); 225 | 226 | // Check for a tag reference and store it. 227 | if ( isset( $payload->ref ) ) { 228 | $deployment_data['tag'] = $payload->ref; 229 | } else { 230 | die(); 231 | } 232 | 233 | // Check to make sure a tag is being created and not a branch. 234 | if ( isset( $payload->ref_type ) && 'tag' === $payload->ref_type ) { 235 | $deployment_data['ref_type'] = $payload->ref_type; 236 | } else { 237 | die(); 238 | } 239 | 240 | if ( isset( $payload->sender ) ) { 241 | $deployment_data['sender'] = $payload->sender->login; 242 | $deployment_data['avatar_url'] = $payload->sender->avatar_url; 243 | } 244 | 245 | $deployment = get_post( get_the_ID() ); 246 | $time = time(); 247 | 248 | // Build the deployment instance. 249 | $title = date( 'Y-m-d H:i:s', $time ) . ' | ' . esc_html( $deployment->post_title ) . ' | ' . esc_html( $deployment_data['tag'] ) . ' | ' . esc_html( $deployment_data['sender'] ); 250 | $args = array( 251 | 'post_type' => $this->deploy_instance_slug, 252 | 'post_title' => $title, 253 | 'post_status' => 'publish', 254 | ); 255 | $instance_id = wp_insert_post( $args ); 256 | 257 | add_post_meta( $instance_id, '_deploy_data', $deployment_data, true ); 258 | 259 | $deployments = get_post_meta( get_the_ID(), '_deploy_instances', true ); 260 | if ( ! is_array( $deployments ) ) { 261 | $deployments = array(); 262 | } 263 | $deployments[ $time ] = absint( $instance_id ); 264 | update_post_meta( get_the_ID(), '_deploy_instances', $deployments ); 265 | 266 | $this->handle_deploy( $deployment_data, $deployment ); 267 | 268 | die(); 269 | } 270 | 271 | /** 272 | * Hand deployment details to the relevant script on the production machine. Script 273 | * is called as: 274 | * 275 | * @since 2.0.0 Differentiate between public and private deployments. 276 | * 277 | * deploy-build.sh 0.0.1 directory-of-theme https://github.com/washingtonstateuniversity/repository.git theme-individual public 278 | * SCRIPT ^ TAG ^ DIRECTORY ^ REPOSITORY URL ^ TYPE ^ PUBLIC ^ 279 | * 280 | * @param array $deployment Array of webhook information. 281 | * @param WP_Post $post Object containing the project being deployed. 282 | */ 283 | private function handle_deploy( $deployment, $post ) { 284 | // Tags can only be alphanumeric with dashes and dots 285 | if ( 0 === preg_match( '|^([a-zA-Z0-9-.])+$|', $deployment['tag'] ) ) { 286 | die( 'Invalid tag format' ); 287 | } 288 | 289 | $repository_directory = sanitize_key( $post->post_name ); 290 | 291 | $deploy_type = get_post_meta( $post->ID, '_deploy_type', true ); 292 | if ( ! in_array( $deploy_type, $this->allowed_deploy_types, true ) ) { 293 | $deploy_type = 'theme-individual'; 294 | } 295 | 296 | $deploy_public = get_post_meta( $post->ID, '_deploy_public', true ); 297 | if ( 'private' !== $deploy_public ) { 298 | $deploy_public = 'public'; 299 | } 300 | 301 | $repository_url = get_post_meta( $post->ID, '_repository_url', true ); 302 | if ( false === $repository_url || empty( $repository_url ) ) { 303 | return; 304 | } else { 305 | $repository_url = esc_url( $repository_url ); 306 | } 307 | 308 | // Remove .git from any existing repository URLs. 309 | $repository_url = str_replace( '.git', '', $repository_url ); 310 | $repository_url = trailingslashit( $repository_url ) . 'archive/' . $deployment['tag'] . '.zip'; 311 | 312 | wp_schedule_single_event( time() + 1, 'wsuwp_run_scheduled_deployment', array( 313 | 'tag' => $deployment['tag'], 314 | 'directory' => $repository_directory, 315 | 'url' => $repository_url, 316 | 'deploy_type' => $deploy_type, 317 | 'sender' => $deployment['sender'], 318 | ) ); 319 | } 320 | 321 | /** 322 | * Add the meta boxes used by our deployment post types. 323 | * 324 | * @param $post_type 325 | * @param $post 326 | */ 327 | public function add_meta_boxes( $post_type, $post ) { 328 | if ( $this->deploy_instance_slug !== $post_type && $this->post_type_slug !== $post_type ) { 329 | return; 330 | } 331 | 332 | add_meta_box( 'wsuwp_deploy_repository', 'Repository URL', array( $this, 'display_repository_url' ), $this->post_type_slug, 'normal' ); 333 | add_meta_box( 'wsuwp_deploy_type', 'Deploy Type', array( $this, 'display_deploy_type' ), $this->post_type_slug, 'normal' ); 334 | add_meta_box( 'wsuwp_deploy_instances', 'Deploy Instances', array( $this, 'display_deploy_instances' ), $this->post_type_slug, 'normal' ); 335 | add_meta_box( 'wsuwp_deploy_instance_data', 'Deploy Payload', array( $this, 'display_instance_payload' ), $this->deploy_instance_slug, 'normal' ); 336 | } 337 | 338 | /** 339 | * Display a meta box for storing the repository's URL. 340 | * 341 | * @param WP_Post $post Current post data. 342 | */ 343 | public function display_repository_url( $post ) { 344 | if ( $this->post_type_slug !== $post->post_type ) { 345 | return; 346 | } 347 | 348 | $repository_url = get_post_meta( $post->ID, '_repository_url', true ); 349 | 350 | if ( ! $repository_url ) { 351 | $repository_url = ''; 352 | } 353 | 354 | wp_nonce_field( 'wsuwp-save-repository', '_wsuwp_repository_nonce' ); 355 | ?> 356 | 357 | 358 | post_type_slug !== $post->post_type ) { 373 | return; 374 | } 375 | 376 | if ( ! isset( $_POST['_wsuwp_repository_nonce'] ) || ! wp_verify_nonce( $_POST['_wsuwp_repository_nonce'], 'wsuwp-save-repository' ) ) { 377 | return; 378 | } 379 | 380 | if ( 'auto-draft' === $post->post_status ) { 381 | return; 382 | } 383 | 384 | if ( isset( $_POST['wsuwp_deploy_repository'] ) && ! empty( trim( $_POST['wsuwp_deploy_repository'] ) ) ) { 385 | update_post_meta( $post_id, '_repository_url', esc_url_raw( $_POST['wsuwp_deploy_repository'] ) ); 386 | } 387 | } 388 | 389 | /** 390 | * Store the deploy type for a deployment. 391 | * 392 | * @param WP_Post $post Current post being edited. 393 | */ 394 | public function display_deploy_type( $post ) { 395 | if ( $this->post_type_slug !== $post->post_type ) { 396 | return; 397 | } 398 | 399 | $deployment_type = get_post_meta( $post->ID, '_deploy_type', true ); 400 | $deployment_public = get_post_meta( $post->ID, '_deploy_public', true ); 401 | 402 | // Force a deployment type from those we expect. 403 | if ( ! in_array( $deployment_type, $this->allowed_deploy_types, true ) ) { 404 | $deployment_type = 'theme-individual'; 405 | } 406 | 407 | if ( 'private' !== $deployment_public ) { 408 | $deployment_public = 'public'; 409 | } 410 | 411 | wp_nonce_field( 'wsuwp-save-deploy-type', '_wsuwp_deploy_type_nonce' ); 412 | ?> 413 | 414 | 425 |
426 | 427 | 431 | post_type_slug !== $post->post_type ) { 446 | return; 447 | } 448 | 449 | if ( ! isset( $_POST['_wsuwp_deploy_type_nonce'] ) || ! wp_verify_nonce( $_POST['_wsuwp_deploy_type_nonce'], 'wsuwp-save-deploy-type' ) ) { 450 | return; 451 | } 452 | 453 | if ( 'auto-draft' === $post->post_status ) { 454 | return; 455 | } 456 | 457 | if ( ! isset( $_POST['wsuwp_deploy_type'] ) || ! in_array( $_POST['wsuwp_deploy_type'], $this->allowed_deploy_types, true ) ) { 458 | $deploy_type = 'theme-individual'; 459 | } else { 460 | $deploy_type = $_POST['wsuwp_deploy_type']; 461 | } 462 | 463 | if ( ! isset( $_POST['wsuwp_deploy_public'] ) || 'private' !== $_POST['wsuwp_deploy_public'] ) { 464 | $deploy_public = 'public'; 465 | } else { 466 | $deploy_public = 'private'; 467 | } 468 | 469 | update_post_meta( $post_id, '_deploy_type', $deploy_type ); 470 | update_post_meta( $post_id, '_deploy_public', $deploy_public ); 471 | } 472 | 473 | /** 474 | * Display the deployment instances that have occurred on this 475 | * deployment configuration. 476 | * 477 | * @param $post 478 | */ 479 | public function display_deploy_instances( $post ) { 480 | if ( $this->post_type_slug !== $post->post_type ) { 481 | return; 482 | } 483 | 484 | $deployments = get_post_meta( get_the_ID(), '_deploy_instances', true ); 485 | if ( ! empty( $deployments ) ) { 486 | $deployments = array_reverse( $deployments, true ); 487 | echo '
    '; 488 | foreach ( $deployments as $time => $instance_id ) { 489 | $deploy_data = get_post_meta( $instance_id, '_deploy_data', true ); 490 | if ( ! $deploy_data ) { 491 | $deploy_tag = 'View'; 492 | } else { 493 | $deploy_tag = $deploy_data['tag']; 494 | } 495 | 496 | echo '
  • ' . esc_html( date( 'Y-m-d H:i:s', $time ) ) . ' | ' . esc_html( $deploy_tag ) . '
  • '; 497 | } 498 | echo '
      '; 499 | } 500 | } 501 | 502 | /** 503 | * Display the payload data from a deployment in the instance meta box. 504 | * @param $post 505 | */ 506 | public function display_instance_payload( $post ) { 507 | $deploy_data = get_post_meta( $post->ID, '_deploy_data', true ); 508 | 509 | if ( isset( $deploy_data['tag'] ) ) { 510 | echo 'Tag: ' . esc_html( $deploy_data['tag'] ) . '
      '; 511 | } 512 | 513 | if ( isset( $deploy_data['ref_type'] ) ) { 514 | echo 'Ref Type: ' . esc_html( $deploy_data['ref_type'] ) . '
      '; 515 | } 516 | 517 | if ( isset( $deploy_data['sender'] ) ) { 518 | echo 'Author: ' . esc_html( $deploy_data['sender'] ) . '
      '; 519 | echo ''; 520 | } 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | WSUWP Deployment 2 | 3 | Copyright 2014-2018 by Washington State University 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | 19 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 2, June 1991 23 | 24 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 25 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 26 | Everyone is permitted to copy and distribute verbatim copies 27 | of this license document, but changing it is not allowed. 28 | 29 | Preamble 30 | 31 | The licenses for most software are designed to take away your 32 | freedom to share and change it. By contrast, the GNU General Public 33 | License is intended to guarantee your freedom to share and change free 34 | software--to make sure the software is free for all its users. This 35 | General Public License applies to most of the Free Software 36 | Foundation's software and to any other program whose authors commit to 37 | using it. (Some other Free Software Foundation software is covered by 38 | the GNU Lesser General Public License instead.) You can apply it to 39 | your programs, too. 40 | 41 | When we speak of free software, we are referring to freedom, not 42 | price. Our General Public Licenses are designed to make sure that you 43 | have the freedom to distribute copies of free software (and charge for 44 | this service if you wish), that you receive source code or can get it 45 | if you want it, that you can change the software or use pieces of it 46 | in new free programs; and that you know you can do these things. 47 | 48 | To protect your rights, we need to make restrictions that forbid 49 | anyone to deny you these rights or to ask you to surrender the rights. 50 | These restrictions translate to certain responsibilities for you if you 51 | distribute copies of the software, or if you modify it. 52 | 53 | For example, if you distribute copies of such a program, whether 54 | gratis or for a fee, you must give the recipients all the rights that 55 | you have. You must make sure that they, too, receive or can get the 56 | source code. And you must show them these terms so they know their 57 | rights. 58 | 59 | We protect your rights with two steps: (1) copyright the software, and 60 | (2) offer you this license which gives you legal permission to copy, 61 | distribute and/or modify the software. 62 | 63 | Also, for each author's protection and ours, we want to make certain 64 | that everyone understands that there is no warranty for this free 65 | software. If the software is modified by someone else and passed on, we 66 | want its recipients to know that what they have is not the original, so 67 | that any problems introduced by others will not reflect on the original 68 | authors' reputations. 69 | 70 | Finally, any free program is threatened constantly by software 71 | patents. We wish to avoid the danger that redistributors of a free 72 | program will individually obtain patent licenses, in effect making the 73 | program proprietary. To prevent this, we have made it clear that any 74 | patent must be licensed for everyone's free use or not licensed at all. 75 | 76 | The precise terms and conditions for copying, distribution and 77 | modification follow. 78 | 79 | GNU GENERAL PUBLIC LICENSE 80 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 81 | 82 | 0. This License applies to any program or other work which contains 83 | a notice placed by the copyright holder saying it may be distributed 84 | under the terms of this General Public License. The "Program", below, 85 | refers to any such program or work, and a "work based on the Program" 86 | means either the Program or any derivative work under copyright law: 87 | that is to say, a work containing the Program or a portion of it, 88 | either verbatim or with modifications and/or translated into another 89 | language. (Hereinafter, translation is included without limitation in 90 | the term "modification".) Each licensee is addressed as "you". 91 | 92 | Activities other than copying, distribution and modification are not 93 | covered by this License; they are outside its scope. The act of 94 | running the Program is not restricted, and the output from the Program 95 | is covered only if its contents constitute a work based on the 96 | Program (independent of having been made by running the Program). 97 | Whether that is true depends on what the Program does. 98 | 99 | 1. You may copy and distribute verbatim copies of the Program's 100 | source code as you receive it, in any medium, provided that you 101 | conspicuously and appropriately publish on each copy an appropriate 102 | copyright notice and disclaimer of warranty; keep intact all the 103 | notices that refer to this License and to the absence of any warranty; 104 | and give any other recipients of the Program a copy of this License 105 | along with the Program. 106 | 107 | You may charge a fee for the physical act of transferring a copy, and 108 | you may at your option offer warranty protection in exchange for a fee. 109 | 110 | 2. You may modify your copy or copies of the Program or any portion 111 | of it, thus forming a work based on the Program, and copy and 112 | distribute such modifications or work under the terms of Section 1 113 | above, provided that you also meet all of these conditions: 114 | 115 | a) You must cause the modified files to carry prominent notices 116 | stating that you changed the files and the date of any change. 117 | 118 | b) You must cause any work that you distribute or publish, that in 119 | whole or in part contains or is derived from the Program or any 120 | part thereof, to be licensed as a whole at no charge to all third 121 | parties under the terms of this License. 122 | 123 | c) If the modified program normally reads commands interactively 124 | when run, you must cause it, when started running for such 125 | interactive use in the most ordinary way, to print or display an 126 | announcement including an appropriate copyright notice and a 127 | notice that there is no warranty (or else, saying that you provide 128 | a warranty) and that users may redistribute the program under 129 | these conditions, and telling the user how to view a copy of this 130 | License. (Exception: if the Program itself is interactive but 131 | does not normally print such an announcement, your work based on 132 | the Program is not required to print an announcement.) 133 | 134 | These requirements apply to the modified work as a whole. If 135 | identifiable sections of that work are not derived from the Program, 136 | and can be reasonably considered independent and separate works in 137 | themselves, then this License, and its terms, do not apply to those 138 | sections when you distribute them as separate works. But when you 139 | distribute the same sections as part of a whole which is a work based 140 | on the Program, the distribution of the whole must be on the terms of 141 | this License, whose permissions for other licensees extend to the 142 | entire whole, and thus to each and every part regardless of who wrote it. 143 | 144 | Thus, it is not the intent of this section to claim rights or contest 145 | your rights to work written entirely by you; rather, the intent is to 146 | exercise the right to control the distribution of derivative or 147 | collective works based on the Program. 148 | 149 | In addition, mere aggregation of another work not based on the Program 150 | with the Program (or with a work based on the Program) on a volume of 151 | a storage or distribution medium does not bring the other work under 152 | the scope of this License. 153 | 154 | 3. You may copy and distribute the Program (or a work based on it, 155 | under Section 2) in object code or executable form under the terms of 156 | Sections 1 and 2 above provided that you also do one of the following: 157 | 158 | a) Accompany it with the complete corresponding machine-readable 159 | source code, which must be distributed under the terms of Sections 160 | 1 and 2 above on a medium customarily used for software interchange; or, 161 | 162 | b) Accompany it with a written offer, valid for at least three 163 | years, to give any third party, for a charge no more than your 164 | cost of physically performing source distribution, a complete 165 | machine-readable copy of the corresponding source code, to be 166 | distributed under the terms of Sections 1 and 2 above on a medium 167 | customarily used for software interchange; or, 168 | 169 | c) Accompany it with the information you received as to the offer 170 | to distribute corresponding source code. (This alternative is 171 | allowed only for noncommercial distribution and only if you 172 | received the program in object code or executable form with such 173 | an offer, in accord with Subsection b above.) 174 | 175 | The source code for a work means the preferred form of the work for 176 | making modifications to it. For an executable work, complete source 177 | code means all the source code for all modules it contains, plus any 178 | associated interface definition files, plus the scripts used to 179 | control compilation and installation of the executable. However, as a 180 | special exception, the source code distributed need not include 181 | anything that is normally distributed (in either source or binary 182 | form) with the major components (compiler, kernel, and so on) of the 183 | operating system on which the executable runs, unless that component 184 | itself accompanies the executable. 185 | 186 | If distribution of executable or object code is made by offering 187 | access to copy from a designated place, then offering equivalent 188 | access to copy the source code from the same place counts as 189 | distribution of the source code, even though third parties are not 190 | compelled to copy the source along with the object code. 191 | 192 | 4. You may not copy, modify, sublicense, or distribute the Program 193 | except as expressly provided under this License. Any attempt 194 | otherwise to copy, modify, sublicense or distribute the Program is 195 | void, and will automatically terminate your rights under this License. 196 | However, parties who have received copies, or rights, from you under 197 | this License will not have their licenses terminated so long as such 198 | parties remain in full compliance. 199 | 200 | 5. You are not required to accept this License, since you have not 201 | signed it. However, nothing else grants you permission to modify or 202 | distribute the Program or its derivative works. These actions are 203 | prohibited by law if you do not accept this License. Therefore, by 204 | modifying or distributing the Program (or any work based on the 205 | Program), you indicate your acceptance of this License to do so, and 206 | all its terms and conditions for copying, distributing or modifying 207 | the Program or works based on it. 208 | 209 | 6. Each time you redistribute the Program (or any work based on the 210 | Program), the recipient automatically receives a license from the 211 | original licensor to copy, distribute or modify the Program subject to 212 | these terms and conditions. You may not impose any further 213 | restrictions on the recipients' exercise of the rights granted herein. 214 | You are not responsible for enforcing compliance by third parties to 215 | this License. 216 | 217 | 7. If, as a consequence of a court judgment or allegation of patent 218 | infringement or for any other reason (not limited to patent issues), 219 | conditions are imposed on you (whether by court order, agreement or 220 | otherwise) that contradict the conditions of this License, they do not 221 | excuse you from the conditions of this License. If you cannot 222 | distribute so as to satisfy simultaneously your obligations under this 223 | License and any other pertinent obligations, then as a consequence you 224 | may not distribute the Program at all. For example, if a patent 225 | license would not permit royalty-free redistribution of the Program by 226 | all those who receive copies directly or indirectly through you, then 227 | the only way you could satisfy both it and this License would be to 228 | refrain entirely from distribution of the Program. 229 | 230 | If any portion of this section is held invalid or unenforceable under 231 | any particular circumstance, the balance of the section is intended to 232 | apply and the section as a whole is intended to apply in other 233 | circumstances. 234 | 235 | It is not the purpose of this section to induce you to infringe any 236 | patents or other property right claims or to contest validity of any 237 | such claims; this section has the sole purpose of protecting the 238 | integrity of the free software distribution system, which is 239 | implemented by public license practices. Many people have made 240 | generous contributions to the wide range of software distributed 241 | through that system in reliance on consistent application of that 242 | system; it is up to the author/donor to decide if he or she is willing 243 | to distribute software through any other system and a licensee cannot 244 | impose that choice. 245 | 246 | This section is intended to make thoroughly clear what is believed to 247 | be a consequence of the rest of this License. 248 | 249 | 8. If the distribution and/or use of the Program is restricted in 250 | certain countries either by patents or by copyrighted interfaces, the 251 | original copyright holder who places the Program under this License 252 | may add an explicit geographical distribution limitation excluding 253 | those countries, so that distribution is permitted only in or among 254 | countries not thus excluded. In such case, this License incorporates 255 | the limitation as if written in the body of this License. 256 | 257 | 9. The Free Software Foundation may publish revised and/or new versions 258 | of the General Public License from time to time. Such new versions will 259 | be similar in spirit to the present version, but may differ in detail to 260 | address new problems or concerns. 261 | 262 | Each version is given a distinguishing version number. If the Program 263 | specifies a version number of this License which applies to it and "any 264 | later version", you have the option of following the terms and conditions 265 | either of that version or of any later version published by the Free 266 | Software Foundation. If the Program does not specify a version number of 267 | this License, you may choose any version ever published by the Free Software 268 | Foundation. 269 | 270 | 10. If you wish to incorporate parts of the Program into other free 271 | programs whose distribution conditions are different, write to the author 272 | to ask for permission. For software which is copyrighted by the Free 273 | Software Foundation, write to the Free Software Foundation; we sometimes 274 | make exceptions for this. Our decision will be guided by the two goals 275 | of preserving the free status of all derivatives of our free software and 276 | of promoting the sharing and reuse of software generally. 277 | 278 | NO WARRANTY 279 | 280 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 281 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 282 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 283 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 284 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 285 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 286 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 287 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 288 | REPAIR OR CORRECTION. 289 | 290 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 291 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 292 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 293 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 294 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 295 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 296 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 297 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 298 | POSSIBILITY OF SUCH DAMAGES. 299 | 300 | END OF TERMS AND CONDITIONS 301 | 302 | How to Apply These Terms to Your New Programs 303 | 304 | If you develop a new program, and you want it to be of the greatest 305 | possible use to the public, the best way to achieve this is to make it 306 | free software which everyone can redistribute and change under these terms. 307 | 308 | To do so, attach the following notices to the program. It is safest 309 | to attach them to the start of each source file to most effectively 310 | convey the exclusion of warranty; and each file should have at least 311 | the "copyright" line and a pointer to where the full notice is found. 312 | 313 | 314 | Copyright (C) 315 | 316 | This program is free software; you can redistribute it and/or modify 317 | it under the terms of the GNU General Public License as published by 318 | the Free Software Foundation; either version 2 of the License, or 319 | (at your option) any later version. 320 | 321 | This program is distributed in the hope that it will be useful, 322 | but WITHOUT ANY WARRANTY; without even the implied warranty of 323 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 324 | GNU General Public License for more details. 325 | 326 | You should have received a copy of the GNU General Public License along 327 | with this program; if not, write to the Free Software Foundation, Inc., 328 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 329 | 330 | Also add information on how to contact you by electronic and paper mail. 331 | 332 | If the program is interactive, make it output a short notice like this 333 | when it starts in an interactive mode: 334 | 335 | Gnomovision version 69, Copyright (C) year name of author 336 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 337 | This is free software, and you are welcome to redistribute it 338 | under certain conditions; type `show c' for details. 339 | 340 | The hypothetical commands `show w' and `show c' should show the appropriate 341 | parts of the General Public License. Of course, the commands you use may 342 | be called something other than `show w' and `show c'; they could even be 343 | mouse-clicks or menu items--whatever suits your program. 344 | 345 | You should also get your employer (if you work as a programmer) or your 346 | school, if any, to sign a "copyright disclaimer" for the program, if 347 | necessary. Here is a sample; alter the names: 348 | 349 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 350 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 351 | 352 | , 1 April 1989 353 | Ty Coon, President of Vice 354 | 355 | This General Public License does not permit incorporating your program into 356 | proprietary programs. If your program is a subroutine library, you may 357 | consider it more useful to permit linking proprietary applications with the 358 | library. If this is what you want to do, use the GNU Lesser General 359 | Public License instead of this License. 360 | 361 | WRITTEN OFFER 362 | 363 | The source code for any program binaries or compressed scripts that are 364 | included with WSUWP Deployment can be freely obtained at the following URL: 365 | 366 | https://github.com/washingtonstateuniversity/WSUWP-Deployment/ 367 | --------------------------------------------------------------------------------